diff --git a/.golangci.yml b/.golangci.yml index 07906283..5953e84c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,7 +11,7 @@ formatters: settings: goimports: local-prefixes: - - github.com/autobrr/qui + - github.com/autobrr/netronome linters: enable: diff --git a/README.md b/README.md index a9137a5a..84a301ca 100644 --- a/README.md +++ b/README.md @@ -794,8 +794,6 @@ NETRONOME__LIBRESPEED_TIMEOUT=60 # LibreSpeed timeout (seconds) ```bash NETRONOME__DEFAULT_PAGE=1 # Default page number -NETRONOME__DEFAULT_PAGE_SIZE=20 # Default items per page -NETRONOME__MAX_PAGE_SIZE=100 # Maximum items per page NETRONOME__DEFAULT_TIME_RANGE=1w # Default time range for queries NETRONOME__DEFAULT_LIMIT=20 # Default query limit ``` @@ -811,11 +809,9 @@ NETRONOME__GEOIP_ASN_DATABASE_PATH= # Path to GeoLite2-ASN.mmdb ```bash NETRONOME__PACKETLOSS_ENABLED=true # Enable packet loss monitoring -NETRONOME__PACKETLOSS_DEFAULT_INTERVAL=3600 # Default test interval (seconds) -NETRONOME__PACKETLOSS_DEFAULT_PACKET_COUNT=10 # Packets per test NETRONOME__PACKETLOSS_MAX_CONCURRENT_MONITORS=10 # Max concurrent monitors NETRONOME__PACKETLOSS_PRIVILEGED_MODE=true # Use privileged ICMP mode -NETRONOME__PACKETLOSS_RESTORE_MONITORS_ON_STARTUP=false # Restore monitors on startup +NETRONOME__PACKETLOSS_MTR_ENABLE_DNS=false # Resolve hostnames in MTR output ``` ### Agent Configuration @@ -833,7 +829,6 @@ NETRONOME__AGENT_DISK_EXCLUDES= # Comma-separated paths to exclude ```bash NETRONOME__MONITOR_ENABLED=true # Enable system monitoring -NETRONOME__MONITOR_RECONNECT_INTERVAL=30s # Agent reconnection interval ``` ### Tailscale Configuration @@ -862,7 +857,6 @@ NETRONOME__TAILSCALE_DISCOVERY_PREFIX= # Hostname prefix filter # Deprecated (for backward compatibility) NETRONOME__TAILSCALE_PREFER_HOST=false # Prefer host mode over tsnet NETRONOME__TAILSCALE_AGENT_ENABLED=false # Enable agent mode -NETRONOME__TAILSCALE_AGENT_ACCEPT_ROUTES=true # Accept Tailscale routes ``` diff --git a/config/config.toml b/config/config.toml index 648c4c70..1b0d98e2 100644 --- a/config/config.toml +++ b/config/config.toml @@ -46,15 +46,12 @@ asn_database_path = "./GeoLite2-ASN.mmdb" [packetloss] enabled = true -default_interval = 3600 -default_packet_count = 10 max_concurrent_monitors = 10 privileged_mode = true mtr_enable_dns = false [monitor] enabled = true -reconnect_interval = "30s" [tailscale] enabled = true diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 36ec8f3b..030a372e 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -10,8 +10,6 @@ import ( "errors" "fmt" "strings" - - "golang.org/x/crypto/bcrypt" ) var ( @@ -30,19 +28,6 @@ var ( // return nil //} -func HashPassword(password string) (string, error) { - bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - if err != nil { - return "", err - } - return string(bytes), nil -} - -func CheckPassword(password, hash string) bool { - err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - return err == nil -} - // MemoryOnlyPrefix is used to mark tokens that should only exist in memory const MemoryOnlyPrefix = "mem_" @@ -70,9 +55,9 @@ func VerifyToken(signedToken, secret string) (string, error) { if secret != "" { parts := strings.Split(signedToken, ".") - + var token, signature string - + // Handle different token formats if len(parts) == 2 { // Regular signed token: token.signature diff --git a/internal/auth/oidc.go b/internal/auth/oidc.go index f561a1f5..4bad335a 100644 --- a/internal/auth/oidc.go +++ b/internal/auth/oidc.go @@ -263,10 +263,6 @@ func getProviderEndpoints(ctx context.Context, client *http.Client, issuer strin }, discovery.UserinfoURL, nil } -func (c *OIDCConfig) AuthURL() string { - return c.OAuth2Config.AuthCodeURL("state") -} - func containsScope(scopes []string, target string) bool { return slices.Contains(scopes, target) } diff --git a/internal/broadcaster/broadcaster.go b/internal/broadcaster/broadcaster.go deleted file mode 100644 index f38bc208..00000000 --- a/internal/broadcaster/broadcaster.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2024-2026, s0up and the autobrr contributors. -// SPDX-License-Identifier: GPL-2.0-or-later - -package broadcaster - -import "github.com/autobrr/netronome/internal/types" - -type Broadcaster interface { - BroadcastUpdate(types.SpeedUpdate) -} diff --git a/internal/config/config.go b/internal/config/config.go index bc2c9136..d13763b0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ package config import ( + "crypto/rand" "fmt" "io" "os" @@ -14,8 +15,6 @@ import ( "github.com/BurntSushi/toml" "github.com/rs/zerolog/log" - - "github.com/autobrr/netronome/internal/utils" ) const ( @@ -113,8 +112,6 @@ type PingConfig struct { type PaginationConfig struct { DefaultPage int `toml:"default_page" env:"DEFAULT_PAGE"` - DefaultPageSize int `toml:"default_page_size" env:"DEFAULT_PAGE_SIZE"` - MaxPageSize int `toml:"max_page_size" env:"MAX_PAGE_SIZE"` DefaultTimeRange string `toml:"default_time_range" env:"DEFAULT_TIME_RANGE"` DefaultLimit int `toml:"default_limit" env:"DEFAULT_LIMIT"` } @@ -129,13 +126,10 @@ type GeoIPConfig struct { } type PacketLossConfig struct { - Enabled bool `toml:"enabled" env:"PACKETLOSS_ENABLED"` - DefaultInterval int `toml:"default_interval" env:"PACKETLOSS_DEFAULT_INTERVAL"` - DefaultPacketCount int `toml:"default_packet_count" env:"PACKETLOSS_DEFAULT_PACKET_COUNT"` - MaxConcurrentMonitors int `toml:"max_concurrent_monitors" env:"PACKETLOSS_MAX_CONCURRENT_MONITORS"` - PrivilegedMode bool `toml:"privileged_mode" env:"PACKETLOSS_PRIVILEGED_MODE"` - MTREnableDNS bool `toml:"mtr_enable_dns" env:"PACKETLOSS_MTR_ENABLE_DNS"` - RestoreMonitorsOnStartup bool `toml:"restore_monitors_on_startup" env:"PACKETLOSS_RESTORE_MONITORS_ON_STARTUP"` + Enabled bool `toml:"enabled" env:"PACKETLOSS_ENABLED"` + MaxConcurrentMonitors int `toml:"max_concurrent_monitors" env:"PACKETLOSS_MAX_CONCURRENT_MONITORS"` + PrivilegedMode bool `toml:"privileged_mode" env:"PACKETLOSS_PRIVILEGED_MODE"` + MTREnableDNS bool `toml:"mtr_enable_dns" env:"PACKETLOSS_MTR_ENABLE_DNS"` } type AgentConfig struct { @@ -149,8 +143,7 @@ type AgentConfig struct { } type MonitorConfig struct { - Enabled bool `toml:"enabled" env:"MONITOR_ENABLED"` - ReconnectInterval string `toml:"reconnect_interval" env:"MONITOR_RECONNECT_INTERVAL"` + Enabled bool `toml:"enabled" env:"MONITOR_ENABLED"` } type TailscaleConfig struct { @@ -182,9 +175,8 @@ type TailscaleConfig struct { // Deprecated - kept for backward compatibility type TailscaleAgentConfig struct { - Enabled bool `toml:"enabled" env:"TAILSCALE_AGENT_ENABLED"` - AcceptRoutes bool `toml:"accept_routes" env:"TAILSCALE_AGENT_ACCEPT_ROUTES"` - Port int `toml:"port" env:"TAILSCALE_AGENT_PORT"` + Enabled bool `toml:"enabled" env:"TAILSCALE_AGENT_ENABLED"` + Port int `toml:"port" env:"TAILSCALE_AGENT_PORT"` } // Deprecated - kept for backward compatibility @@ -264,8 +256,6 @@ func New() *Config { }, Pagination: PaginationConfig{ DefaultPage: 1, - DefaultPageSize: 20, - MaxPageSize: 100, DefaultTimeRange: "1w", DefaultLimit: 20, }, @@ -277,13 +267,10 @@ func New() *Config { ASNDatabasePath: "", }, PacketLoss: PacketLossConfig{ - Enabled: true, - DefaultInterval: 3600, - DefaultPacketCount: 10, - MaxConcurrentMonitors: 10, - PrivilegedMode: true, - MTREnableDNS: false, - RestoreMonitorsOnStartup: false, + Enabled: true, + MaxConcurrentMonitors: 10, + PrivilegedMode: true, + MTREnableDNS: false, }, Agent: AgentConfig{ Host: "0.0.0.0", @@ -293,8 +280,7 @@ func New() *Config { DiskExcludes: []string{}, }, Monitor: MonitorConfig{ - Enabled: true, - ReconnectInterval: "30s", + Enabled: true, }, Tailscale: TailscaleConfig{ Enabled: false, @@ -311,9 +297,8 @@ func New() *Config { DiscoveryPrefix: "", // Deprecated fields - kept for compatibility during migration Agent: TailscaleAgentConfig{ - Enabled: false, - AcceptRoutes: true, - Port: 8200, + Enabled: false, + Port: 8200, }, Monitor: TailscaleMonitorConfig{ AutoDiscover: true, @@ -569,16 +554,6 @@ func (c *Config) loadPaginationFromEnv() { c.Pagination.DefaultPage = page } } - if v := getEnv("DEFAULT_PAGE_SIZE"); v != "" { - if size, err := strconv.Atoi(v); err == nil { - c.Pagination.DefaultPageSize = size - } - } - if v := getEnv("MAX_PAGE_SIZE"); v != "" { - if size, err := strconv.Atoi(v); err == nil { - c.Pagination.MaxPageSize = size - } - } if v := getEnv("DEFAULT_TIME_RANGE"); v != "" { c.Pagination.DefaultTimeRange = v } @@ -610,16 +585,6 @@ func (c *Config) loadPacketLossFromEnv() { c.PacketLoss.Enabled = enabled } } - if v := getEnv("PACKETLOSS_DEFAULT_INTERVAL"); v != "" { - if interval, err := strconv.Atoi(v); err == nil { - c.PacketLoss.DefaultInterval = interval - } - } - if v := getEnv("PACKETLOSS_DEFAULT_PACKET_COUNT"); v != "" { - if count, err := strconv.Atoi(v); err == nil { - c.PacketLoss.DefaultPacketCount = count - } - } if v := getEnv("PACKETLOSS_MAX_CONCURRENT_MONITORS"); v != "" { if max, err := strconv.Atoi(v); err == nil { c.PacketLoss.MaxConcurrentMonitors = max @@ -635,11 +600,6 @@ func (c *Config) loadPacketLossFromEnv() { c.PacketLoss.MTREnableDNS = enableDNS } } - if v := getEnv("PACKETLOSS_RESTORE_MONITORS_ON_STARTUP"); v != "" { - if restore, err := strconv.ParseBool(v); err == nil { - c.PacketLoss.RestoreMonitorsOnStartup = restore - } - } } func (c *Config) loadAgentFromEnv() { @@ -682,9 +642,6 @@ func (c *Config) loadMonitorFromEnv() { c.Monitor.Enabled = enabled } } - if v := getEnv("MONITOR_RECONNECT_INTERVAL"); v != "" { - c.Monitor.ReconnectInterval = v - } } func (c *Config) loadTailscaleFromEnv() { @@ -695,11 +652,10 @@ func (c *Config) WriteToml(w io.Writer) error { cfg := New() cfg.Database.Path = "netronome.db" - secret, err := utils.GenerateSecureToken(32) - if err != nil { - return fmt.Errorf("failed to generate session secret: %w", err) - } - cfg.Session.Secret = secret + // Two rand.Text() calls: this is a long-lived signing key, so keep the + // ~256 bits the old 32-byte generator gave. One call (~130 bits) is + // plenty for the per-request tokens elsewhere, but not worth shaving here. + cfg.Session.Secret = rand.Text() + rand.Text() if isRunningInContainer() { cfg.Server.Host = "0.0.0.0" @@ -934,12 +890,6 @@ func (c *Config) WriteToml(w io.Writer) error { if _, err := fmt.Fprintf(w, "enabled = %v\n", cfg.PacketLoss.Enabled); err != nil { return err } - if _, err := fmt.Fprintf(w, "default_interval = %d # seconds between tests\n", cfg.PacketLoss.DefaultInterval); err != nil { - return err - } - if _, err := fmt.Fprintf(w, "default_packet_count = %d # packets per test\n", cfg.PacketLoss.DefaultPacketCount); err != nil { - return err - } if _, err := fmt.Fprintf(w, "max_concurrent_monitors = %d\n", cfg.PacketLoss.MaxConcurrentMonitors); err != nil { return err } @@ -960,9 +910,6 @@ func (c *Config) WriteToml(w io.Writer) error { if _, err := fmt.Fprintf(w, "enabled = %v\n", cfg.Monitor.Enabled); err != nil { return err } - if _, err := fmt.Fprintf(w, "reconnect_interval = \"%s\"\n", cfg.Monitor.ReconnectInterval); err != nil { - return err - } // Tailscale section if _, err := fmt.Fprintln(w, ""); err != nil { @@ -1029,17 +976,6 @@ func (c *Config) WriteToml(w io.Writer) error { return nil } -func GetDefaultConfigPath() string { - if configDir, err := os.UserConfigDir(); err == nil { - configPath := filepath.Join(configDir, AppName, "config.toml") - if _, err := os.Stat(configPath); err == nil { - return configPath - } - } - - return "config.toml" -} - func DefaultConfigPaths() []string { var paths []string @@ -1328,11 +1264,6 @@ func (t *TailscaleConfig) loadFromEnv() { t.Agent.Enabled = enabled } } - if v := getEnv("TAILSCALE_AGENT_ACCEPT_ROUTES"); v != "" { - if accept, err := strconv.ParseBool(v); err == nil { - t.Agent.AcceptRoutes = accept - } - } if v := getEnv("TAILSCALE_AGENT_PORT"); v != "" { if port, err := strconv.Atoi(v); err == nil { t.Agent.Port = port diff --git a/internal/database/database.go b/internal/database/database.go index 71fb5fae..7150cb01 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -148,34 +148,6 @@ func (s *service) insert(ctx context.Context, table string, data map[string]inte return query.RunWith(s.db).ExecContext(ctx) } -func (s *service) update(ctx context.Context, table string, data map[string]interface{}, where sq.Eq) (sql.Result, error) { - query := s.sqlBuilder.Update(table) - - for col, val := range data { - query = query.Set(col, val) - } - - query = query.Where(where) - return query.RunWith(s.db).ExecContext(ctx) -} - -func (s *service) delete(ctx context.Context, table string, where sq.Eq) (sql.Result, error) { - query := s.sqlBuilder. - Delete(table). - Where(where) - - return query.RunWith(s.db).ExecContext(ctx) -} - -func (s *service) select_(ctx context.Context, table string, columns []string, where sq.Eq) (*sql.Rows, error) { - query := s.sqlBuilder. - Select(columns...). - From(table). - Where(where) - - return query.RunWith(s.db).QueryContext(ctx) -} - func (s *service) count(ctx context.Context, table string, where sq.Eq) (int, error) { query := s.sqlBuilder. Select("COUNT(*)"). @@ -328,21 +300,6 @@ func (s *service) Close() error { return s.db.Close() } -func getMigrationVersion(fileName string) int { - parts := strings.Split(fileName, "/") - if len(parts) > 0 { - fileName = parts[len(parts)-1] - } - - parts = strings.Split(fileName, "_") - if len(parts) > 0 { - if v, err := strconv.Atoi(parts[0]); err == nil { - return v - } - } - return 0 -} - func (s *service) InitializeTables(ctx context.Context) error { // Detect if we're in a test environment to reduce logging verbosity isTest := strings.Contains(os.Args[0], ".test") || strings.HasSuffix(os.Args[0], "/test") @@ -357,11 +314,7 @@ func (s *service) InitializeTables(ctx context.Context) error { return fmt.Errorf("failed to get migration files: %w", err) } - // log.Trace().Interface("migration_files", migrationFiles).Msg("Found migration files") - for _, fileName := range migrationFiles { - // version := getMigrationVersion(fileName) - // log.Trace().Str("file", fileName).Int("version", version).Msg("Adding migration") m.Add(&migrator.Migration{ Name: fileName, File: fileName, diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 41bb2ac3..146f45e0 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -30,7 +30,6 @@ // Avoid duplicating these tests in new files. // // Performance Tips: -// - Use RunTestWithSQLiteOnly() for simple tests that don't need PostgreSQL // - Set SKIP_POSTGRES_TESTS=1 to skip PostgreSQL tests during local development // - PostgreSQL tests take ~6 seconds each due to embedded database initialization @@ -356,13 +355,6 @@ func RunTestWithBothDatabases(t *testing.T, testFunc func(t *testing.T, td *Test }) } -// RunTestWithSQLiteOnly runs a test function against SQLite only (for faster development) -func RunTestWithSQLiteOnly(t *testing.T, testFunc func(t *testing.T, td *TestDatabase)) { - td := SetupTestDatabase(t, config.SQLite) - defer td.Close() - testFunc(t, td) -} - // AssertRecordExists checks if a record exists in the database func AssertRecordExists(t *testing.T, td *TestDatabase, table string, column string, value any) { t.Helper() @@ -399,17 +391,6 @@ func AssertRecordNotExists(t *testing.T, td *TestDatabase, table string, column require.Equal(t, 0, count, "Expected no records in %s where %s = %v", table, column, value) } -// CreateTestUser creates a test user in the database -func CreateTestUser(t *testing.T, td *TestDatabase, username, password string) *User { - t.Helper() - - ctx := context.Background() - user, err := td.Service.CreateUser(ctx, username, password) - require.NoError(t, err) - require.NotNil(t, user) - return user -} - // CreateTestPacketLossMonitor creates a test packet loss monitor func CreateTestPacketLossMonitor(t *testing.T, td *TestDatabase) *types.PacketLossMonitor { t.Helper() diff --git a/internal/database/migrations/migrations.go b/internal/database/migrations/migrations.go index 960e707d..0158b57a 100644 --- a/internal/database/migrations/migrations.go +++ b/internal/database/migrations/migrations.go @@ -8,6 +8,7 @@ import ( "fmt" "io/fs" "os" + "strconv" "strings" "github.com/rs/zerolog/log" @@ -98,35 +99,9 @@ func getMigrationVersion(fileName string) int { parts := strings.Split(fileName, "_") if len(parts) > 0 { version := strings.TrimPrefix(parts[0], "0") - if v, err := parseInt(version); err == nil { + if v, err := strconv.Atoi(version); err == nil { return v } } return 0 } - -func parseInt(s string) (int, error) { - var result int - for _, ch := range s { - if ch < '0' || ch > '9' { - return 0, fmt.Errorf("invalid integer: %s", s) - } - result = result*10 + int(ch-'0') - } - return result, nil -} - -func ReadMigration(fileName string) ([]byte, error) { - content, err := fs.ReadFile(SchemaMigrations, fileName) - if err != nil { - log.Error().Err(err).Str("file", fileName).Msg("Failed to read migration file") - return nil, err - } - - log.Debug(). - Str("file", fileName). - Int("contentLength", len(content)). - Msg("Successfully read migration content") - - return content, nil -} diff --git a/internal/database/user.go b/internal/database/user.go index 26ce022b..4c395bd5 100644 --- a/internal/database/user.go +++ b/internal/database/user.go @@ -26,13 +26,6 @@ type User struct { PasswordHash string `json:"-"` } -type UserService interface { - CreateUser(ctx context.Context, username, password string) (*User, error) - GetUserByUsername(ctx context.Context, username string) (*User, error) - ValidatePassword(user *User, password string) bool - UpdatePassword(ctx context.Context, username, newPassword string) error -} - func (s *service) CreateUser(ctx context.Context, username, password string) (*User, error) { if username == "" || password == "" { return nil, fmt.Errorf("%w: username and password required", ErrInvalidInput) @@ -104,8 +97,8 @@ func (s *service) CreateUser(ctx context.Context, username, password string) (*U INSERT INTO registration_status (is_registration_enabled) VALUES (false);` } else { disableRegQuery = ` - INSERT INTO registration_status (is_registration_enabled) - VALUES (0) + INSERT INTO registration_status (is_registration_enabled) + VALUES (0) ON CONFLICT (rowid) DO UPDATE SET is_registration_enabled = 0` } diff --git a/internal/monitor/tailscale_discovery.go b/internal/monitor/tailscale_discovery.go index 513f8bab..2875535a 100644 --- a/internal/monitor/tailscale_discovery.go +++ b/internal/monitor/tailscale_discovery.go @@ -26,7 +26,7 @@ import ( type TailscaleDiscovery struct { config *config.TailscaleConfig tsnetServer *tsnet.Server - tailscaleClient tailscale.Client + tailscaleClient *tailscale.Client service *Service discoveryTicker *time.Ticker mode tailscale.Mode diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index 1e1cae95..6af17a30 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -579,33 +579,6 @@ func (n *Notifier) formatHighPingMessage(result *SpeedTestResult, threshold *flo return sb.String() } -// MigrateDiscordWebhook converts an old Discord webhook URL to Shoutrrr format -func MigrateDiscordWebhook(webhookURL string) string { - if webhookURL == "" { - return "" - } - - // Already in Shoutrrr format - if strings.HasPrefix(webhookURL, "discord://") { - return webhookURL - } - - // Parse Discord webhook URL - // Format: https://discord.com/api/webhooks/{id}/{token} - if strings.Contains(webhookURL, "discord.com/api/webhooks/") || - strings.Contains(webhookURL, "discordapp.com/api/webhooks/") { - parts := strings.Split(webhookURL, "/") - if len(parts) >= 2 { - token := parts[len(parts)-1] - id := parts[len(parts)-2] - return fmt.Sprintf("discord://%s@%s", token, id) - } - } - - // Return as-is if we can't parse it - return webhookURL -} - // SpeedTestResult represents the result of a speed test type SpeedTestResult struct { ServerName string @@ -617,18 +590,3 @@ type SpeedTestResult struct { ISP string Failed bool } - -// PacketLossNotification represents packet loss monitoring data for notifications -type PacketLossNotification struct { - MonitorName string - Host string - PacketLoss float64 - Threshold float64 - AvgRTT float64 // in milliseconds - MinRTT float64 // in milliseconds - MaxRTT float64 // in milliseconds - PacketsSent int - PacketsRecv int - UsedMTR bool - HopCount int -} diff --git a/internal/server/auth.go b/internal/server/auth.go index 7e7c317d..559d1ba5 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -4,6 +4,7 @@ package server import ( + "crypto/rand" "errors" "fmt" "net" @@ -17,7 +18,6 @@ import ( "github.com/autobrr/netronome/internal/auth" "github.com/autobrr/netronome/internal/database" - "github.com/autobrr/netronome/internal/utils" ) type AuthHandler struct { @@ -344,12 +344,7 @@ func (h *AuthHandler) Register(c *gin.Context) { return } - sessionToken, err := utils.GenerateSecureToken(32) - if err != nil { - log.Error().Err(err).Msg("Failed to generate session token") - _ = c.Error(fmt.Errorf("failed to generate session token: %w", err)) - return - } + sessionToken := rand.Text() h.refreshSession(c, sessionToken, nil) @@ -396,14 +391,7 @@ func (h *AuthHandler) Login(c *gin.Context) { return } - sessionToken, err := utils.GenerateSecureToken(32) - if err != nil { - log.Error().Err(err).Msg("Failed to generate session token") - c.JSON(http.StatusInternalServerError, gin.H{ - "error": "Failed to generate session token", - }) - return - } + sessionToken := rand.Text() h.refreshSession(c, sessionToken, nil) diff --git a/internal/server/auth_oidc.go b/internal/server/auth_oidc.go index e60f44d7..bc182b31 100644 --- a/internal/server/auth_oidc.go +++ b/internal/server/auth_oidc.go @@ -4,6 +4,7 @@ package server import ( + "crypto/rand" "net/http" "net/url" "strings" @@ -12,7 +13,6 @@ import ( "github.com/rs/zerolog/log" "github.com/autobrr/netronome/internal/auth" - "github.com/autobrr/netronome/internal/utils" ) func loginErrorRedirectURL(baseURL, errorCode string) string { @@ -33,12 +33,7 @@ func (h *AuthHandler) handleOIDCLogin(c *gin.Context) { } // Generate state parameter - state, err := utils.GenerateSecureToken(32) - if err != nil { - log.Error().Err(err).Msg("Failed to generate state parameter") - c.Redirect(http.StatusTemporaryRedirect, loginErrorRedirectURL(baseURL, "state_generation_failed")) - return - } + state := rand.Text() // Generate PKCE parameters pkceParams, err := auth.GeneratePKCEParams() diff --git a/internal/server/server.go b/internal/server/server.go index 09dc91de..00ce06df 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -14,7 +14,6 @@ import ( "github.com/rs/zerolog/log" "github.com/autobrr/netronome/internal/auth" - "github.com/autobrr/netronome/internal/broadcaster" "github.com/autobrr/netronome/internal/config" "github.com/autobrr/netronome/internal/database" "github.com/autobrr/netronome/internal/handlers" @@ -28,8 +27,6 @@ import ( "github.com/autobrr/netronome/web" ) -var _ broadcaster.Broadcaster = &Server{} - type Server struct { Router *gin.Engine speedtest speedtest.Service @@ -167,12 +164,6 @@ func (s *Server) BroadcastMonitorUpdate(update types.MonitorUpdate) { Msg("Broadcasting monitor update") } -func (s *Server) SetPacketLossService(service *speedtest.PacketLossService) { - s.mu.Lock() - s.packetLossService = service - s.mu.Unlock() -} - func (s *Server) SetMonitorService(service *monitor.Service) { s.mu.Lock() s.monitorService = service diff --git a/internal/speedtest/iperf.go b/internal/speedtest/iperf.go index 0e969476..24a8b3c5 100644 --- a/internal/speedtest/iperf.go +++ b/internal/speedtest/iperf.go @@ -24,26 +24,6 @@ import ( "github.com/autobrr/netronome/internal/types" ) -// IperfResult represents the parsed output from iperf3 -type IperfResult struct { - Start struct { - Connected []struct { - RemoteHost string `json:"remote_host"` - } `json:"connected"` - } `json:"start"` - End struct { - SumSent struct { - BitsPerSecond float64 `json:"bits_per_second"` - JitterMs float64 `json:"jitter_ms"` - } `json:"sum_sent"` - SumReceived struct { - BitsPerSecond float64 `json:"bits_per_second"` - JitterMs float64 `json:"jitter_ms"` - } `json:"sum_received"` - } `json:"end"` - Error string `json:"error,omitempty"` -} - type iperfEndData struct { SumSent struct { BitsPerSecond float64 `json:"bits_per_second"` diff --git a/internal/speedtest/progress_broadcaster.go b/internal/speedtest/progress_broadcaster.go deleted file mode 100644 index 6fc79e29..00000000 --- a/internal/speedtest/progress_broadcaster.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2024-2026, s0up and the autobrr contributors. -// SPDX-License-Identifier: GPL-2.0-or-later - -package speedtest - -import ( - "github.com/autobrr/netronome/internal/broadcaster" - "github.com/autobrr/netronome/internal/types" -) - -type DefaultProgressBroadcaster struct { - broadcaster broadcaster.Broadcaster -} - -func NewProgressBroadcaster(broadcaster broadcaster.Broadcaster) *DefaultProgressBroadcaster { - return &DefaultProgressBroadcaster{ - broadcaster: broadcaster, - } -} - -func (p *DefaultProgressBroadcaster) BroadcastUpdate(update types.SpeedUpdate) { - if p.broadcaster != nil { - p.broadcaster.BroadcastUpdate(update) - } -} - diff --git a/internal/speedtest/traceroute.go b/internal/speedtest/traceroute.go index c51b176e..03da74b9 100644 --- a/internal/speedtest/traceroute.go +++ b/internal/speedtest/traceroute.go @@ -379,245 +379,6 @@ func (s *service) buildTracerouteArgs(host string) []string { return args } -// parseTracerouteOutput parses traceroute command output based on the operating system -func (s *service) parseTracerouteOutput(output, originalHost string) (*TracerouteResult, error) { - result := &TracerouteResult{ - Destination: originalHost, // Use the original host/URL for display - Hops: []TracerouteHop{}, - } - - lines := strings.Split(output, "\n") - - switch runtime.GOOS { - case "darwin", "linux": - return s.parseUnixTracerouteOutput(lines, result) - case "windows": - return s.parseWindowsTracerouteOutput(lines, result) - default: - return s.parseUnixTracerouteOutput(lines, result) - } -} - -// parseUnixTracerouteOutput parses Unix/Linux/macOS traceroute output -func (s *service) parseUnixTracerouteOutput(lines []string, result *TracerouteResult) (*TracerouteResult, error) { - // Unix traceroute format: - // traceroute to google.com (172.217.14.110), 30 hops max, 60 byte packets - // 1 192.168.1.1 0.123 ms 0.456 ms 0.789 ms - // 2 10.0.0.1 5.123 ms 5.456 ms 5.789 ms - // 3 * * * - // 4 172.217.14.110 15.123 ms 15.456 ms 15.789 ms - - // Extract destination IP from first line - if len(lines) > 0 { - firstLine := lines[0] - if strings.Contains(firstLine, "traceroute to") { - // Extract IP from parentheses - ipRegex := regexp.MustCompile(`\(([^)]+)\)`) - if match := ipRegex.FindStringSubmatch(firstLine); match != nil { - result.IP = match[1] - } - } - } - - // Regex patterns for parsing hop lines - // Updated to handle both hostname and IP, or just IP - // Updated regex patterns for single query per hop - hopRegex := regexp.MustCompile(`^\s*(\d+)\s+([^\s]+)\s+\(([^)]+)\)\s+([\d.]+)\s+ms`) - hopRegexIPOnly := regexp.MustCompile(`^\s*(\d+)\s+([^\s]+)\s+([0-9A-Fa-f:.]+)\s+ms`) - // Also support the old 3-query format for backward compatibility - hopRegex3 := regexp.MustCompile(`^\s*(\d+)\s+([^\s]+)\s+\(([^)]+)\)\s+([\d.]+)\s+ms\s+([\d.]+)\s+ms\s+([\d.]+)\s+ms`) - hopRegexIPOnly3 := regexp.MustCompile(`^\s*(\d+)\s+([^\s]+)\s+([0-9A-Fa-f:.]+)\s+ms\s+([\d.]+)\s+ms\s+([\d.]+)\s+ms`) - timeoutRegex := regexp.MustCompile(`^\s*(\d+)\s+\*\s+\*\s+\*`) - - for _, line := range lines { - line = strings.TrimSpace(line) - if line == "" || strings.HasPrefix(line, "traceroute to") { - continue - } - - // Try to match timeout line first - if match := timeoutRegex.FindStringSubmatch(line); match != nil { - hopNum, _ := strconv.Atoi(match[1]) - hop := TracerouteHop{ - Number: hopNum, - Host: "*", - IP: "*", - Timeout: true, - CountryCode: "", - AS: "", - } - result.Hops = append(result.Hops, hop) - continue - } - - // Try to match hop line with hostname and IP first (3-query format) - if match := hopRegex3.FindStringSubmatch(line); match != nil { - hopNum, _ := strconv.Atoi(match[1]) - hostname := match[2] - ip := match[3] - rtt1, _ := strconv.ParseFloat(match[4], 64) - rtt2, _ := strconv.ParseFloat(match[5], 64) - rtt3, _ := strconv.ParseFloat(match[6], 64) - - hop := TracerouteHop{ - Number: hopNum, - Host: hostname, - IP: ip, - RTT1: rtt1, - RTT2: rtt2, - RTT3: rtt3, - Timeout: false, - CountryCode: getCountryFromHost(ip), - AS: getASNFromHost(ip), - } - result.Hops = append(result.Hops, hop) - } else if match := hopRegexIPOnly3.FindStringSubmatch(line); match != nil { - // Try to match hop line with IP only (3-query format) - hopNum, _ := strconv.Atoi(match[1]) - ip := match[2] - rtt1, _ := strconv.ParseFloat(match[3], 64) - rtt2, _ := strconv.ParseFloat(match[4], 64) - rtt3, _ := strconv.ParseFloat(match[5], 64) - - hop := TracerouteHop{ - Number: hopNum, - Host: ip, - IP: ip, - RTT1: rtt1, - RTT2: rtt2, - RTT3: rtt3, - Timeout: false, - CountryCode: getCountryFromHost(ip), - AS: getASNFromHost(ip), - } - result.Hops = append(result.Hops, hop) - } else if match := hopRegex.FindStringSubmatch(line); match != nil { - // Try to match hop line with hostname and IP (single query format) - hopNum, _ := strconv.Atoi(match[1]) - hostname := match[2] - ip := match[3] - rtt1, _ := strconv.ParseFloat(match[4], 64) - - hop := TracerouteHop{ - Number: hopNum, - Host: hostname, - IP: ip, - RTT1: rtt1, - RTT2: 0, // No second query - RTT3: 0, // No third query - Timeout: false, - CountryCode: getCountryFromHost(ip), - AS: getASNFromHost(ip), - } - result.Hops = append(result.Hops, hop) - } else if match := hopRegexIPOnly.FindStringSubmatch(line); match != nil { - // Try to match hop line with IP only (single query format) - hopNum, _ := strconv.Atoi(match[1]) - ip := match[2] - rtt1, _ := strconv.ParseFloat(match[3], 64) - - hop := TracerouteHop{ - Number: hopNum, - Host: ip, - IP: ip, - RTT1: rtt1, - RTT2: 0, // No second query - RTT3: 0, // No third query - Timeout: false, - CountryCode: getCountryFromHost(ip), - AS: getASNFromHost(ip), - } - result.Hops = append(result.Hops, hop) - } - } - - result.TotalHops = len(result.Hops) - result.Complete = result.TotalHops > 0 - - return result, nil -} - -// parseWindowsTracerouteOutput parses Windows tracert output -func (s *service) parseWindowsTracerouteOutput(lines []string, result *TracerouteResult) (*TracerouteResult, error) { - // Windows tracert format: - // Tracing route to google.com [172.217.14.110] - // over a maximum of 30 hops: - // - // 1 <1 ms <1 ms <1 ms 192.168.1.1 - // 2 5 ms 5 ms 5 ms 10.0.0.1 - // 3 * * * Request timed out. - // 4 15 ms 15 ms 15 ms 172.217.14.110 - - // Extract destination IP from first line - if len(lines) > 0 { - firstLine := lines[0] - if strings.Contains(firstLine, "Tracing route to") { - // Extract IP from brackets - ipRegex := regexp.MustCompile(`\[([^\]]+)\]`) - if match := ipRegex.FindStringSubmatch(firstLine); match != nil { - result.IP = match[1] - } - } - } - - // Regex patterns for parsing hop lines - hopRegex := regexp.MustCompile(`^\s*(\d+)\s+( 0 - - return result, nil -} - // parseTracerouteOutputStreaming parses traceroute output line by line and broadcasts updates func (s *service) parseTracerouteOutputStreaming(stdout io.ReadCloser, originalHost, host, destinationIP string, cmd *exec.Cmd) (*TracerouteResult, error) { result := &TracerouteResult{ diff --git a/internal/speedtest/traceroute_test.go b/internal/speedtest/traceroute_test.go index ea296774..3c69fee6 100644 --- a/internal/speedtest/traceroute_test.go +++ b/internal/speedtest/traceroute_test.go @@ -54,19 +54,3 @@ func TestParseHopLineIPv6Address(t *testing.T) { assert.Equal(t, 4.890, hop.RTT3) assert.False(t, hop.Timeout) } - -func TestParseUnixTracerouteOutputIPv6Address(t *testing.T) { - s := &service{} - lines := []string{ - "traceroute to ipv6.google.com (2a00:1450:400f:802::200e), 30 hops max, 60 byte packets", - " 1 2001:db8::1 1.100 ms 1.200 ms 1.300 ms", - } - - result, err := s.parseUnixTracerouteOutput(lines, &TracerouteResult{ - Destination: "ipv6.google.com", - Hops: []TracerouteHop{}, - }) - require.NoError(t, err) - require.Len(t, result.Hops, 1) - assert.Equal(t, "2001:db8::1", result.Hops[0].IP) -} diff --git a/internal/speedtest/types.go b/internal/speedtest/types.go index 7533fa18..415e6089 100644 --- a/internal/speedtest/types.go +++ b/internal/speedtest/types.go @@ -54,28 +54,8 @@ type SpeedUpdate struct { IsScheduled bool `json:"isScheduled"` } -// TestRunner interface for different speed test implementations -type TestRunner interface { - // RunTest executes a speed test and returns the result - RunTest(ctx context.Context, opts *types.TestOptions) (*Result, error) - - // GetServers returns available servers for this test type - GetServers() ([]ServerResponse, error) - - // GetTestType returns the test type identifier - GetTestType() string - - // SetProgressCallback sets the callback for progress updates - SetProgressCallback(callback func(types.SpeedUpdate)) -} - // ResultHandler handles database saves and notifications type ResultHandler interface { SaveResult(ctx context.Context, result *Result, testType string, opts *types.TestOptions) error SendNotification(result *types.SpeedTestResult) } - -// ProgressBroadcaster handles real-time progress updates -type ProgressBroadcaster interface { - BroadcastUpdate(update types.SpeedUpdate) -} diff --git a/internal/tailscale/tailscale.go b/internal/tailscale/tailscale.go index 55848d78..c51e35f7 100644 --- a/internal/tailscale/tailscale.go +++ b/internal/tailscale/tailscale.go @@ -10,15 +10,14 @@ import ( "strings" "time" - "tailscale.com/client/tailscale" - "tailscale.com/ipn/ipnstate" + "tailscale.com/client/local" "tailscale.com/tsnet" ) -// Client provides a unified interface for both tsnet and host tailscaled -type Client interface { - Status(ctx context.Context) (*ipnstate.Status, error) -} +// Client talks to a tailscaled LocalAPI, whether that is the host's daemon or +// an embedded tsnet server. Both hand back the same type, so there is nothing +// to abstract over. +type Client = local.Client // Mode represents how we're connecting to Tailscale type Mode string @@ -28,76 +27,54 @@ const ( ModeTsnet Mode = "tsnet" // Using embedded tsnet ) -// hostClient wraps the system tailscaled client -type hostClient struct { - client *tailscale.LocalClient -} - -func (h *hostClient) Status(ctx context.Context) (*ipnstate.Status, error) { - return h.client.Status(ctx) -} - -// tsnetClient wraps a tsnet server's local client -type tsnetClient struct { - client *tailscale.LocalClient -} - -func (t *tsnetClient) Status(ctx context.Context) (*ipnstate.Status, error) { - return t.client.Status(ctx) -} - // GetHostClient attempts to connect to the host's tailscaled -func GetHostClient() (Client, error) { +func GetHostClient() (*Client, error) { // Try default client first (it will auto-detect socket/HTTP) - client := &tailscale.LocalClient{} - + client := &Client{} + // Test the connection ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - + if _, err := client.Status(ctx); err == nil { - return &hostClient{client: client}, nil + return client, nil } - + return nil, fmt.Errorf("no running tailscaled found on host") } // GetTsnetClient creates a client from a tsnet server -func GetTsnetClient(server *tsnet.Server) (Client, error) { - localClient, err := server.LocalClient() - if err != nil { - return nil, err - } - return &tsnetClient{client: localClient}, nil +func GetTsnetClient(server *tsnet.Server) (*Client, error) { + return server.LocalClient() } // ListenOnTailscale listens on the Tailscale network if available -func ListenOnTailscale(hostClient Client, port int) (net.Listener, error) { +func ListenOnTailscale(hostClient *Client, port int) (net.Listener, error) { status, err := hostClient.Status(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get Tailscale status: %w", err) } - + if status.Self == nil || len(status.Self.TailscaleIPs) == 0 { return nil, fmt.Errorf("no Tailscale IPs available") } - + // Listen on the first Tailscale IP addr := fmt.Sprintf("%s:%d", status.Self.TailscaleIPs[0], port) return net.Listen("tcp", addr) } // GetSelfInfo returns information about the current Tailscale node -func GetSelfInfo(client Client) (hostname string, ips []string, err error) { +func GetSelfInfo(client *Client) (hostname string, ips []string, err error) { status, err := client.Status(context.Background()) if err != nil { return "", nil, err } - + if status.Self == nil { return "", nil, fmt.Errorf("no self information available") } - + // Use the actual Tailscale machine name (DNSName without suffix) hostname = status.Self.DNSName // Trim the MagicDNS suffix to get just the machine name @@ -108,10 +85,10 @@ func GetSelfInfo(client Client) (hostname string, ips []string, err error) { if hostname == "" { hostname = status.Self.HostName } - + for _, ip := range status.Self.TailscaleIPs { ips = append(ips, ip.String()) } - + return hostname, ips, nil -} \ No newline at end of file +} diff --git a/internal/types/types.go b/internal/types/types.go index 12e53f7e..2df1b852 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -205,19 +205,6 @@ type MonitorAgent struct { UpdatedAt time.Time `db:"updated_at" json:"updatedAt"` } -// MonitorBandwidth represents bandwidth data from monitoring agent -type MonitorBandwidth struct { - ID int64 `db:"id" json:"id"` - AgentID int64 `db:"agent_id" json:"agentId"` - RxBytesPerSecond *int64 `db:"rx_bytes_per_second" json:"rxBytesPerSecond"` - TxBytesPerSecond *int64 `db:"tx_bytes_per_second" json:"txBytesPerSecond"` - RxPacketsPerSecond *int `db:"rx_packets_per_second" json:"rxPacketsPerSecond"` - TxPacketsPerSecond *int `db:"tx_packets_per_second" json:"txPacketsPerSecond"` - RxRateString *string `db:"rx_rate_string" json:"rxRateString"` - TxRateString *string `db:"tx_rate_string" json:"txRateString"` - CreatedAt time.Time `db:"created_at" json:"createdAt"` -} - // MonitorLiveData represents live data from monitoring agent type MonitorLiveData struct { Index int `json:"index"` @@ -242,92 +229,6 @@ type MonitorLiveData struct { } `json:"tx"` } -// MonitorFullData represents the complete bandwidth monitor JSON export structure -type MonitorFullData struct { - Vnstatversion string `json:"vnstatversion"` - Jsonversion string `json:"jsonversion"` - Interfaces []struct { - Name string `json:"name"` - Alias string `json:"alias"` - Created struct { - Date struct { - Year int `json:"year"` - Month int `json:"month"` - Day int `json:"day"` - } `json:"date"` - } `json:"created"` - Updated struct { - Date struct { - Year int `json:"year"` - Month int `json:"month"` - Day int `json:"day"` - } `json:"date"` - Time struct { - Hour int `json:"hour"` - Minute int `json:"minute"` - } `json:"time"` - } `json:"updated"` - Traffic struct { - Total struct { - Rx int64 `json:"rx"` - Tx int64 `json:"tx"` - } `json:"total"` - Fiveminute []struct { - ID int `json:"id"` - Date struct { - Year int `json:"year"` - Month int `json:"month"` - Day int `json:"day"` - } `json:"date"` - Time struct { - Hour int `json:"hour"` - Minute int `json:"minute"` - } `json:"time"` - Rx int64 `json:"rx"` - Tx int64 `json:"tx"` - } `json:"fiveminute"` - Hour []struct { - ID int `json:"id"` - Date struct { - Year int `json:"year"` - Month int `json:"month"` - Day int `json:"day"` - } `json:"date"` - Hour int `json:"hour"` - Rx int64 `json:"rx"` - Tx int64 `json:"tx"` - } `json:"hour"` - Day []struct { - ID int `json:"id"` - Date struct { - Year int `json:"year"` - Month int `json:"month"` - Day int `json:"day"` - } `json:"date"` - Rx int64 `json:"rx"` - Tx int64 `json:"tx"` - } `json:"day"` - Month []struct { - ID int `json:"id"` - Date struct { - Year int `json:"year"` - Month int `json:"month"` - } `json:"date"` - Rx int64 `json:"rx"` - Tx int64 `json:"tx"` - } `json:"month"` - Year []struct { - ID int `json:"id"` - Date struct { - Year int `json:"year"` - } `json:"date"` - Rx int64 `json:"rx"` - Tx int64 `json:"tx"` - } `json:"year"` - } `json:"traffic"` - } `json:"interfaces"` -} - // MonitorUpdate represents real-time monitoring updates type MonitorUpdate struct { Type string `json:"type"` diff --git a/internal/utils/crypto.go b/internal/utils/crypto.go deleted file mode 100644 index c4c5fff8..00000000 --- a/internal/utils/crypto.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2024-2026, s0up and the autobrr contributors. -// SPDX-License-Identifier: GPL-2.0-or-later - -package utils - -import ( - "crypto/rand" - "encoding/hex" -) - -func GenerateSecureToken(length int) (string, error) { - bytes := make([]byte, length) - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return hex.EncodeToString(bytes), nil -} diff --git a/internal/utils/tailscale.go b/internal/utils/tailscale.go index 6f2de84f..2fe4b3cd 100644 --- a/internal/utils/tailscale.go +++ b/internal/utils/tailscale.go @@ -4,9 +4,11 @@ package utils import ( - "net" + "net/netip" "net/url" "strings" + + "tailscale.com/net/tsaddr" ) // IsTailscaleIP checks if a given URL contains a Tailscale IP address @@ -16,30 +18,12 @@ func IsTailscaleIP(urlStr string) bool { return false } - host := parsedURL.Hostname() - if host == "" { - return false - } - - // Parse the IP address - ip := net.ParseIP(host) - if ip == nil { + ip, err := netip.ParseAddr(parsedURL.Hostname()) + if err != nil { return false } - // Check if it's in the Tailscale CGNAT range (100.64.0.0/10) - _, tailscaleNet, _ := net.ParseCIDR("100.64.0.0/10") - if tailscaleNet != nil && tailscaleNet.Contains(ip) { - return true - } - - // Check if it's in the Tailscale IPv6 range (fd7a:115c:a1e0::/48) - _, tailscaleNet6, _ := net.ParseCIDR("fd7a:115c:a1e0::/48") - if tailscaleNet6 != nil && tailscaleNet6.Contains(ip) { - return true - } - - return false + return tsaddr.IsTailscaleIP(ip) } // IsTailscaleHostname checks if a hostname looks like a Tailscale MagicDNS name @@ -73,4 +57,4 @@ func IsTailscaleHostname(urlStr string) bool { // IsTailscaleURL checks if a URL is using Tailscale (either IP or hostname) func IsTailscaleURL(urlStr string) bool { return IsTailscaleIP(urlStr) || IsTailscaleHostname(urlStr) -} \ No newline at end of file +} diff --git a/pkg/migrator/migrator.go b/pkg/migrator/migrator.go index e630ea14..00b5cf74 100644 --- a/pkg/migrator/migrator.go +++ b/pkg/migrator/migrator.go @@ -4,7 +4,6 @@ package migrator import ( - "context" "database/sql" "embed" "fmt" @@ -24,33 +23,12 @@ type Migrator struct { logger Logger embedFS *embed.FS - initialSchemaFile string - initialSchema string - migrations []*Migration migrationLookup map[int]*Migration } type Option func(migrate *Migrator) -func WithTableName(table string) Option { - return func(migrate *Migrator) { - migrate.tableName = table - } -} - -func WithSchemaString(schema string) Option { - return func(migrate *Migrator) { - migrate.initialSchema = schema - } -} - -func WithSchemaFile(file string) Option { - return func(migrate *Migrator) { - migrate.initialSchemaFile = file - } -} - func WithEmbedFS(embedFS embed.FS) Option { return func(migrate *Migrator) { migrate.embedFS = &embedFS @@ -62,14 +40,6 @@ type Logger interface { Printf(string, ...interface{}) } -// LoggerFunc adapts Logger and any third party logger -type LoggerFunc func(string, ...interface{}) - -// Printf implements Logger interface -func (f LoggerFunc) Printf(msg string, args ...interface{}) { - f(msg, args...) -} - func WithLogger(logger Logger) Option { return func(migrate *Migrator) { migrate.logger = logger @@ -78,13 +48,11 @@ func WithLogger(logger Logger) Option { func NewMigrate(db *sql.DB, opts ...Option) *Migrator { m := &Migrator{ - db: db, - tableName: DefaultTableName, - logger: log.New(io.Discard, "migrator: ", 0), - initialSchema: "", - initialSchemaFile: "", - migrations: make([]*Migration, 0), - migrationLookup: map[int]*Migration{}, + db: db, + tableName: DefaultTableName, + logger: log.New(io.Discard, "migrator: ", 0), + migrations: make([]*Migration, 0), + migrationLookup: map[int]*Migration{}, } for _, opt := range opts { @@ -109,18 +77,6 @@ func (m *Migration) String() string { return m.Name } -func (m *Migration) Id() int { - return m.id -} - -func (m *Migrator) TableDrop(table string) error { - if _, err := m.db.Exec(fmt.Sprintf(`DROP TABLE "%s"`, table)); err != nil { - return err - } - - return nil -} - func (m *Migrator) Add(mi ...*Migration) { for _, migration := range mi { migration.db = m.db @@ -130,18 +86,6 @@ func (m *Migrator) Add(mi ...*Migration) { } } -func (m *Migrator) Exec(query string, args ...string) error { - if _, err := m.db.Exec(query, args); err != nil { - return err - } - - return nil -} - -func (m *Migrator) BeginTx() (*sql.Tx, error) { - return m.db.BeginTx(context.Background(), nil) -} - func (m *Migrator) CountApplied() (int, error) { row := m.db.QueryRow(fmt.Sprintf("SELECT count(*) FROM %s", m.tableName)) if row.Err() != nil { @@ -156,15 +100,6 @@ func (m *Migrator) CountApplied() (int, error) { return count, nil } -func (m *Migrator) Pending() ([]*Migration, error) { - count, err := m.CountApplied() - if err != nil { - return nil, err - } - - return m.migrations[count:len(m.migrations)], nil -} - func (m *Migrator) Migrate() error { migrationsTable := fmt.Sprintf(`CREATE TABLE IF NOT EXISTS %s ( id INT8 NOT NULL, @@ -315,55 +250,6 @@ func (m *Migrator) migrateInitialSchema(migration *Migration) error { return err } -func (m *Migrator) migrateInitialSchemaOpt() error { - if m.initialSchema == "" && m.initialSchemaFile != "" { - data, err := m.readFile(m.initialSchemaFile) - if err != nil { - return errors.Wrapf(err, "could not read initial schema: %q", m.initialSchemaFile) - } - - m.initialSchema = string(data) - } - - tx, err := m.db.Begin() - if err != nil { - return errors.Wrap(err, "error could not begin transaction") - } - - defer func() { - if err != nil { - if errRb := tx.Rollback(); errRb != nil { - //err = fmt.Errorf("error rolling back: %s\n%s", errRb, err) - err = errors.Wrapf(errRb, "error rolling back: %q", err) - } - return - } - err = tx.Commit() - }() - - m.logger.Printf("applying base schema migration...") - - if _, err = tx.Exec(m.initialSchema); err != nil { - return errors.Wrap(err, "error applying base schema migration") - } - - if err = m.updateSchemaVersion(tx, 0, "initial schema"); err != nil { - return errors.Wrapf(err, "error updating migration versions: %s", "initial schema") - } - - //if len(m.migrations) > 0 { - // lastMigration := m.migrations[len(m.migrations)-1] - // - // if err = m.updateVersion(tx, len(m.migrations), lastMigration.Name); err != nil { - // return errors.Wrapf(err, "error updating migration versions: %s", lastMigration.Name) - // } - //} - - m.logger.Printf("applied base schema migration") - - return err -} - func (m *Migrator) migrate(migrationNumber int, migration *Migration) error { if migration.Name == "" { return errors.New("migration must have a name") diff --git a/scripts/check_vnstat_data.sh b/scripts/check_vnstat_data.sh deleted file mode 100644 index fc6a8632..00000000 --- a/scripts/check_vnstat_data.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -echo "=== vnstat Data Export Verification ===" -echo "Date: $(date)" -echo "" - -# 1. Check how many days of daily data are in JSON export -echo "=== Daily data count in JSON export ===" -vnstat --json a | jq '.interfaces[0].traffic.day | length' - -# 2. Show the date range of daily data in JSON -echo -e "\n=== Date range of daily data in JSON ===" -echo "First day:" -vnstat --json a | jq '.interfaces[0].traffic.day[0].date' -echo "Last day:" -vnstat --json a | jq '.interfaces[0].traffic.day[-1].date' - -# 3. Calculate total from daily data in JSON -echo -e "\n=== Total from daily data in JSON ===" -vnstat --json a | jq '.interfaces[0].traffic.day | map(.rx + .tx) | add / 1099511627776' | awk '{printf "%.2f TiB\n", $1}' - -# 4. Check monthly data count in JSON -echo -e "\n=== Monthly data in JSON export ===" -echo "Count:" -vnstat --json a | jq '.interfaces[0].traffic.month | length' -echo "Data:" -vnstat --json a | jq '.interfaces[0].traffic.month' - -# 5. Check yearly data in JSON -echo -e "\n=== Yearly data in JSON export ===" -echo "Count:" -vnstat --json a | jq '.interfaces[0].traffic.year | length' -echo "Data:" -vnstat --json a | jq '.interfaces[0].traffic.year' - -# 6. Show total from JSON (all-time) -echo -e "\n=== Total (all-time) from JSON ===" -vnstat --json a | jq '.interfaces[0].traffic.total | (.rx + .tx) / 1099511627776' | awk '{printf "%.2f TiB\n", $1}' - -# 7. Compare with CLI monthly output -echo -e "\n=== Monthly data from CLI ===" -vnstat -m - -# 8. Compare with CLI yearly output -echo -e "\n=== Yearly data from CLI ===" -vnstat -y - -# 9. Show vnstat version and config -echo -e "\n=== vnstat version ===" -vnstat --version - -# 10. Check daily data retention config -echo -e "\n=== Check retention settings (if accessible) ===" -if [ -r /etc/vnstat.conf ]; then - grep -E "DailyDays|MonthlyMonths|YearlyYears" /etc/vnstat.conf | grep -v "^#" -else - echo "Cannot read /etc/vnstat.conf" -fi \ No newline at end of file diff --git a/web/build.go b/web/build.go index 4033a5fc..2977d1ef 100644 --- a/web/build.go +++ b/web/build.go @@ -9,10 +9,7 @@ import ( "io" "io/fs" "net/http" - "os" - "os/exec" "path" - "path/filepath" "strings" "github.com/gin-gonic/gin" @@ -24,43 +21,6 @@ var Dist embed.FS var DistDirFS = MustSubFS(Dist, "dist") -// BuildFrontend executes the frontend build process -func BuildFrontend() error { - webDir, err := filepath.Abs("web") - if err != nil { - return fmt.Errorf("failed to get web directory path: %w", err) - } - - log.Info().Str("webDir", webDir).Msg("Building frontend in directory") - - // Run pnpm install - installCmd := exec.Command("pnpm", "install") - installCmd.Dir = webDir - installCmd.Stdout = os.Stdout - installCmd.Stderr = os.Stderr - if err := installCmd.Run(); err != nil { - return fmt.Errorf("failed to run pnpm install: %w", err) - } - - // Run pnpm build - buildCmd := exec.Command("pnpm", "build") - buildCmd.Dir = webDir - buildCmd.Stdout = os.Stdout - buildCmd.Stderr = os.Stderr - if err := buildCmd.Run(); err != nil { - return fmt.Errorf("failed to run pnpm build: %w", err) - } - - // Verify dist directory was created - distDir := filepath.Join(webDir, "dist") - if _, err := os.Stat(distDir); os.IsNotExist(err) { - return fmt.Errorf("dist directory was not created at %s", distDir) - } - - log.Info().Str("distDir", distDir).Msg("Frontend built successfully") - return nil -} - // MustSubFS creates sub FS from current filesystem or panic on failure func MustSubFS(currentFs fs.FS, fsRoot string) fs.FS { subFs, err := fs.Sub(currentFs, fsRoot) diff --git a/web/package.json b/web/package.json index d0ec5a9d..ddc7f422 100644 --- a/web/package.json +++ b/web/package.json @@ -17,15 +17,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.1", - "@fortawesome/fontawesome-svg-core": "^7.3.0", - "@fortawesome/free-brands-svg-icons": "^7.3.0", - "@fortawesome/free-solid-svg-icons": "^7.3.0", - "@fortawesome/react-fontawesome": "^3.3.1", - "@headlessui/react": "^2.2.10", "@heroicons/react": "^2.2.0", - "@mui/material": "^9.1.2", "@radix-ui/react-checkbox": "^1.3.6", "@radix-ui/react-collapsible": "^1.1.15", "@radix-ui/react-dialog": "^1.1.18", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 5457a108..ff9a094d 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -25,33 +25,9 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.2.7) - '@emotion/react': - specifier: ^11.14.0 - version: 11.14.0(@types/react@19.2.17)(react@19.2.7) - '@emotion/styled': - specifier: ^11.14.1 - version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7) - '@fortawesome/fontawesome-svg-core': - specifier: ^7.3.0 - version: 7.3.0 - '@fortawesome/free-brands-svg-icons': - specifier: ^7.3.0 - version: 7.3.0 - '@fortawesome/free-solid-svg-icons': - specifier: ^7.3.0 - version: 7.3.0 - '@fortawesome/react-fontawesome': - specifier: ^3.3.1 - version: 3.3.1(@fortawesome/fontawesome-svg-core@7.3.0)(react@19.2.7) - '@headlessui/react': - specifier: ^2.2.10 - version: 2.2.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@heroicons/react': specifier: ^2.2.0 version: 2.2.0(react@19.2.7) - '@mui/material': - specifier: ^9.1.2 - version: 9.1.2(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-checkbox': specifier: ^1.3.6 version: 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -216,10 +192,6 @@ packages: peerDependencies: ajv: '>=8' - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -232,10 +204,6 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} @@ -265,10 +233,6 @@ packages: peerDependencies: '@babel/core': ^7.29.6 - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.29.7': resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} @@ -277,10 +241,6 @@ packages: resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.29.7': resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} @@ -730,26 +690,14 @@ packages: peerDependencies: '@babel/core': ^7.29.6 - '@babel/runtime@7.27.6': - resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.29.7': resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - '@babel/template@7.29.7': resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} @@ -802,60 +750,12 @@ packages: '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} - '@emotion/babel-plugin@11.13.5': - resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} - - '@emotion/cache@11.14.0': - resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - - '@emotion/hash@0.9.2': - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - '@emotion/is-prop-valid@1.3.1': resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} '@emotion/memoize@0.9.0': resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - '@emotion/react@11.14.0': - resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/serialize@1.3.3': - resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} - - '@emotion/sheet@1.4.0': - resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} - - '@emotion/styled@11.14.1': - resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} - peerDependencies: - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/unitless@0.10.0': - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0': - resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} - peerDependencies: - react: '>=16.8.0' - - '@emotion/utils@1.4.2': - resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} - - '@emotion/weak-memoize@0.4.0': - resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -1063,45 +963,9 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react@0.26.28': - resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@fortawesome/fontawesome-common-types@7.3.0': - resolution: {integrity: sha512-X/vND0Y1l9fVJ9O79UgtZnXSpz4aNF3bXlDxiJAEAm6kgeSftp9wjjBPgqzazJV8YlmxfRoeXNfSCJ48sf/Hhw==} - engines: {node: '>=6'} - - '@fortawesome/fontawesome-svg-core@7.3.0': - resolution: {integrity: sha512-MFbTNLDWkLJwbozDvHOZ7hwyDjQcBMBattlcOQ6ZmV5YD9bBrqdl1rNtmVjQ/lzqveXXX3sMz2Ew6fAgXoxmkw==} - engines: {node: '>=6'} - - '@fortawesome/free-brands-svg-icons@7.3.0': - resolution: {integrity: sha512-W6C9ZbPWpwcUycq6U90lVbvWTrEnr01Td2x5jlO8fOtvww4kqDBMSmZqXQCc6FIIJD4kTH6G1MBRExAaSXz3yg==} - engines: {node: '>=6'} - - '@fortawesome/free-solid-svg-icons@7.3.0': - resolution: {integrity: sha512-YxI/CuwWeI3nPIoYU//vkDS+3ige/67DPZ6XwMATpYEFESzO9L8zfJOKllGRgIlpT/uebrZCcvAzp3peD7GmTw==} - engines: {node: '>=6'} - - '@fortawesome/react-fontawesome@3.3.1': - resolution: {integrity: sha512-wGnAPhfzivDwBWYmEG8MSrEXPruoiMMo48NnsRkj1NZkoaawgOijPNAiSHKMYEoCsqTBSgLTzL6EqTTWGaUR4w==} - engines: {node: '>=20'} - peerDependencies: - '@fortawesome/fontawesome-svg-core': ~6 || ~7 - react: ^18.0.0 || ^19.0.0 - - '@headlessui/react@2.2.10': - resolution: {integrity: sha512-5pVLNK9wlpxTUTy9GpgbX/SdcRh+HBnPktjM2wbiLTH4p+2EPHBO1aoSryUCuKUIItdDWO9ITlhUL8UnUN/oIA==} - engines: {node: '>=10'} - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - '@heroicons/react@2.2.0': resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==} peerDependencies: @@ -1273,15 +1137,6 @@ packages: cpu: [x64] os: [win32] - '@internationalized/date@3.12.1': - resolution: {integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==} - - '@internationalized/number@3.6.6': - resolution: {integrity: sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==} - - '@internationalized/string@3.2.8': - resolution: {integrity: sha512-NdbMQUSfXLYIQol5VyMtinm9pZDciiMfN7RtmSuSB78io1hqwJ0naYfxyW6vgxWBkzWymQa/3uLDlbfmshtCaA==} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1313,86 +1168,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@mui/core-downloads-tracker@9.1.2': - resolution: {integrity: sha512-ZMufoA/YFOEVp48lskcAOTlQYwpdBk4Z++4yUgPDEfuLHIpxBx9g+urGmIBKOtr+7M0ZlYfCxSvrJpEE/S32sg==} - - '@mui/material@9.1.2': - resolution: {integrity: sha512-CN2U1etAL+6qZT2XjJR1Ibv7nyE2wBN3/28b5XpXjQFMtBKNlD45wQupODfJrm9PLanJ1DefocHWIQZ5PkSipQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@mui/material-pigment-css': ^9.1.1 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@mui/material-pigment-css': - optional: true - '@types/react': - optional: true - - '@mui/private-theming@9.1.1': - resolution: {integrity: sha512-oH6c+d6sJ1CZT0Vg2/fHdUQ5zvo9Pn+f+WWk0tlQliHqqIRdN32DZ7UxjalW3LUj4OkHbdWR31biWuLxK9i7Cg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@mui/styled-engine@9.1.1': - resolution: {integrity: sha512-neaYKdJfvEG54q8efHLJR7swpHG/gfSv9xGqW5iTSMsubD7yPCPFrhVBt284j1DOF3uZaaDJSHQL7gz6jGF21Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - - '@mui/system@9.1.2': - resolution: {integrity: sha512-oJxyyummOR6nV8ODF/yugasJ//pSsQxxfYCE9q9RU2Hef0f5RRzJ75M9zr5NvHDhzhGgrPstkaNrJtmcuz/Pdg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - - '@mui/types@9.1.1': - resolution: {integrity: sha512-Zjt7u8wNvDg40rPTGoL+TnfkpuSKjwubsNSFRH1KAVZLcaV4I3AFNHIFbvH7p4F3alEibSbdd90xAgn5Rnfndg==} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - '@mui/utils@9.1.1': - resolution: {integrity: sha512-qSNfnkzZMptaaWFFklpDf4NPJztgwsMDVfM/sSDt+wq4ssYSBhLYwwjuB6eS/+p2IUYbeRzHluzXbw0Zn7aI4A==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -1635,9 +1410,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@quansync/fs@0.1.5': resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} @@ -2040,23 +1812,6 @@ packages: '@radix-ui/rect@1.1.2': resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} - '@react-aria/focus@3.22.0': - resolution: {integrity: sha512-ZfDOVuVhqDsM9mkNji3QUZ/d40JhlVgXrDkrfXylM1035QCrcTHN7m2DpbE95sU2A8EQb4wikvt5jM6K/73BPg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/interactions@3.28.0': - resolution: {integrity: sha512-OXwdU1EWFdMxmr/K1CXNGJzmNlCClByb+PuCaqUyzBymHPCGVhawirLIon/CrIN5psh3AiWpHSh4H0WeJdVpng==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/shared@3.34.0': - resolution: {integrity: sha512-gp6xo/s2lX54AlTjOiqwDnxA7UW79BNvI9dB9pr3LZTzRKCd1ZA+ZbgKw/ReIiWuvvVw/8QFJpnqeeFyLocMcQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - '@reduxjs/toolkit@2.12.0': resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} peerDependencies: @@ -2218,9 +1973,6 @@ packages: '@surma/rollup-plugin-off-main-thread@2.2.3': resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} - '@swc/helpers@0.5.21': - resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} - '@tailwindcss/forms@0.5.11': resolution: {integrity: sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==} peerDependencies: @@ -2351,12 +2103,6 @@ packages: react: '>=16.8' react-dom: '>=16.8' - '@tanstack/react-virtual@3.13.24': - resolution: {integrity: sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==} - 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 - '@tanstack/router-core@1.171.13': resolution: {integrity: sha512-+NOwEj1kO/6IGmpHRIZHasYxYWpyBQGNIZAST9aNrk9Q3YlU9SgqVnl1pbLa9qAKfeNdXQIRve0RQb/0kyDeDA==} engines: {node: '>=20.19'} @@ -2368,9 +2114,6 @@ packages: resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} - '@tanstack/virtual-core@3.14.0': - resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} - '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -2428,22 +2171,11 @@ packages: '@types/node@26.1.0': resolution: {integrity: sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==} - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react-transition-group@4.4.12': - resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} - peerDependencies: - '@types/react': '*' - '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} @@ -2609,10 +2341,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - babel-plugin-polyfill-corejs2@0.4.17: resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} peerDependencies: @@ -2682,10 +2410,6 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -2735,9 +2459,6 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2747,10 +2468,6 @@ packages: core-js-compat@3.49.0: resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2882,9 +2599,6 @@ packages: dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2910,9 +2624,6 @@ packages: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract-get@1.0.0: resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} engines: {node: '>= 0.4'} @@ -3061,9 +2772,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -3222,9 +2930,6 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - ico-endec@0.1.6: resolution: {integrity: sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ==} @@ -3242,10 +2947,6 @@ packages: immer@11.1.9: resolution: {integrity: sha512-sc/z0Cyti70bZa0ZU4sWfAElfovFb9Ni8tArJZLuklYWxegPiK3pDOql1Rq5H0FIRAW9LSQRG6OX4KqBldbhBA==} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -3269,9 +2970,6 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -3296,10 +2994,6 @@ packages: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - is-core-module@2.16.2: resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} @@ -3440,9 +3134,6 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -3570,10 +3261,6 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3717,14 +3404,6 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -3744,10 +3423,6 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3824,9 +3499,6 @@ packages: resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} engines: {node: ^14.13.1 || >=16.0.0} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -3837,12 +3509,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-aria@3.48.0: - resolution: {integrity: sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom@19.2.7: resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: @@ -3853,9 +3519,6 @@ packages: peerDependencies: react: '*' - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-is@19.2.7: resolution: {integrity: sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==} @@ -3891,11 +3554,6 @@ packages: '@types/react': optional: true - react-stately@3.46.0: - resolution: {integrity: sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -3906,12 +3564,6 @@ packages: '@types/react': optional: true - react-transition-group@4.4.5: - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - react@19.2.7: resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} @@ -3972,18 +3624,9 @@ packages: reselect@5.2.0: resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true - resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -4121,10 +3764,6 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -4186,9 +3825,6 @@ packages: resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} engines: {node: '>=14.16'} - stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -4198,9 +3834,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tabbable@6.4.0: - resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} - tailwind-lerp-colors@1.2.6: resolution: {integrity: sha512-YUFTo5GEV2HHfu+On9UbxCgzQTE/RbgZbBJ50gTcKK1bvBimWy7QzTjWAjrKqrhuRZsm2bB1+KatoFwaXGX0gg==} @@ -4548,10 +4181,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@1.10.3: - resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} - engines: {node: '>= 6'} - yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} @@ -4583,12 +4212,6 @@ snapshots: jsonpointer: 5.0.1 leven: 3.1.0 - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 @@ -4617,14 +4240,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - '@babel/generator@7.29.7': dependencies: '@babel/parser': 7.29.7 @@ -4676,8 +4291,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-globals@7.28.0': {} - '@babel/helper-globals@7.29.7': {} '@babel/helper-member-expression-to-functions@7.29.7': @@ -4687,13 +4300,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.29.7': dependencies: '@babel/traverse': 7.29.7 @@ -5252,34 +4858,14 @@ snapshots: '@babel/types': 7.29.7 esutils: 2.0.3 - '@babel/runtime@7.27.6': {} - '@babel/runtime@7.29.7': {} - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.3 - '@babel/types': 7.29.0 - '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.3 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.29.7': dependencies: '@babel/code-frame': 7.29.7 @@ -5356,88 +4942,13 @@ snapshots: tslib: 2.8.1 optional: true - '@emotion/babel-plugin@11.13.5': - dependencies: - '@babel/helper-module-imports': 7.27.1 - '@babel/runtime': 7.27.6 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@emotion/cache@11.14.0': - dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 - - '@emotion/hash@0.9.2': {} - '@emotion/is-prop-valid@1.3.1': dependencies: '@emotion/memoize': 0.9.0 + optional: true - '@emotion/memoize@0.9.0': {} - - '@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@babel/runtime': 7.27.6 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.7) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - transitivePeerDependencies: - - supports-color - - '@emotion/serialize@1.3.3': - dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.2.3 - - '@emotion/sheet@1.4.0': {} - - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@babel/runtime': 7.27.6 - '@emotion/babel-plugin': 11.13.5 - '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7) - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.7) - '@emotion/utils': 1.4.2 - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - transitivePeerDependencies: - - supports-color - - '@emotion/unitless@0.10.0': {} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.7)': - dependencies: - react: 19.2.7 - - '@emotion/utils@1.4.2': {} - - '@emotion/weak-memoize@0.4.0': {} + '@emotion/memoize@0.9.0': + optional: true '@esbuild/aix-ppc64@0.27.2': optional: true @@ -5566,45 +5077,8 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@floating-ui/react@0.26.28(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@floating-ui/utils': 0.2.11 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - tabbable: 6.4.0 - '@floating-ui/utils@0.2.11': {} - '@fortawesome/fontawesome-common-types@7.3.0': {} - - '@fortawesome/fontawesome-svg-core@7.3.0': - dependencies: - '@fortawesome/fontawesome-common-types': 7.3.0 - - '@fortawesome/free-brands-svg-icons@7.3.0': - dependencies: - '@fortawesome/fontawesome-common-types': 7.3.0 - - '@fortawesome/free-solid-svg-icons@7.3.0': - dependencies: - '@fortawesome/fontawesome-common-types': 7.3.0 - - '@fortawesome/react-fontawesome@3.3.1(@fortawesome/fontawesome-svg-core@7.3.0)(react@19.2.7)': - dependencies: - '@fortawesome/fontawesome-svg-core': 7.3.0 - react: 19.2.7 - - '@headlessui/react@2.2.10(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@floating-ui/react': 0.26.28(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@react-aria/focus': 3.22.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@react-aria/interactions': 3.28.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tanstack/react-virtual': 3.13.24(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - use-sync-external-store: 1.6.0(react@19.2.7) - '@heroicons/react@2.2.0(react@19.2.7)': dependencies: react: 19.2.7 @@ -5731,18 +5205,6 @@ snapshots: '@img/sharp-win32-x64@0.35.3': optional: true - '@internationalized/date@3.12.1': - dependencies: - '@swc/helpers': 0.5.21 - - '@internationalized/number@3.6.6': - dependencies: - '@swc/helpers': 0.5.21 - - '@internationalized/string@3.2.8': - dependencies: - '@swc/helpers': 0.5.21 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -5784,85 +5246,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mui/core-downloads-tracker@9.1.2': {} - - '@mui/material@9.1.2(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/core-downloads-tracker': 9.1.2 - '@mui/system': 9.1.2(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7) - '@mui/types': 9.1.1(@types/react@19.2.17) - '@mui/utils': 9.1.1(@types/react@19.2.17)(react@19.2.7) - '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@19.2.17) - clsx: 2.1.1 - csstype: 3.2.3 - prop-types: 15.8.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-is: 19.2.7 - react-transition-group: 4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7) - '@types/react': 19.2.17 - - '@mui/private-theming@9.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/utils': 9.1.1(@types/react@19.2.17)(react@19.2.7) - prop-types: 15.8.1 - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@mui/styled-engine@9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/sheet': 1.4.0 - csstype: 3.2.3 - prop-types: 15.8.1 - react: 19.2.7 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7) - - '@mui/system@9.1.2(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/private-theming': 9.1.1(@types/react@19.2.17)(react@19.2.7) - '@mui/styled-engine': 9.1.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7))(react@19.2.7) - '@mui/types': 9.1.1(@types/react@19.2.17) - '@mui/utils': 9.1.1(@types/react@19.2.17)(react@19.2.7) - clsx: 2.1.1 - csstype: 3.2.3 - prop-types: 15.8.1 - react: 19.2.7 - optionalDependencies: - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7))(@types/react@19.2.17)(react@19.2.7) - '@types/react': 19.2.17 - - '@mui/types@9.1.1(@types/react@19.2.17)': - dependencies: - '@babel/runtime': 7.29.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@mui/utils@9.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@mui/types': 9.1.1(@types/react@19.2.17) - '@types/prop-types': 15.7.15 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 19.2.7 - react-is: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': dependencies: '@emnapi/core': 1.11.0 @@ -6021,8 +5404,6 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@popperjs/core@2.11.8': {} - '@quansync/fs@0.1.5': dependencies: quansync: 0.2.11 @@ -6452,25 +5833,6 @@ snapshots: '@radix-ui/rect@1.1.2': {} - '@react-aria/focus@3.22.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@swc/helpers': 0.5.21 - react: 19.2.7 - react-aria: 3.48.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-dom: 19.2.7(react@19.2.7) - - '@react-aria/interactions@3.28.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@react-types/shared': 3.34.0(react@19.2.7) - '@swc/helpers': 0.5.21 - react: 19.2.7 - react-aria: 3.48.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-dom: 19.2.7(react@19.2.7) - - '@react-types/shared@3.34.0(react@19.2.7)': - dependencies: - react: 19.2.7 - '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7)': dependencies: '@standard-schema/spec': 1.1.0 @@ -6595,10 +5957,6 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - '@swc/helpers@0.5.21': - dependencies: - tslib: 2.8.1 - '@tailwindcss/forms@0.5.11(tailwindcss@4.3.2)': dependencies: mini-svg-data-uri: 1.4.4 @@ -6711,12 +6069,6 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@tanstack/react-virtual@3.13.24(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@tanstack/virtual-core': 3.14.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - '@tanstack/router-core@1.171.13': dependencies: '@tanstack/history': 1.162.0 @@ -6728,8 +6080,6 @@ snapshots: '@tanstack/table-core@8.21.3': {} - '@tanstack/virtual-core@3.14.0': {} - '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -6796,18 +6146,10 @@ snapshots: dependencies: undici-types: 8.3.0 - '@types/parse-json@4.0.2': {} - - '@types/prop-types@15.7.15': {} - '@types/react-dom@19.2.3(@types/react@19.2.17)': dependencies: '@types/react': 19.2.17 - '@types/react-transition-group@4.4.12(@types/react@19.2.17)': - dependencies: - '@types/react': 19.2.17 - '@types/react@19.2.17': dependencies: csstype: 3.2.3 @@ -7002,12 +6344,6 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - babel-plugin-macros@3.1.0: - dependencies: - '@babel/runtime': 7.27.6 - cosmiconfig: 7.1.0 - resolve: 1.22.10 - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): dependencies: '@babel/compat-data': 7.29.7 @@ -7086,8 +6422,6 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - callsites@3.1.0: {} - camelcase-css@2.0.1: {} caniuse-lite@1.0.30001800: {} @@ -7130,8 +6464,6 @@ snapshots: consola@3.4.2: {} - convert-source-map@1.9.0: {} - convert-source-map@2.0.0: {} cookie-es@3.1.1: {} @@ -7140,14 +6472,6 @@ snapshots: dependencies: browserslist: 4.28.4 - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.3 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -7265,11 +6589,6 @@ snapshots: dlv@1.1.3: {} - dom-helpers@5.2.1: - dependencies: - '@babel/runtime': 7.29.7 - csstype: 3.2.3 - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -7293,10 +6612,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - es-abstract-get@1.0.0: dependencies: es-errors: 1.3.0 @@ -7548,8 +6863,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - find-root@1.1.0: {} - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -7718,10 +7031,6 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 - ico-endec@0.1.6: {} idb@7.1.1: {} @@ -7732,11 +7041,6 @@ snapshots: immer@11.1.9: {} - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - imurmurhash@0.1.4: {} inflight@1.0.6: @@ -7760,8 +7064,6 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-arrayish@0.2.1: {} - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -7789,10 +7091,6 @@ snapshots: dependencies: hasown: 2.0.2 - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - is-core-module@2.16.2: dependencies: hasown: 2.0.4 @@ -7918,8 +7216,6 @@ snapshots: json-buffer@3.0.1: {} - json-parse-even-better-errors@2.3.1: {} - json-schema-traverse@0.4.1: {} json-schema-traverse@1.0.0: {} @@ -8026,10 +7322,6 @@ snapshots: lodash@4.18.1: {} - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -8201,17 +7493,6 @@ snapshots: package-json-from-dist@1.0.1: {} - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.29.0 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -8225,8 +7506,6 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-type@4.0.0: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -8282,32 +7561,12 @@ snapshots: pretty-bytes@6.1.1: {} - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - punycode@2.3.1: {} quansync@0.2.11: {} queue-microtask@1.2.3: {} - react-aria@3.48.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@internationalized/date': 3.12.1 - '@internationalized/number': 3.6.6 - '@internationalized/string': 3.2.8 - '@react-types/shared': 3.34.0(react@19.2.7) - '@swc/helpers': 0.5.21 - aria-hidden: 1.2.6 - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-stately: 3.46.0(react@19.2.7) - use-sync-external-store: 1.6.0(react@19.2.7) - react-dom@19.2.7(react@19.2.7): dependencies: react: 19.2.7 @@ -8317,8 +7576,6 @@ snapshots: dependencies: react: 19.2.7 - react-is@16.13.1: {} - react-is@19.2.7: {} react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1): @@ -8349,16 +7606,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - react-stately@3.46.0(react@19.2.7): - dependencies: - '@internationalized/date': 3.12.1 - '@internationalized/number': 3.6.6 - '@internationalized/string': 3.2.8 - '@react-types/shared': 3.34.0(react@19.2.7) - '@swc/helpers': 0.5.21 - react: 19.2.7 - use-sync-external-store: 1.6.0(react@19.2.7) - react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7): dependencies: get-nonce: 1.0.1 @@ -8367,15 +7614,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - react-transition-group@4.4.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react@19.2.7: {} read-cache@1.0.0: @@ -8457,16 +7695,8 @@ snapshots: reselect@5.2.0: {} - resolve-from@4.0.0: {} - resolve-pkg-maps@1.0.0: {} - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -8659,8 +7889,6 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 - source-map@0.5.7: {} - source-map@0.6.1: {} source-map@0.8.0-beta.0: @@ -8744,8 +7972,6 @@ snapshots: strip-json-comments@5.0.3: {} - stylis@4.2.0: {} - sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.5 @@ -8758,8 +7984,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tabbable@6.4.0: {} - tailwind-lerp-colors@1.2.6: dependencies: chroma-js: 2.6.0 @@ -9209,8 +8433,6 @@ snapshots: yallist@3.1.1: {} - yaml@1.10.3: {} - yaml@2.9.0: {} yocto-queue@0.1.0: {} diff --git a/web/src/api/auth.ts b/web/src/api/auth.ts index ffdd28dc..3ef9a2b9 100644 --- a/web/src/api/auth.ts +++ b/web/src/api/auth.ts @@ -167,7 +167,3 @@ export async function checkRegistrationStatus(): Promise { }; } } - -export function getOIDCLoginUrl(): string { - return getApiUrl("/auth/oidc/login"); -} diff --git a/web/src/api/monitor.ts b/web/src/api/monitor.ts index 186fc84b..fde7316d 100644 --- a/web/src/api/monitor.ts +++ b/web/src/api/monitor.ts @@ -138,15 +138,6 @@ export async function getMonitorAgents(): Promise { return Array.isArray(data) ? data : []; } -export async function getMonitorAgent(id: number): Promise { - const response = await fetch(getApiUrl(`/monitor/agents/${id}`)); - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.error || "Failed to fetch agent"); - } - return response.json(); -} - export async function createMonitorAgent( data: CreateAgentRequest, ): Promise { diff --git a/web/src/api/notifications.ts b/web/src/api/notifications.ts index b79c82fb..7f7aafe5 100644 --- a/web/src/api/notifications.ts +++ b/web/src/api/notifications.ts @@ -245,33 +245,3 @@ export const SHOUTRRR_SERVICES = [ { value: "telegram", label: "Telegram", example: "telegram://TOKEN@telegram?chats=CHAT_ID" }, { value: "zulip", label: "Zulip", example: "zulip://BOTMAIL:BOTKEY@DOMAIN" }, ]; - -export const getThresholdOperatorLabel = (operator: string): string => { - switch (operator) { - case "gt": - return "Greater than"; - case "lt": - return "Less than"; - case "eq": - return "Equal to"; - case "gte": - return "Greater than or equal"; - case "lte": - return "Less than or equal"; - default: - return operator; - } -}; - -export const getEventCategoryIcon = (category: string): string => { - switch (category) { - case "speedtest": - return "πŸ“Š"; - case "packetloss": - return "πŸ“‰"; - case "agent": - return "πŸ–₯️"; - default: - return "πŸ“Œ"; - } -}; diff --git a/web/src/api/tailscale.ts b/web/src/api/tailscale.ts deleted file mode 100644 index c22ffdbb..00000000 --- a/web/src/api/tailscale.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2024-2026, s0up and the autobrr contributors. - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -import { getApiUrl } from "@/utils/baseUrl"; - -export interface TailscaleStatus { - enabled: boolean; - status?: string; - hostname?: string; - tailscale_ips?: string[]; - online?: boolean; - magic_dns_suffix?: string; - discovered_agents?: number; -} - -export interface AgentTailscaleStatus { - enabled: boolean; - status: string; - hostname?: string; - tailscale_ips?: string[]; - online?: boolean; -} - -export const tailscaleAPI = { - // Get server Tailscale discovery status - getDiscoveryStatus: async (): Promise => { - const response = await fetch(getApiUrl('/api/monitor/tailscale/status'), { - credentials: 'same-origin', - }); - if (!response.ok) { - throw new Error('Failed to fetch Tailscale status'); - } - return response.json(); - }, - - // Get agent Tailscale status - getAgentStatus: async (agentId: number): Promise => { - const response = await fetch(getApiUrl(`/api/monitor/agents/${agentId}/tailscale/status`), { - credentials: 'same-origin', - }); - if (!response.ok) { - throw new Error('Failed to fetch agent Tailscale status'); - } - return response.json(); - }, -}; \ No newline at end of file diff --git a/web/src/components/Footer.tsx b/web/src/components/Footer.tsx index 329eeef5..be6db2c2 100644 --- a/web/src/components/Footer.tsx +++ b/web/src/components/Footer.tsx @@ -3,8 +3,8 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faReadme, faDiscord } from "@fortawesome/free-brands-svg-icons"; +import { FaDiscord } from "react-icons/fa"; +import { SiReadme } from "react-icons/si"; import { Tooltip, TooltipContent, @@ -28,7 +28,7 @@ export const Footer = () => { alignItems: "center", }} > - + @@ -48,7 +48,7 @@ export const Footer = () => { alignItems: "center", }} > - + diff --git a/web/src/components/Main.tsx b/web/src/components/Main.tsx index bec86a7b..eb579af9 100644 --- a/web/src/components/Main.tsx +++ b/web/src/components/Main.tsx @@ -4,7 +4,6 @@ */ import { useState, useEffect, useMemo } from "react"; -import { Container } from "@mui/material"; import { FaGithub } from "react-icons/fa"; import { XMarkIcon } from "@heroicons/react/20/solid"; import { ShareModal } from "./speedtest/ShareModal"; @@ -444,7 +443,7 @@ export default function Main({ isPublic = false }: MainProps) { return (
- +
{/* Test Progress - Always rendered with fixed height to prevent layout shift */}
@@ -623,13 +622,13 @@ export default function Main({ isPublic = false }: MainProps) { )} - +
{/* Public Footer */} {isPublic && (
- +
Powered by{" "} @@ -655,7 +654,7 @@ export default function Main({ isPublic = false }: MainProps) {
- +
)}
diff --git a/web/src/components/auth/Login.tsx b/web/src/components/auth/Login.tsx index 72d46f41..29095640 100644 --- a/web/src/components/auth/Login.tsx +++ b/web/src/components/auth/Login.tsx @@ -7,8 +7,7 @@ import { useState, useEffect } from "react"; import { useAuth } from "@/context/auth"; import { router } from "@/routes"; import logo from "@/assets/logo_small.png"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faOpenid } from "@fortawesome/free-brands-svg-icons"; +import { FaOpenid } from "react-icons/fa"; import { Footer } from "@/components/Footer"; import { getApiUrl } from "@/utils/baseUrl"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; @@ -142,7 +141,7 @@ export default function Login() { > Sign in with - )} diff --git a/web/src/components/monitor/MonitorSystemInfo.tsx b/web/src/components/monitor/MonitorSystemInfo.tsx index 31bba69c..b59cf421 100644 --- a/web/src/components/monitor/MonitorSystemInfo.tsx +++ b/web/src/components/monitor/MonitorSystemInfo.tsx @@ -12,8 +12,7 @@ import { InformationCircleIcon, CodeBracketIcon, } from "@heroicons/react/24/outline"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faLinux, faApple } from "@fortawesome/free-brands-svg-icons"; +import { FaLinux, FaApple } from "react-icons/fa"; import { SystemInfo, InterfaceInfo } from "@/api/monitor"; import { formatBytes } from "@/utils/formatBytes"; import { formatters } from "@/utils/timeSettings"; @@ -83,15 +82,9 @@ export const MonitorSystemInfo: React.FC = ({
{systemInfo.kernel.toLowerCase().includes("darwin") ? ( - + ) : systemInfo.kernel.toLowerCase().includes("linux") ? ( - + ) : ( )} diff --git a/web/src/components/monitor/tabs/MonitorOverviewTab.tsx b/web/src/components/monitor/tabs/MonitorOverviewTab.tsx index be6308f8..38e56ffe 100644 --- a/web/src/components/monitor/tabs/MonitorOverviewTab.tsx +++ b/web/src/components/monitor/tabs/MonitorOverviewTab.tsx @@ -17,8 +17,7 @@ import { CircleStackIcon, FireIcon, } from "@heroicons/react/24/outline"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faLinux, faApple } from "@fortawesome/free-brands-svg-icons"; +import { FaLinux, FaApple } from "react-icons/fa"; import { MonitorAgent, MonitorStatus } from "@/api/monitor"; import { useMonitorAgent } from "@/hooks/useMonitorAgent"; import { formatBytes } from "@/utils/formatBytes"; @@ -290,10 +289,10 @@ const SystemInfoDetails: React.FC = ({ cpu, kernel }) => if (!kernel) return null; if (kernel.toLowerCase().includes("darwin")) { - return ; + return ; } if (kernel.toLowerCase().includes("linux")) { - return ; + return ; } return null; }; @@ -482,9 +481,9 @@ export const MonitorOverviewTab: React.FC = ({ {systemInfo?.kernel && (
{systemInfo.kernel.toLowerCase().includes("darwin") ? ( - + ) : systemInfo.kernel.toLowerCase().includes("linux") ? ( - + ) : ( )} diff --git a/web/src/components/settings/notifications/index.ts b/web/src/components/settings/notifications/index.ts index b7cada06..823e6075 100644 --- a/web/src/components/settings/notifications/index.ts +++ b/web/src/components/settings/notifications/index.ts @@ -7,5 +7,4 @@ export { AddChannelForm } from "./AddChannelForm"; export { ChannelCard } from "./ChannelCard"; export { ChannelDetails } from "./ChannelDetails"; export { EventCategorySection } from "./EventCategorySection"; -export { EventRuleItem } from "./EventRuleItem"; -export { MobileNotificationView } from "./MobileNotificationView"; \ No newline at end of file +export { MobileNotificationView } from "./MobileNotificationView"; diff --git a/web/src/components/speedtest/traceroute/constants/tracerouteConstants.ts b/web/src/components/speedtest/traceroute/constants/tracerouteConstants.ts index 809c9091..12882eeb 100644 --- a/web/src/components/speedtest/traceroute/constants/tracerouteConstants.ts +++ b/web/src/components/speedtest/traceroute/constants/tracerouteConstants.ts @@ -3,26 +3,11 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -/** - * Animation configuration for Motion components - * Moved outside component to prevent re-creation - */ -export const SPRING_TRANSITION = { - type: "spring" as const, - stiffness: 500, - damping: 30, -} as const; - /** * Default display count for servers list */ export const DEFAULT_SERVER_DISPLAY_COUNT = 4; -/** - * Server list increment when loading more - */ -export const SERVER_DISPLAY_INCREMENT = 4; - /** * Default traceroute configuration */ @@ -72,4 +57,3 @@ export const TABLE_COLUMNS = { rtt3: "RTT 3", average: "Average", } as const; - diff --git a/web/src/components/speedtest/traceroute/utils/serverUtils.ts b/web/src/components/speedtest/traceroute/utils/serverUtils.ts index 90b7a2af..d166258b 100644 --- a/web/src/components/speedtest/traceroute/utils/serverUtils.ts +++ b/web/src/components/speedtest/traceroute/utils/serverUtils.ts @@ -18,7 +18,7 @@ export const SERVER_TYPE_OPTIONS = [ /** * Convert iperf servers to Server format */ -export const convertIperfServersToServerFormat = ( +const convertIperfServersToServerFormat = ( iperfServers: SavedIperfServer[], ): Server[] => { return iperfServers.map((server) => ({ @@ -51,7 +51,7 @@ export const combineServers = ( /** * Filter servers based on search term and server type */ -export const filterServers = ( +const filterServers = ( servers: Server[], searchTerm: string, filterType: string, @@ -77,7 +77,7 @@ export const filterServers = ( /** * Sort servers - iperf servers by name, others by distance */ -export const sortServers = (servers: Server[]): Server[] => { +const sortServers = (servers: Server[]): Server[] => { return servers.sort((a, b) => { // Sort iperf servers by name, others by distance if (a.isIperf && b.isIperf) { diff --git a/web/src/components/speedtest/traceroute/utils/tracerouteUtils.ts b/web/src/components/speedtest/traceroute/utils/tracerouteUtils.ts index 6b392bcc..99ebb938 100644 --- a/web/src/components/speedtest/traceroute/utils/tracerouteUtils.ts +++ b/web/src/components/speedtest/traceroute/utils/tracerouteUtils.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ -import { TracerouteHop } from "@/types/types"; /** * Utility function to extract hostname from any server host value @@ -66,44 +65,3 @@ export const getAverageRTT = (hop: { if (validRTTs.length === 0) return 0; return validRTTs.reduce((sum, rtt) => sum + rtt, 0) / validRTTs.length; }; - -/** - * Get CSS color classes for RTT display based on RTT column - */ -export const getRTTColorClass = ( - timeout: boolean, - rttColumn: "rtt1" | "rtt2" | "rtt3" | "average", -): string => { - if (timeout) return "text-gray-500"; - - switch (rttColumn) { - case "rtt1": - return "text-emerald-600 dark:text-emerald-400"; - case "rtt2": - return "text-yellow-600 dark:text-yellow-400"; - case "rtt3": - return "text-orange-600 dark:text-orange-400"; - case "average": - return "text-gray-700 dark:text-gray-300"; - default: - return "text-gray-700 dark:text-gray-300"; - } -}; - -/** - * Format hop data for display in table/cards - */ -export const formatHopData = (hop: TracerouteHop) => { - return { - number: hop.number, - host: hop.timeout ? "Timeout" : hop.host, - provider: hop.as || "β€”", - countryCode: hop.countryCode, - location: hop.location, - rtt1: formatRTT(hop.rtt1), - rtt2: formatRTT(hop.rtt2), - rtt3: formatRTT(hop.rtt3), - average: hop.timeout ? "*" : formatRTT(getAverageRTT(hop)), - timeout: hop.timeout, - }; -}; diff --git a/web/src/constants/monitorRefreshIntervals.ts b/web/src/constants/monitorRefreshIntervals.ts index 18caa5c5..083a1b00 100644 --- a/web/src/constants/monitorRefreshIntervals.ts +++ b/web/src/constants/monitorRefreshIntervals.ts @@ -10,22 +10,19 @@ export const MONITOR_REFRESH_INTERVALS = { // Live status polling - most frequent STATUS: 5000, // 5 seconds - + // Hardware stats - frequent updates HARDWARE_STATS: 30000, // 30 seconds - + // Agent list refresh AGENTS_LIST: 30000, // 30 seconds - + // Native vnstat data NATIVE_DATA: 60000, // 1 minute - + // System info - less frequent SYSTEM_INFO: 300000, // 5 minutes - + // Peak stats PEAK_STATS: 30000, // 30 seconds } as const; - -// Type for the intervals -export type MonitorRefreshInterval = typeof MONITOR_REFRESH_INTERVALS[keyof typeof MONITOR_REFRESH_INTERVALS]; \ No newline at end of file diff --git a/web/src/types/pwa.d.ts b/web/src/types/pwa.d.ts deleted file mode 100644 index 6fb7c0e7..00000000 --- a/web/src/types/pwa.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -declare module 'virtual:pwa-register/react' { - import { Dispatch, SetStateAction } from 'react' - - export interface RegisterSWOptions { - immediate?: boolean - onNeedRefresh?: () => void - onOfflineReady?: () => void - onRegistered?: (registration: ServiceWorkerRegistration | undefined) => void - onRegisterError?: (error: Error) => void - } - - export function useRegisterSW(options?: RegisterSWOptions): { - needRefresh: [boolean, Dispatch>] - offlineReady: [boolean, Dispatch>] - updateServiceWorker: (reloadPage?: boolean) => Promise - } -} \ No newline at end of file diff --git a/web/src/types/service-worker.d.ts b/web/src/types/service-worker.d.ts deleted file mode 100644 index d3834c1f..00000000 --- a/web/src/types/service-worker.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/// - -declare interface ExtendableEvent extends Event { - waitUntil(fn: Promise): void; -} - -declare interface Args { - [key: string]: unknown; -} \ No newline at end of file diff --git a/web/src/types/speedtest.ts b/web/src/types/speedtest.ts index 3527925f..99a5520f 100644 --- a/web/src/types/speedtest.ts +++ b/web/src/types/speedtest.ts @@ -21,10 +21,3 @@ export interface SpeedTest { isComplete: boolean; isScheduled?: boolean; } - -export interface SpeedTestHistory { - results: SpeedTest[]; - total: number; - page: number; - limit: number; -} diff --git a/web/src/types/types.ts b/web/src/types/types.ts index 28bb6dd7..a78ddad1 100644 --- a/web/src/types/types.ts +++ b/web/src/types/types.ts @@ -80,17 +80,6 @@ export type TimeRange = "1d" | "3d" | "1w" | "1m" | "all"; export type TestType = "speedtest" | "iperf" | "librespeed"; -export interface SpeedUpdate { - isComplete: boolean; - type: "download" | "upload" | "ping" | "complete"; - speed: number; - progress: number; - serverName: string; - latency?: string; - isScheduled: boolean; - testType?: string; // "speedtest", "iperf3", "librespeed" -} - export interface PaginatedResponse { data: T[]; page: number; diff --git a/web/src/utils/agentIcons.tsx b/web/src/utils/agentIcons.tsx index 26934195..986bb586 100644 --- a/web/src/utils/agentIcons.tsx +++ b/web/src/utils/agentIcons.tsx @@ -17,19 +17,10 @@ import { TvIcon, HomeIcon, } from "@heroicons/react/24/outline"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faLaptop } from "@fortawesome/free-solid-svg-icons"; -import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { FaLaptop } from "react-icons/fa"; export type IconComponent = React.FC<{ className?: string }>; -// Helper function to create Font Awesome icon components -const createFAIcon = (icon: IconProp): IconComponent => { - return ({ className }) => ( - - ); -}; - interface DevicePattern { patterns: RegExp[]; icon: IconComponent; @@ -43,7 +34,7 @@ const devicePatterns: DevicePattern[] = [ /\b(laptop|macbook|notebook|thinkpad|ideapad|pavilion|inspiron|latitude|xps|chromebook)\b/i, /\b(macbook pro|macbook air|surface laptop)\b/i, ], - icon: createFAIcon(faLaptop), + icon: FaLaptop, description: "Laptop", }, diff --git a/web/src/utils/countryFlags.ts b/web/src/utils/countryFlags.ts index ed5a0f91..c1732063 100644 --- a/web/src/utils/countryFlags.ts +++ b/web/src/utils/countryFlags.ts @@ -4,7 +4,7 @@ */ // Country code to emoji flag mapping -export const flagEmojis: Record = { +const flagEmojis: Record = { US: "πŸ‡ΊπŸ‡Έ", NL: "πŸ‡³πŸ‡±", DE: "πŸ‡©πŸ‡ͺ", diff --git a/web/src/utils/darkMode.ts b/web/src/utils/darkMode.ts index 81b18f42..bf8568c2 100644 --- a/web/src/utils/darkMode.ts +++ b/web/src/utils/darkMode.ts @@ -148,23 +148,6 @@ const addMediaQueryListener = ( } }; -// Public API -export const toggleDarkMode = (): void => { - const root = document.documentElement; - root.classList.add(THEME_TRANSITION_CLASS); - - const isDark = root.classList.contains(THEME_DARK); - const newTheme: Theme = isDark ? THEME_LIGHT : THEME_DARK; - - applyTheme(!isDark, false); - setStoredTheme(newTheme); - dispatchThemeChange(newTheme, false); - - setTimeout(() => { - root.classList.remove(THEME_TRANSITION_CLASS); - }, THEME_TRANSITION_DURATION); -}; - export const initializeDarkMode = (): void => { injectThemeStyles(); @@ -190,7 +173,7 @@ export const initializeDarkMode = (): void => { addMediaQueryListener(systemPreference, handleSystemThemeChange); }; -export const resetToSystemTheme = (): void => { +const resetToSystemTheme = (): void => { setStoredTheme(THEME_AUTO); applyTheme(getSystemPreference().matches, true); dispatchThemeChange(getSystemTheme(), false); @@ -211,11 +194,6 @@ export const setThemeMode = (mode: ThemeMode): void => { dispatchThemeChange(mode, false); }; -export const hasManualPreference = (): boolean => { - const theme = getStoredTheme(); - return theme === THEME_DARK || theme === THEME_LIGHT; -}; - export const getCurrentThemeMode = (): ThemeMode => { return getStoredTheme() || THEME_AUTO; }; diff --git a/web/src/utils/timeSettings.ts b/web/src/utils/timeSettings.ts index 9c1769a5..5a0777e9 100644 --- a/web/src/utils/timeSettings.ts +++ b/web/src/utils/timeSettings.ts @@ -4,7 +4,7 @@ */ import React from "react"; -import { formatInTimeZone, toZonedTime, fromZonedTime } from 'date-fns-tz'; +import { formatInTimeZone, fromZonedTime } from 'date-fns-tz'; export interface TimeFormatSettings { timezone: string; @@ -21,45 +21,45 @@ export interface TimezoneOption { export const TIMEZONE_OPTIONS: TimezoneOption[] = [ // UTC-10 { value: "Pacific/Honolulu", label: "Hawaii Time (HST)", offset: "UTC-10" }, - + // UTC-9/-8 { value: "America/Anchorage", label: "Alaska Time (AKST/AKDT)", offset: "UTC-9/-8" }, - + // UTC-8/-7 { value: "America/Los_Angeles", label: "Pacific Time (PST/PDT)", offset: "UTC-8/-7" }, { value: "America/Vancouver", label: "Pacific Time Canada (PST/PDT)", offset: "UTC-8/-7" }, - + // UTC-7/-6 { value: "America/Denver", label: "Mountain Time (MST/MDT)", offset: "UTC-7/-6" }, { value: "America/Phoenix", label: "Arizona Time (MST)", offset: "UTC-7" }, - + // UTC-6/-5 { value: "America/Chicago", label: "Central Time (CST/CDT)", offset: "UTC-6/-5" }, { value: "America/Mexico_City", label: "Mexico Central Time (CST/CDT)", offset: "UTC-6/-5" }, - + // UTC-5/-4 { value: "America/New_York", label: "Eastern Time (EST/EDT)", offset: "UTC-5/-4" }, { value: "America/Toronto", label: "Eastern Time Canada (EST/EDT)", offset: "UTC-5/-4" }, - + // UTC-5 { value: "America/Lima", label: "Peru Time (PET)", offset: "UTC-5" }, { value: "America/Bogota", label: "Colombia Time (COT)", offset: "UTC-5" }, - + // UTC-4/-3 { value: "America/Santiago", label: "Chile Time (CLT/CLST)", offset: "UTC-4/-3" }, - + // UTC-3/-2 { value: "America/Sao_Paulo", label: "BrasΓ­lia Time (BRT/BRST)", offset: "UTC-3/-2" }, - + // UTC-3 { value: "America/Buenos_Aires", label: "Argentina Time (ART)", offset: "UTC-3" }, - + // UTC+0 { value: "UTC", label: "Coordinated Universal Time (UTC)", offset: "UTC+0" }, - + // UTC+0/+1 { value: "Europe/London", label: "Greenwich Mean Time (GMT/BST)", offset: "UTC+0/+1" }, - + // UTC+1/+2 { value: "Europe/Berlin", label: "Central European Time (CET/CEST)", offset: "UTC+1/+2" }, { value: "Europe/Paris", label: "France Time (CET/CEST)", offset: "UTC+1/+2" }, @@ -67,49 +67,49 @@ export const TIMEZONE_OPTIONS: TimezoneOption[] = [ { value: "Europe/Madrid", label: "Spain Time (CET/CEST)", offset: "UTC+1/+2" }, { value: "Europe/Amsterdam", label: "Netherlands Time (CET/CEST)", offset: "UTC+1/+2" }, { value: "Europe/Stockholm", label: "Sweden Time (CET/CEST)", offset: "UTC+1/+2" }, - + // UTC+2/+3 { value: "Europe/Helsinki", label: "Finland Time (EET/EEST)", offset: "UTC+2/+3" }, { value: "Europe/Athens", label: "Greece Time (EET/EEST)", offset: "UTC+2/+3" }, - + // UTC+3 { value: "Europe/Istanbul", label: "Turkey Time (TRT)", offset: "UTC+3" }, { value: "Europe/Moscow", label: "Moscow Time (MSK)", offset: "UTC+3" }, - + // UTC+4 { value: "Asia/Dubai", label: "Gulf Standard Time (GST)", offset: "UTC+4" }, - + // UTC+5 { value: "Asia/Karachi", label: "Pakistan Time (PKT)", offset: "UTC+5" }, - + // UTC+5:30 { value: "Asia/Kolkata", label: "India Standard Time (IST)", offset: "UTC+5:30" }, - + // UTC+6 { value: "Asia/Dhaka", label: "Bangladesh Time (BST)", offset: "UTC+6" }, - + // UTC+7 { value: "Asia/Bangkok", label: "Indochina Time (ICT)", offset: "UTC+7" }, - + // UTC+8 { value: "Asia/Singapore", label: "Singapore Time (SGT)", offset: "UTC+8" }, { value: "Asia/Shanghai", label: "China Time (CST)", offset: "UTC+8" }, { value: "Asia/Hong_Kong", label: "Hong Kong Time (HKT)", offset: "UTC+8" }, { value: "Australia/Perth", label: "Western Australia Time (AWST)", offset: "UTC+8" }, - + // UTC+9 { value: "Asia/Tokyo", label: "Japan Time (JST)", offset: "UTC+9" }, { value: "Asia/Seoul", label: "Korea Time (KST)", offset: "UTC+9" }, - + // UTC+9:30/+10:30 { value: "Australia/Adelaide", label: "Central Australia Time (ACST/ACDT)", offset: "UTC+9:30/+10:30" }, - + // UTC+10/+11 { value: "Australia/Sydney", label: "Eastern Australia Time (AEST/AEDT)", offset: "UTC+10/+11" }, - + // UTC+10 { value: "Australia/Brisbane", label: "Queensland Time (AEST)", offset: "UTC+10" }, - + // UTC+12/+13 { value: "Pacific/Auckland", label: "New Zealand Time (NZST/NZDT)", offset: "UTC+12/+13" }, ]; @@ -156,7 +156,7 @@ export const saveTimeFormatSettings = (settings: TimeFormatSettings): void => { /** * Get the effective timezone for date formatting */ -export const getEffectiveTimezone = (settings?: TimeFormatSettings): string | undefined => { +const getEffectiveTimezone = (settings?: TimeFormatSettings): string | undefined => { const currentSettings = settings || getTimeFormatSettings(); return currentSettings.timezone === "auto" ? undefined : currentSettings.timezone; }; @@ -177,7 +177,7 @@ export const getTimezoneDisplayName = (timezone: string): string => { const option = TIMEZONE_OPTIONS.find(tz => tz.value === browserTz); return option ? `Auto (${option.label})` : `Auto (${browserTz})`; } - + const option = TIMEZONE_OPTIONS.find(tz => tz.value === timezone); return option ? option.label : timezone; }; @@ -192,7 +192,7 @@ export const formatDateWithSettings = ( ): string => { const currentSettings = settings || getTimeFormatSettings(); const dateObj = typeof date === "string" ? new Date(date) : date; - + const formatOptions: Intl.DateTimeFormatOptions = { ...options, timeZone: getEffectiveTimezone(currentSettings), @@ -255,7 +255,7 @@ export const useTimeSettings = () => { }; window.addEventListener("timeSettingsChanged", handleSettingsChange as EventListener); - + return () => { window.removeEventListener("timeSettingsChanged", handleSettingsChange as EventListener); }; @@ -270,8 +270,7 @@ export const useTimeSettings = () => { }; /** - * Global function to format dates consistently across the app - * This replaces the need for toLocaleString(undefined, options) calls + * Format a date consistently using the current time settings. */ export const formatDate = ( date: Date | string, @@ -280,38 +279,6 @@ export const formatDate = ( return formatDateWithSettings(date, options); }; -/** - * Convert a UTC date to a specific timezone - */ -export const convertUTCToTimezone = ( - utcDate: Date | string, - timezone: string -): Date => { - const date = typeof utcDate === 'string' ? new Date(utcDate) : utcDate; - return toZonedTime(date, timezone); -}; - -/** - * Convert a date from a specific timezone to UTC - */ -export const convertTimezoneToUTC = ( - localDate: Date, - timezone: string -): Date => { - return fromZonedTime(localDate, timezone); -}; - -/** - * Format a UTC date in a specific timezone - */ -export const formatUTCInTimezone = ( - utcDate: Date | string, - timezone: string, - format: string -): string => { - return formatInTimeZone(utcDate, timezone, format); -}; - /** * Convert user's selected time (HH:MM) to UTC for backend storage * Using a reference date to handle timezone offset calculation @@ -416,7 +383,7 @@ export const formatters = { chartTick: (date: Date | string, timeRange: string, isMobile?: boolean) => { const settings = getTimeFormatSettings(); const dateObj = typeof date === "string" ? new Date(date) : date; - + // Dynamic formatting based on time range and mobile context switch (timeRange) { case "1d": @@ -431,7 +398,7 @@ export const formatters = { hour: "numeric", minute: "2-digit", }, settings); - + case "3d": if (isMobile) { return formatDateWithSettings(dateObj, { @@ -444,7 +411,7 @@ export const formatters = { hour: "numeric", minute: "2-digit", }, settings); - + case "1w": if (isMobile) { return formatDateWithSettings(dateObj, { @@ -457,13 +424,13 @@ export const formatters = { month: "short", day: "numeric", }, settings); - + case "1m": return formatDateWithSettings(dateObj, { month: "short", day: "numeric", }, settings); - + case "all": { const now = new Date(); const showYear = dateObj.getFullYear() !== now.getFullYear(); @@ -488,11 +455,11 @@ export const formatters = { }, settings); } }, - + chartTooltip: (date: Date | string, timeRange: string) => { const settings = getTimeFormatSettings(); const dateObj = typeof date === "string" ? new Date(date) : date; - + // More detailed formatting for tooltips switch (timeRange) { case "1d": @@ -504,7 +471,7 @@ export const formatters = { hour: "numeric", minute: "2-digit", }, settings); - + case "1w": return formatDateWithSettings(dateObj, { weekday: "long", @@ -513,7 +480,7 @@ export const formatters = { hour: "numeric", minute: "2-digit", }, settings); - + case "1m": return formatDateWithSettings(dateObj, { weekday: "long", @@ -521,7 +488,7 @@ export const formatters = { day: "numeric", year: "numeric", }, settings); - + case "all": return formatDateWithSettings(dateObj, { weekday: "long", @@ -531,7 +498,7 @@ export const formatters = { hour: "numeric", minute: "2-digit", }, settings); - + default: return formatDateWithSettings(dateObj, { weekday: "long",