From 75fb86cc00595a419c3d9035cc7bd6fd3c7b0936 Mon Sep 17 00:00:00 2001 From: pommee Date: Fri, 31 Oct 2025 18:29:58 +0100 Subject: [PATCH] fix: restructure codebase, make setup and flow easier --- .air.toml | 2 +- .github/workflows/docs.yml | 34 + .github/workflows/pull-request.yml | 2 +- .gitignore | 4 +- Dockerfile.dev | 2 +- README.md | 22 +- backend/alert/discord.go | 10 +- backend/alert/repository.go | 61 + backend/alert/{manager.go => service.go} | 130 +- backend/api/alert.go | 24 +- backend/api/api.go | 101 +- backend/api/audit.go | 2 +- backend/api/auth.go | 67 +- backend/api/blacklist.go | 32 +- backend/api/client.go | 7 +- backend/api/dns.go | 56 +- backend/api/key/key.go | 177 - backend/api/key/models.go | 9 + backend/api/key/repository.go | 55 + backend/api/key/service.go | 176 + backend/api/list.go | 90 +- backend/api/middleware.go | 18 +- backend/api/models/models.go | 6 +- backend/api/notification.go | 19 +- backend/api/ratelimit/ratelimit.go | 4 +- backend/api/resolution.go | 11 +- backend/api/server.go | 46 +- backend/api/settings.go | 28 +- backend/api/upstream.go | 56 +- backend/api/user/models.go | 6 - backend/api/user/user.go | 119 - backend/api/whitelist.go | 8 +- backend/app.go | 111 + backend/{asciiart => }/asciiart.go | 38 +- backend/audit/audit.go | 50 +- backend/audit/repository.go | 51 + backend/audit/service.go | 50 + backend/blacklist/model.go | 16 + backend/blacklist/repository.go | 383 +++ backend/blacklist/service.go | 693 ++++ backend/database/database.go | 46 + backend/database/model.go | 118 + backend/dns/arp.go | 102 +- backend/dns/database/database.go | 148 - backend/dns/database/mac.go | 34 - backend/dns/database/models/models.go | 23 - backend/dns/database/resolution.go | 67 - backend/dns/lists/blacklist.go | 794 ----- backend/dns/lists/whitelist.go | 85 - backend/dns/server/cache.go | 8 +- backend/dns/server/doh.go | 42 +- backend/dns/server/dot.go | 4 +- backend/dns/server/handler.go | 127 +- backend/dns/server/logs.go | 14 +- backend/dns/server/models/request.go | 10 +- backend/dns/server/prefetch/prefetch.go | 204 -- backend/dns/server/server.go | 115 +- backend/jobs/background.go | 76 + backend/lifecycle/manager.go | 61 + backend/logging/logging.go | 72 +- backend/mac/repository.go | 51 + backend/mac/service.go | 24 + backend/notification/repository.go | 53 + backend/notification/service.go | 62 + backend/notifications/notifications.go | 89 - backend/prefetch/repository.go | 53 + backend/prefetch/service.go | 182 + backend/request/model.go | 15 + .../request.go => request/repository.go} | 203 +- backend/request/service.go | 89 + backend/resolution/repository.go | 77 + backend/resolution/service.go | 34 + backend/services/context.go | 51 + backend/services/registry.go | 235 ++ backend/settings/model.go | 69 + backend/settings/settings.go | 228 +- backend/setup/setup.go | 33 +- backend/updater/updater.go | 8 +- backend/user/models.go | 6 + backend/user/repository.go | 66 + backend/user/service.go | 114 + backend/whitelist/repository.go | 61 + backend/whitelist/service.go | 59 + client/components.json | 5 +- client/package.json | 64 +- client/pnpm-lock.yaml | 2975 ++++++++++------- client/public/favicon.ico | Bin 15406 -> 7662 bytes client/public/gray-icon.png | Bin 0 -> 3448168 bytes client/public/logo.png | Bin 27347 -> 3722172 bytes client/src/app/clients/details.tsx | 199 +- client/src/app/clients/map.tsx | 19 +- client/src/app/home/Audit.tsx | 12 +- .../app/home/FrequencyChartBlockedDomains.tsx | 16 +- .../home/FrequencyChartTopBlockedClients.tsx | 16 +- client/src/app/home/ResponseSizeTimeline.tsx | 22 +- client/src/app/home/request-timeline.tsx | 28 +- client/src/app/home/request-types.tsx | 8 +- .../app/lists/{addList.tsx => AddList.tsx} | 0 ...{blockedDomains.tsx => BlockedDomains.tsx} | 4 +- client/src/app/lists/card.tsx | 2 +- client/src/app/lists/details.tsx | 18 +- client/src/app/lists/updateCustom.tsx | 5 +- client/src/app/logs/columns.tsx | 2 +- client/src/app/logs/columnsData.tsx | 6 +- client/src/app/settings/AlertSection.tsx | 18 + client/src/app/settings/DatabaseSection.tsx | 154 + .../app/settings/DynamicSettingsSection.tsx | 62 + client/src/app/settings/ImportModal.tsx | 96 + client/src/app/settings/LoggingSection.tsx | 85 + client/src/app/settings/PasswordModal.tsx | 77 + client/src/app/settings/SecuritySection.tsx | 31 + client/src/app/settings/SettingsRow.tsx | 17 + client/src/app/settings/SettingsSection.tsx | 161 + client/src/app/settings/helpers.ts | 21 + client/src/app/settings/types.ts | 71 + client/src/components/app-sidebar.tsx | 15 +- client/src/components/nav-actions.tsx | 10 +- client/src/components/notifications.tsx | 17 +- client/src/components/server-statistics.tsx | 308 +- client/src/components/site-header.tsx | 2 +- client/src/components/ui/button.tsx | 22 +- client/src/components/ui/checkbox.tsx | 2 +- client/src/components/ui/collapsible.tsx | 2 +- client/src/components/ui/dialog.tsx | 2 +- client/src/components/ui/dropdown-menu.tsx | 2 +- client/src/components/ui/input.tsx | 2 +- client/src/components/ui/label.tsx | 2 +- client/src/components/ui/popover.tsx | 2 +- client/src/components/ui/scroll-area.tsx | 2 +- client/src/components/ui/select.tsx | 2 +- client/src/components/ui/separator.tsx | 6 +- client/src/components/ui/sheet.tsx | 11 +- client/src/components/ui/sidebar.tsx | 33 +- client/src/components/ui/skeleton.tsx | 2 +- client/src/components/ui/slider.tsx | 2 +- client/src/components/ui/switch.tsx | 2 +- client/src/components/ui/tabs.tsx | 2 +- client/src/components/ui/text-animate.tsx | 22 +- client/src/components/ui/toggle-group.tsx | 83 + client/src/components/ui/toggle.tsx | 46 + client/src/components/ui/tooltip.tsx | 8 +- client/src/pages/blacklist.tsx | 26 +- client/src/pages/changelog.tsx | 46 +- client/src/pages/clients.tsx | 22 +- client/src/pages/login.tsx | 34 +- client/src/pages/logs.tsx | 13 +- client/src/pages/prefetch.tsx | 132 +- client/src/pages/resolution.tsx | 130 +- client/src/pages/settings.tsx | 973 ++---- client/src/pages/upstream.tsx | 40 +- client/src/pages/validation.ts | 75 + client/src/pages/whitelist.tsx | 49 +- client/src/shared.tsx | 12 + client/src/util.ts | 38 +- client/tsconfig.app.json | 4 +- client/tsconfig.json | 4 +- client/tsconfig.node.json | 4 +- client/vite.config.ts | 21 +- cmd/flags.go | 84 + cmd/goaway.go | 47 + go.mod | 52 +- go.sum | 72 +- main.go | 328 +- resources/preview.png | Bin 0 -> 518749 bytes settings.yaml | 104 +- test/blacklist/blacklist_test.go | 6 +- test/database/database_test.go | 46 +- 167 files changed, 8092 insertions(+), 5861 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 backend/alert/repository.go rename backend/alert/{manager.go => service.go} (52%) delete mode 100644 backend/api/key/key.go create mode 100644 backend/api/key/models.go create mode 100644 backend/api/key/repository.go create mode 100644 backend/api/key/service.go delete mode 100644 backend/api/user/models.go delete mode 100644 backend/api/user/user.go create mode 100644 backend/app.go rename backend/{asciiart => }/asciiart.go (51%) create mode 100644 backend/audit/repository.go create mode 100644 backend/audit/service.go create mode 100644 backend/blacklist/model.go create mode 100644 backend/blacklist/repository.go create mode 100644 backend/blacklist/service.go create mode 100644 backend/database/database.go create mode 100644 backend/database/model.go delete mode 100644 backend/dns/database/database.go delete mode 100644 backend/dns/database/mac.go delete mode 100644 backend/dns/database/models/models.go delete mode 100644 backend/dns/database/resolution.go delete mode 100644 backend/dns/lists/blacklist.go delete mode 100644 backend/dns/lists/whitelist.go delete mode 100644 backend/dns/server/prefetch/prefetch.go create mode 100644 backend/jobs/background.go create mode 100644 backend/lifecycle/manager.go create mode 100644 backend/mac/repository.go create mode 100644 backend/mac/service.go create mode 100644 backend/notification/repository.go create mode 100644 backend/notification/service.go delete mode 100644 backend/notifications/notifications.go create mode 100644 backend/prefetch/repository.go create mode 100644 backend/prefetch/service.go create mode 100644 backend/request/model.go rename backend/{dns/database/request.go => request/repository.go} (61%) create mode 100644 backend/request/service.go create mode 100644 backend/resolution/repository.go create mode 100644 backend/resolution/service.go create mode 100644 backend/services/context.go create mode 100644 backend/services/registry.go create mode 100644 backend/settings/model.go create mode 100644 backend/user/models.go create mode 100644 backend/user/repository.go create mode 100644 backend/user/service.go create mode 100644 backend/whitelist/repository.go create mode 100644 backend/whitelist/service.go create mode 100644 client/public/gray-icon.png rename client/src/app/lists/{addList.tsx => AddList.tsx} (100%) rename client/src/app/lists/{blockedDomains.tsx => BlockedDomains.tsx} (96%) create mode 100644 client/src/app/settings/AlertSection.tsx create mode 100644 client/src/app/settings/DatabaseSection.tsx create mode 100644 client/src/app/settings/DynamicSettingsSection.tsx create mode 100644 client/src/app/settings/ImportModal.tsx create mode 100644 client/src/app/settings/LoggingSection.tsx create mode 100644 client/src/app/settings/PasswordModal.tsx create mode 100644 client/src/app/settings/SecuritySection.tsx create mode 100644 client/src/app/settings/SettingsRow.tsx create mode 100644 client/src/app/settings/SettingsSection.tsx create mode 100644 client/src/app/settings/helpers.ts create mode 100644 client/src/app/settings/types.ts create mode 100644 client/src/components/ui/toggle-group.tsx create mode 100644 client/src/components/ui/toggle.tsx create mode 100644 client/src/pages/validation.ts create mode 100644 client/src/shared.tsx create mode 100644 cmd/flags.go create mode 100644 cmd/goaway.go create mode 100644 resources/preview.png diff --git a/.air.toml b/.air.toml index 1558d14..800fa1d 100644 --- a/.air.toml +++ b/.air.toml @@ -5,7 +5,7 @@ tmp_dir = "tmp" bin = "./tmp/goaway" args_bin = [ "--dns-port=6121", "--dot-port=6122", "--doh-port=9012", "--webserver-port=8080", "--log-level=0", "--logging=true", "--auth=false", "--statistics-retention=7", "--dashboard=false", "--ansi=true" ] cmd = 'go build -o goaway -ldflags="-X main.version=0.0.0 -X main.commit=ead2d7830add26d53ecab3c907a290f0cdc1e078 -X main.date=2025-04-11T13:37:56Z" -o ./tmp/goaway .' -exclude_dir = [ "assets", "tmp", "vendor", "client", "test", "resources" ] +exclude_dir = [ "assets", "tmp", "vendor", "client", "test", "resources", "config", "data" ] include_ext = [ "go", "tpl", "tmpl", "html", "css", "js", "jsx", "ts", "tsx" ] [color] diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..e8dda99 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,34 @@ +name: Build & Deploy Homepage + +on: + push: + branches: + - main + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: ~/.cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material mkdocs-git-revision-date-localized-plugin + - run: | + mkdocs gh-deploy --force \ + --config-file docs/mkdocs.yml \ + -m "docs: update website" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 821ecf5..151e686 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -41,7 +41,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.25.1" + go-version: "1.25.3" - name: Golangci-lint uses: golangci/golangci-lint-action@v8.0.0 diff --git a/.gitignore b/.gitignore index 722ca3f..4e71fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ tmp requests.json counters.json main -goaway* +goaway *.db** .vite dist @@ -38,3 +38,5 @@ benchmark.prof *.crt *.key + +docs/site/** diff --git a/Dockerfile.dev b/Dockerfile.dev index 2f6102d..2ac9915 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM golang:1.25.1-alpine +FROM golang:1.25.3-alpine WORKDIR /app diff --git a/README.md b/README.md index 71479ce..b2fc2f6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -# GoAway - DNS Sinkhole - ![GitHub Release](https://img.shields.io/github/v/release/pommee/goaway) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/pommee/goaway/release.yml) ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/pommee/goaway/total?color=cornflowerblue) @@ -7,11 +5,19 @@ A lightweight DNS sinkhole for blocking unwanted domains at the network level. Block ads, trackers, and malicious domains before they reach your devices. -![goaway Dashboard Preview](./resources/dashboard.png) +![goaway banner](./resources/preview.png) **[View more screenshots](./resources/PREVIEW.md)** +<<<<<<< Updated upstream ## 🌟 Features +======= +## Getting started + +Instructions for installation, configuration and more can be found on the homepage: https://pommee.github.io/goaway + +## Features +>>>>>>> Stashed changes - DNS-level domain blocking - Web-based admin dashboard @@ -219,7 +225,7 @@ Contributions are welcomed! Here's how you can help: 3. **Submit PRs:** Before any work is started, create a new issue explaining what is wanted, why it would fit, how it can be done, so on and so forth... Once the topic has been discussed with a maintainer then either you or a maintainer starts with the implementation. This is done to prevent any collisions, save time and confusion. [Read more here](./CONTRIBUTING.md) -## ⚠️ Platform Support +## Platform Support | Platform | Architecture | Support Level | | -------- | ------------ | ------------- | @@ -233,7 +239,7 @@ Contributions are welcomed! Here's how you can help: > **Note**: Primary testing is conducted on Linux (amd64). While the aim is to support all listed platforms, functionality on macOS and Windows may vary. -## 🔍 Troubleshooting +## Troubleshooting ### Common Issues @@ -252,7 +258,7 @@ Contributions are welcomed! Here's how you can help: - Check device DNS settings point to GoAway's IP address - Test with `nslookup google.com ` or `dig @ google.com.` -## 📈 Performance +## Performance GoAway is designed to be lightweight and efficient: @@ -261,10 +267,10 @@ GoAway is designed to be lightweight and efficient: - **Network:** Low latency DNS resolution - **Storage:** Logs and statistics use minimal disk space -## 📜 License +## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## 🙏 Acknowledgments +## Acknowledgments This project is heavily inspired by [Pi-hole](https://github.com/pi-hole/pi-hole). Thanks to all people involved for their work. diff --git a/backend/alert/discord.go b/backend/alert/discord.go index f2b604b..f6b0553 100644 --- a/backend/alert/discord.go +++ b/backend/alert/discord.go @@ -16,8 +16,8 @@ type DiscordConfig struct { } type DiscordService struct { - config DiscordConfig client *http.Client + config DiscordConfig } type DiscordWebhookPayload struct { @@ -28,12 +28,12 @@ type DiscordWebhookPayload struct { } type DiscordEmbed struct { + Author *DiscordEmbedAuthor `json:"author,omitempty"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` - Color int `json:"color,omitempty"` - Fields []DiscordEmbedField `json:"fields,omitempty"` Timestamp string `json:"timestamp,omitempty"` - Author *DiscordEmbedAuthor `json:"author,omitempty"` + Fields []DiscordEmbedField `json:"fields,omitempty"` + Color int `json:"color,omitempty"` } type DiscordEmbedField struct { @@ -81,7 +81,7 @@ func (d *DiscordService) SendMessage(ctx context.Context, msg Message) error { return fmt.Errorf("failed to marshal Discord payload: %w", err) } - req, err := http.NewRequestWithContext(ctx, "POST", d.config.WebhookURL, bytes.NewBuffer(jsonData)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, d.config.WebhookURL, bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("failed to create Discord request: %w", err) } diff --git a/backend/alert/repository.go b/backend/alert/repository.go new file mode 100644 index 0000000..a8bfa1d --- /dev/null +++ b/backend/alert/repository.go @@ -0,0 +1,61 @@ +package alert + +import ( + "fmt" + "goaway/backend/database" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type Repository interface { + SaveAlert(alert database.Alert) error + GetAllAlerts() ([]database.Alert, error) + RemoveAlert(alertType string) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) SaveAlert(alert database.Alert) error { + result := r.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "type"}}, + DoUpdates: clause.AssignmentColumns([]string{"enabled", "name", "webhook"}), + }).Create(&alert) + + if result.Error != nil { + return fmt.Errorf("failed to save alert: %w", result.Error) + } + + return nil +} + +func (r *repository) GetAllAlerts() ([]database.Alert, error) { + var alerts []database.Alert + result := r.db.Find(&alerts) + return alerts, result.Error +} + +func (r *repository) RemoveAlert(alertType string) error { + if alertType == "" { + return fmt.Errorf("alert type cannot be empty") + } + + result := r.db.Where("type = ?", alertType).Delete(&database.Alert{}) + + if result.Error != nil { + return fmt.Errorf("failed to remove alert: %w", result.Error) + } + + if result.RowsAffected == 0 { + log.Warning("No alert found with type: %s", alertType) + return fmt.Errorf("no alert found with type: %s", alertType) + } + + return nil +} diff --git a/backend/alert/manager.go b/backend/alert/service.go similarity index 52% rename from backend/alert/manager.go rename to backend/alert/service.go index 45d169b..96745d7 100644 --- a/backend/alert/manager.go +++ b/backend/alert/service.go @@ -3,14 +3,14 @@ package alert import ( "context" "fmt" - "goaway/backend/dns/database" + "goaway/backend/database" "goaway/backend/logging" - - "gorm.io/gorm" - "gorm.io/gorm/clause" ) -var log = logging.GetLogger() +type Service struct { + repository Repository + services []messageSender +} type Message struct { Content string @@ -19,32 +19,50 @@ type Message struct { Severity string } -type MessageSender interface { +type messageSender interface { SendMessage(ctx context.Context, msg Message) error IsEnabled() bool GetServiceName() string } -type Manager struct { - DB *gorm.DB - services []MessageSender -} +var log = logging.GetLogger() -func NewManager(db *gorm.DB) *Manager { - return &Manager{ - DB: db, - services: make([]MessageSender, 0), +func NewService(repo Repository) *Service { + return &Service{ + repository: repo, + services: make([]messageSender, 0), } } -func (m *Manager) Reset() { - m.services = make([]MessageSender, 0) +func (s *Service) reset() { + s.services = make([]messageSender, 0) } -func (m *Manager) Load() { +func (s *Service) registerService(service messageSender) { + log.Debug("Registering alert service: %s", service.GetServiceName()) + s.services = append(s.services, service) +} + +func (s *Service) SaveAlert(alert database.Alert) error { + err := s.repository.SaveAlert(alert) + if err != nil { + log.Error("Failed to save alert settings: %v", err) + return err + } + s.Load() + log.Info("Alert settings saved for type: %s", alert.Type) + + return nil +} + +func (s *Service) GetAllAlerts() ([]database.Alert, error) { + return s.repository.GetAllAlerts() +} + +func (s *Service) Load() { discordService := NewDiscordService(DiscordConfig{}) - alerts, err := m.GetAllAlerts() + alerts, err := s.GetAllAlerts() if err != nil { log.Warning("Failed to load alerts from database: %v", err) @@ -62,61 +80,15 @@ func (m *Manager) Load() { } } - m.Reset() - m.RegisterService(discordService) - log.Debug("Alert Manager loaded with %d services", len(m.services)) + s.reset() + s.registerService(discordService) + log.Debug("Alert Manager loaded with %d services", len(s.services)) } -func (m *Manager) SaveAlert(alert database.Alert) error { - result := m.DB.Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "type"}}, - DoUpdates: clause.AssignmentColumns([]string{"enabled", "name", "webhook"}), - }).Create(&alert) - - if result.Error != nil { - return fmt.Errorf("failed to save alert: %w", result.Error) - } - - log.Info("Alert settings saved for type: %s", alert.Type) - - m.Load() - return nil -} - -func (m *Manager) RemoveAlert(alertType string) error { - if alertType == "" { - return fmt.Errorf("alert type cannot be empty") - } - - result := m.DB.Where("type = ?", alertType).Delete(&database.Alert{}) - - if result.Error != nil { - return fmt.Errorf("failed to remove alert: %w", result.Error) - } - - if result.RowsAffected == 0 { - log.Warning("No alert found with type: %s", alertType) - return fmt.Errorf("no alert found with type: %s", alertType) - } - - m.Load() - return nil -} - -func (m *Manager) GetAllAlerts() ([]database.Alert, error) { - ctx := context.Background() - return gorm.G[database.Alert](m.DB).Find(ctx) -} - -func (m *Manager) RegisterService(service MessageSender) { - log.Debug("Registering alert service: %s", service.GetServiceName()) - m.services = append(m.services, service) -} - -func (m *Manager) SendToAll(ctx context.Context, msg Message) error { +func (s *Service) SendToAll(ctx context.Context, msg Message) error { var errors []error - for _, service := range m.services { + for _, service := range s.services { if !service.IsEnabled() { continue } @@ -135,8 +107,20 @@ func (m *Manager) SendToAll(ctx context.Context, msg Message) error { return nil } -func (m *Manager) SendTest(ctx context.Context, alertType, name, webhook string) error { - err := m.SaveAlert(database.Alert{ +func (s *Service) RemoveAlert(alertType string) error { + err := s.repository.RemoveAlert(alertType) + if err != nil { + log.Error("Failed to remove alert: %v", err) + return err + } + s.Load() + log.Info("Alert removed for type: %s", alertType) + + return nil +} + +func (s *Service) SendTest(ctx context.Context, alertType, name, webhook string) error { + err := s.SaveAlert(database.Alert{ Type: alertType, Enabled: true, Name: name, @@ -147,7 +131,7 @@ func (m *Manager) SendTest(ctx context.Context, alertType, name, webhook string) return err } - for _, service := range m.services { + for _, service := range s.services { if service.GetServiceName() == alertType { log.Info("Sending test alert via %s", service.GetServiceName()) err = service.SendMessage(ctx, Message{ @@ -162,7 +146,7 @@ func (m *Manager) SendTest(ctx context.Context, alertType, name, webhook string) } } - err = m.RemoveAlert(alertType) + err = s.RemoveAlert(alertType) if err != nil { log.Error("Failed to remove test alert settings: %v", err) return err diff --git a/backend/api/alert.go b/backend/api/alert.go index a2515d8..7db268c 100644 --- a/backend/api/alert.go +++ b/backend/api/alert.go @@ -2,7 +2,7 @@ package api import ( "context" - "goaway/backend/dns/database" + "goaway/backend/database" "net/http" "github.com/gin-gonic/gin" @@ -15,14 +15,14 @@ const ( SeverityError = "error" ) -type DiscordSettings struct { - Enabled bool `json:"enabled"` +type discordSettings struct { Name string `json:"name"` Webhook string `json:"webhook"` + Enabled bool `json:"enabled"` } -type AlertSettings struct { - Discord DiscordSettings `json:"discord"` +type alertSettings struct { + Discord discordSettings `json:"discord"` } func (api *API) registerAlertRoutes() { @@ -33,7 +33,7 @@ func (api *API) registerAlertRoutes() { } func (api *API) setAlert(c *gin.Context) { - var request AlertSettings + var request alertSettings err := c.Bind(&request) if err != nil { log.Error("Failed to parse alert settings: %v", err) @@ -41,7 +41,7 @@ func (api *API) setAlert(c *gin.Context) { return } - err = api.DNSServer.Alerts.SaveAlert(database.Alert{ + err = api.DNSServer.AlertService.SaveAlert(database.Alert{ Type: "discord", Enabled: request.Discord.Enabled, Name: request.Discord.Name, @@ -57,17 +57,17 @@ func (api *API) setAlert(c *gin.Context) { } func (api *API) getAlert(c *gin.Context) { - alerts, err := api.DNSServer.Alerts.GetAllAlerts() + alerts, err := api.DNSServer.AlertService.GetAllAlerts() if err != nil { log.Error("Failed to retrieve alert settings: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve alert settings"}) return } - var response AlertSettings + var response alertSettings for _, alert := range alerts { if alert.Type == "discord" { - response.Discord = DiscordSettings{ + response.Discord = discordSettings{ Enabled: alert.Enabled, Name: alert.Name, Webhook: alert.Webhook, @@ -79,7 +79,7 @@ func (api *API) getAlert(c *gin.Context) { } func (api *API) testAlert(c *gin.Context) { - var request AlertSettings + var request alertSettings err := c.Bind(&request) if err != nil { log.Error("Failed to parse alert settings: %v", err) @@ -87,7 +87,7 @@ func (api *API) testAlert(c *gin.Context) { return } - err = api.DNSServer.Alerts.SendTest( + err = api.DNSServer.AlertService.SendTest( context.Background(), "discord", request.Discord.Name, diff --git a/backend/api/api.go b/backend/api/api.go index 21b04c0..6b0ef11 100644 --- a/backend/api/api.go +++ b/backend/api/api.go @@ -5,6 +5,18 @@ import ( "embed" "encoding/base64" "fmt" + "goaway/backend/api/key" + "goaway/backend/api/ratelimit" + "goaway/backend/blacklist" + "goaway/backend/dns/server" + "goaway/backend/logging" + "goaway/backend/notification" + "goaway/backend/prefetch" + "goaway/backend/request" + "goaway/backend/resolution" + "goaway/backend/settings" + "goaway/backend/user" + "goaway/backend/whitelist" "io/fs" "mime" "net" @@ -18,65 +30,53 @@ import ( "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" - - "goaway/backend/api/key" - "goaway/backend/api/ratelimit" - "goaway/backend/api/user" - "goaway/backend/dns/database" - "goaway/backend/dns/lists" - "goaway/backend/dns/server" - "goaway/backend/dns/server/prefetch" - "goaway/backend/logging" - notification "goaway/backend/notifications" - "goaway/backend/settings" + "gorm.io/gorm" ) var log = logging.GetLogger() const ( maxRetries = 10 - retryDelay = 10 * time.Second ) type API struct { - Authentication bool - Config *settings.Config - DNSPort int - - Version string - Commit string - Date string - - router *gin.Engine - routes *gin.RouterGroup - - DNSServer *server.DNSServer - DBManager *database.DatabaseManager - Blacklist *lists.Blacklist - Whitelist *lists.Whitelist - KeyManager *key.ApiKeyManager - PrefetchedDomainsManager *prefetch.Manager - Notifications *notification.Manager - - WSQueries *websocket.Conn + DNS *server.DNSServer + RateLimiter *ratelimit.RateLimiter + DBConn *gorm.DB WSCommunication *websocket.Conn + WSQueries *websocket.Conn + router *gin.Engine + routes *gin.RouterGroup + Config *settings.Config + DNSServer *server.DNSServer + Version string + Date string + Commit string + DNSPort int + Authentication bool - RateLimiter *ratelimit.RateLimiter + RequestService *request.Service + UserService *user.Service + KeyService *key.Service + PrefetchService *prefetch.Service + ResolutionService *resolution.Service + NotificationService *notification.Service + BlacklistService *blacklist.Service + WhitelistService *whitelist.Service } func (api *API) Start(content embed.FS, errorChannel chan struct{}) { api.initializeRouter() api.configureCORS() - api.KeyManager = key.NewApiKeyManager(api.DBManager) - api.RateLimiter = ratelimit.NewRateLimiter( - api.Config.API.RateLimiterConfig.Enabled, - api.Config.API.RateLimiterConfig.MaxTries, - api.Config.API.RateLimiterConfig.Window, - ) api.setupRoutes() + api.RateLimiter = ratelimit.NewRateLimiter( + api.Config.API.RateLimit.Enabled, + api.Config.API.RateLimit.MaxTries, + api.Config.API.RateLimit.Window, + ) - if api.Config.Dashboard { - api.ServeEmbeddedContent(content) + if api.Config.Misc.Dashboard { + api.serveEmbeddedContent(content) } api.startServer(errorChannel) @@ -104,7 +104,7 @@ func (api *API) configureCORS() { } ) - if api.Config.Dashboard { + if api.Config.Misc.Dashboard { corsConfig.AllowOrigins = append(corsConfig.AllowOrigins, "*") } else { log.Warning("Dashboard UI is disabled") @@ -134,23 +134,19 @@ func (api *API) setupRoutes() { func (api *API) setupAuthAndMiddleware() { if api.Authentication { - api.SetupAuth() + api.setupAuth() api.routes.Use(api.authMiddleware()) } else { - log.Warning("Authentication is disabled.") + log.Warning("Dashboard authentication is disabled.") } } -func (api *API) SetupAuth() { - newUser := &user.User{Username: "admin"} - if newUser.Exists(api.DBManager.Conn) { +func (api *API) setupAuth() { + if api.UserService.Exists("admin") { return } - password := api.getOrGeneratePassword() - newUser.Password = password - - if err := newUser.Create(api.DBManager.Conn); err != nil { + if err := api.UserService.CreateUser("admin", api.getOrGeneratePassword()); err != nil { log.Error("Unable to create new user: %v", err) } } @@ -204,7 +200,7 @@ func (api *API) startServer(errorChannel chan struct{}) { } } -func (api *API) ServeEmbeddedContent(content embed.FS) { +func (api *API) serveEmbeddedContent(content embed.FS) { ipAddress, err := GetServerIP() if err != nil { log.Error("Error getting IP address: %v", err) @@ -291,6 +287,7 @@ func injectServerConfig(htmlContent, serverIP string, port int) string { ) } +// GetServerIP retrieves the first non-loopback IPv4 address of the server. func GetServerIP() (string, error) { addrs, err := net.InterfaceAddrs() if err != nil { diff --git a/backend/api/audit.go b/backend/api/audit.go index 1ae3cd8..87811c5 100644 --- a/backend/api/audit.go +++ b/backend/api/audit.go @@ -11,7 +11,7 @@ func (api *API) registerAuditRoutes() { } func (api *API) getAudits(c *gin.Context) { - audits, err := api.DNSServer.Audits.ReadAudits() + audits, err := api.DNSServer.AuditService.ReadAudits() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return diff --git a/backend/api/auth.go b/backend/api/auth.go index 687a9ce..839dedf 100644 --- a/backend/api/auth.go +++ b/backend/api/auth.go @@ -3,18 +3,14 @@ package api import ( "context" "encoding/json" - "errors" "fmt" "goaway/backend/alert" - "goaway/backend/api/user" "goaway/backend/audit" - "goaway/backend/dns/database" + "goaway/backend/user" "io" "net/http" "github.com/gin-gonic/gin" - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" ) func (api *API) registerAuthRoutes() { @@ -27,11 +23,6 @@ func (api *API) registerAuthRoutes() { api.routes.GET("/deleteApiKey", api.deleteAPIKey) } -func (api *API) validateCredentials(username, password string) bool { - existingUser := &user.User{Username: username, Password: password} - return existingUser.Authenticate(api.DBManager.Conn) -} - func (api *API) handleLogin(c *gin.Context) { allowed, timeUntilReset := api.RateLimiter.CheckLimit(c.ClientIP()) if !allowed { @@ -42,21 +33,21 @@ func (api *API) handleLogin(c *gin.Context) { return } - var creds user.Credentials - if err := c.BindJSON(&creds); err != nil { + var loginUser user.User + if err := c.BindJSON(&loginUser); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"}) return } - if err := creds.Validate(); err != nil { + if err := api.UserService.ValidateCredentials(loginUser); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"}) return } - if api.authenticateUser(creds.Username, creds.Password) { - token, err := generateToken(creds.Username) + if api.UserService.Authenticate(loginUser.Username, loginUser.Password) { + token, err := generateToken(loginUser.Username) if err != nil { - log.Info("Token generation failed for user %s: %v", creds.Username, err) + log.Info("Token generation failed for user %s: %v", loginUser.Username, err) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Authentication service temporarily unavailable", }) @@ -75,27 +66,6 @@ func (api *API) handleLogin(c *gin.Context) { } } -func (api *API) authenticateUser(username, password string) bool { - var user database.User - - if err := api.DBManager.Conn.Where("username = ?", username).First(&user).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - log.Info("Authentication attempt for non-existent or invalid credentials") - } else { - log.Warning("Database error during authentication: %v", err) - } - return false - } - - if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { - log.Info("Password comparison failed for user: %s", username) - return false - } - - log.Info("Successful authentication for user: %s", username) - return true -} - func (api *API) getAuthentication(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"enabled": api.Authentication}) } @@ -112,24 +82,23 @@ func (api *API) updatePassword(c *gin.Context) { return } - if !api.validateCredentials("admin", newCredentials.CurrentPassword) { + if !api.UserService.Authenticate("admin", newCredentials.CurrentPassword) { c.JSON(http.StatusBadRequest, gin.H{"error": "Current password is not valid"}) return } - existingUser := user.User{Username: "admin", Password: newCredentials.NewPassword} - if err := existingUser.UpdatePassword(api.DBManager.Conn); err != nil { + if err := api.UserService.UpdatePassword("admin", newCredentials.NewPassword); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Unable to update password"}) return } - logMsg := fmt.Sprintf("Password changed for user '%s'", existingUser.Username) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + logMsg := "Password changed for user 'admin'" + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicUser, Message: logMsg, }) go func() { - _ = api.DNSServer.Alerts.SendToAll(context.Background(), alert.Message{ + _ = api.DNSServer.AlertService.SendToAll(context.Background(), alert.Message{ Title: "System", Content: logMsg, Severity: SeverityWarning, @@ -141,7 +110,7 @@ func (api *API) updatePassword(c *gin.Context) { } func (api *API) createAPIKey(c *gin.Context) { - type NewApiKeyName struct { + type NewAPIKeyName struct { Name string `json:"name"` } @@ -152,21 +121,21 @@ func (api *API) createAPIKey(c *gin.Context) { return } - var request NewApiKeyName + var request NewAPIKeyName if err := json.Unmarshal(body, &request); err != nil { log.Error("Failed to parse JSON: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON format"}) return } - apiKey, err := api.KeyManager.CreateKey(request.Name) + apiKey, err := api.KeyService.CreateKey(request.Name) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } go func() { - _ = api.DNSServer.Alerts.SendToAll(context.Background(), alert.Message{ + _ = api.DNSServer.AlertService.SendToAll(context.Background(), alert.Message{ Title: "System", Content: fmt.Sprintf("New API key created with the name '%s'", request.Name), Severity: SeverityWarning, @@ -177,7 +146,7 @@ func (api *API) createAPIKey(c *gin.Context) { } func (api *API) getAPIKeys(c *gin.Context) { - apiKeys, err := api.KeyManager.GetAllKeys() + apiKeys, err := api.KeyService.GetAllKeys() if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return @@ -188,7 +157,7 @@ func (api *API) getAPIKeys(c *gin.Context) { func (api *API) deleteAPIKey(c *gin.Context) { keyName := c.Query("name") - err := api.KeyManager.DeleteKey(keyName) + err := api.KeyService.DeleteKey(keyName) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return diff --git a/backend/api/blacklist.go b/backend/api/blacklist.go index 740934c..434ac61 100644 --- a/backend/api/blacklist.go +++ b/backend/api/blacklist.go @@ -1,11 +1,11 @@ package api import ( + "context" "encoding/json" "fmt" "goaway/backend/audit" - "goaway/backend/dns/database" - "goaway/backend/dns/server/prefetch" + "goaway/backend/database" "io" "net/http" "strconv" @@ -44,13 +44,13 @@ func (api *API) createPrefetchedDomain(c *gin.Context) { return } - err = api.PrefetchedDomainsManager.AddPrefetchedDomain(prefetchedDomain.Domain, prefetchedDomain.Refresh, prefetchedDomain.QType) + err = api.PrefetchService.AddPrefetchedDomain(prefetchedDomain.Domain, prefetchedDomain.Refresh, prefetchedDomain.QType) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicPrefetch, Message: fmt.Sprintf("Added new prefetch '%s'", prefetchedDomain.Domain), }) @@ -58,8 +58,8 @@ func (api *API) createPrefetchedDomain(c *gin.Context) { } func (api *API) fetchPrefetchedDomains(c *gin.Context) { - prefetchedDomains := make([]prefetch.PrefetchedDomain, 0) - for _, b := range api.PrefetchedDomainsManager.Domains { + prefetchedDomains := make([]database.Prefetch, 0) + for _, b := range api.PrefetchService.Domains { prefetchedDomains = append(prefetchedDomains, b) } c.JSON(http.StatusOK, prefetchedDomains) @@ -72,9 +72,9 @@ func (api *API) removeDomainFromCustom(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "Empty domain name"}) } - err := api.Blacklist.RemoveCustomDomain(domain) + err := api.BlacklistService.RemoveCustomDomain(context.Background(), domain) if err != nil { - log.Debug("Error occured while removing domain from custom list: %v", err) + log.Debug("Error occurred while removing domain from custom list: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to update custom blocklist."}) return } @@ -98,7 +98,7 @@ func (api *API) getBlacklistedDomains(c *gin.Context) { pageSizeInt = 10 } - domains, total, err := api.Blacklist.LoadPaginatedBlacklist(pageInt, pageSizeInt, search) + domains, total, err := api.BlacklistService.LoadPaginatedBlacklist(context.Background(), pageInt, pageSizeInt, search) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -113,8 +113,8 @@ func (api *API) getBlacklistedDomains(c *gin.Context) { } func (api *API) getTopBlockedDomains(c *gin.Context) { - _, blocked, _ := api.Blacklist.GetAllowedAndBlocked() - topBlockedDomains, err := database.GetTopBlockedDomains(api.DBManager.Conn, blocked) + _, blocked, _ := api.BlacklistService.GetAllowedAndBlocked(context.Background()) + topBlockedDomains, err := api.RequestService.GetTopBlockedDomains(blocked) if err != nil { log.Error("%v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -131,7 +131,7 @@ func (api *API) getDomainsForList(c *gin.Context) { return } - domains, err := api.Blacklist.GetDomainsForList(list) + domains, _, err := api.BlacklistService.FetchDBHostsList(context.Background(), list) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -143,19 +143,19 @@ func (api *API) getDomainsForList(c *gin.Context) { func (api *API) deletePrefetchedDomain(c *gin.Context) { domainPrefetchToDelete := c.Query("domain") - domain := api.PrefetchedDomainsManager.Domains[domainPrefetchToDelete] - if (domain == prefetch.PrefetchedDomain{}) { + domain := api.PrefetchService.Domains[domainPrefetchToDelete] + if (domain == database.Prefetch{}) { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("%s does not exist", domainPrefetchToDelete)}) return } - err := api.PrefetchedDomainsManager.RemovePrefetchedDomain(domainPrefetchToDelete) + err := api.PrefetchService.RemovePrefetchedDomain(domainPrefetchToDelete) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicPrefetch, Message: fmt.Sprintf("Removed prefetched domain '%s'", domainPrefetchToDelete), }) diff --git a/backend/api/client.go b/backend/api/client.go index 6425acb..e9a54bc 100644 --- a/backend/api/client.go +++ b/backend/api/client.go @@ -1,7 +1,6 @@ package api import ( - "goaway/backend/dns/database" "net/http" "github.com/gin-gonic/gin" @@ -14,7 +13,7 @@ func (api *API) registerClientRoutes() { } func (api *API) getClients(c *gin.Context) { - uniqueClients, err := database.FetchAllClients(api.DBManager.Conn) + uniqueClients, err := api.RequestService.FetchAllClients() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return @@ -37,7 +36,7 @@ func (api *API) getClients(c *gin.Context) { func (api *API) getClientDetails(c *gin.Context) { clientIP := c.DefaultQuery("clientIP", "") - clientRequestDetails, mostQueriedDomain, domainQueryCounts, err := database.GetClientDetailsWithDomains(api.DBManager.Conn, clientIP) + clientRequestDetails, mostQueriedDomain, domainQueryCounts, err := api.RequestService.GetClientDetailsWithDomains(clientIP) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -57,7 +56,7 @@ func (api *API) getClientDetails(c *gin.Context) { } func (api *API) getTopClients(c *gin.Context) { - topClients, err := database.GetTopClients(api.DBManager.Conn) + topClients, err := api.RequestService.GetTopClients() if err != nil { log.Error("%v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) diff --git a/backend/api/dns.go b/backend/api/dns.go index baffa42..ed91942 100644 --- a/backend/api/dns.go +++ b/backend/api/dns.go @@ -1,9 +1,10 @@ package api import ( + "context" "goaway/backend/api/models" "goaway/backend/audit" - "goaway/backend/dns/database" + "goaway/backend/database" "goaway/backend/dns/server" model "goaway/backend/dns/server/models" "goaway/backend/settings" @@ -18,7 +19,7 @@ import ( ) func (api *API) registerDNSRoutes() { - api.setupWSLiveQueries(api.PrefetchedDomainsManager.DNS) + api.setupWSLiveQueries(api.DNS) api.routes.POST("/pause", api.pauseBlocking) api.routes.GET("/pause", api.getBlocking) @@ -44,52 +45,63 @@ func (api *API) pauseBlocking(c *gin.Context) { return } - api.Config.DNS.Status = settings.Status{ - Paused: true, - PausedAt: time.Now(), - PauseTime: blockTime.Time, + now := time.Now() + if blockTime.Time <= 0 { + c.JSON(http.StatusBadRequest, gin.H{ + "error": "Time must be greater than 0", + }) + return } + duration := time.Duration(blockTime.Time) * time.Second + pauseTime := now.Add(duration) + + api.Config.DNS.Status.Paused = true + api.Config.DNS.Status.PausedAt = now + api.Config.DNS.Status.PauseTime = pauseTime + log.Info("DNS blocking paused for %d seconds", blockTime.Time) c.Status(http.StatusOK) } func (api *API) getBlocking(c *gin.Context) { if api.Config.DNS.Status.Paused { - elapsed := time.Since(api.Config.DNS.Status.PausedAt).Seconds() - remainingTime := api.Config.DNS.Status.PauseTime - int(elapsed) + now := time.Now() + remainingTime := api.Config.DNS.Status.PauseTime.Sub(now) if remainingTime <= 0 { + api.Config.DNS.Status.Paused = false c.JSON(http.StatusOK, gin.H{"paused": false}) + return } else { - c.JSON(http.StatusOK, gin.H{"paused": true, "timeLeft": remainingTime}) + secondsLeft := int(remainingTime.Seconds()) + c.JSON(http.StatusOK, gin.H{"paused": true, "timeLeft": secondsLeft}) + return } } - if !api.Config.DNS.Status.Paused { - c.JSON(http.StatusOK, gin.H{"paused": false}) - } + c.JSON(http.StatusOK, gin.H{"paused": false}) } func (api *API) getQueries(c *gin.Context) { query := parseQueryParams(c) type result struct { + err error queries []model.RequestLogEntry total int - err error } queryCh := make(chan result, 1) countCh := make(chan result, 1) go func() { - queries, err := database.FetchQueries(api.DBManager.Conn, query) + queries, err := api.RequestService.FetchQueries(query) queryCh <- result{queries: queries, err: err} }() go func() { - total, err := database.CountQueries(api.DBManager.Conn, query.Search) + total, err := api.RequestService.CountQueries(query.Search) countCh <- result{total: total, err: err} }() @@ -169,7 +181,7 @@ func (api *API) getQueryTimestamps(c *gin.Context) { return } - timestamps, err := database.GetRequestSummaryByInterval(interval, api.DBManager.Conn) + timestamps, err := api.RequestService.GetRequestSummaryByInterval(interval) if err != nil { log.Error("Failed to get request summary: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) @@ -187,7 +199,7 @@ func (api *API) getResponseSizeTimestamps(c *gin.Context) { return } - timestamps, err := database.GetResponseSizeSummaryByInterval(interval, api.DBManager.Conn) + timestamps, err := api.RequestService.GetResponseSizeSummaryByInterval(interval) if err != nil { log.Error("Error fetching response size timestamps: %v", err.Error()) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -198,7 +210,7 @@ func (api *API) getResponseSizeTimestamps(c *gin.Context) { } func (api *API) getQueryTypes(c *gin.Context) { - queries, err := database.GetUniqueQueryTypes(api.DBManager.Conn) + queries, err := api.RequestService.GetUniqueQueryTypes() if err != nil { log.Error("%v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -209,19 +221,19 @@ func (api *API) getQueryTypes(c *gin.Context) { } func (api *API) clearQueries(c *gin.Context) { - if err := api.DBManager.Conn.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&database.RequestLog{}).Error; err != nil { + if err := api.DBConn.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&database.RequestLog{}).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Could not clear query logs", "reason": err.Error()}) return } - if err := api.DBManager.Conn.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&database.RequestLogIP{}).Error; err != nil { + if err := api.DBConn.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&database.RequestLogIP{}).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Could not clear IP logs", "reason": err.Error()}) return } - api.Blacklist.Vacuum() + api.BlacklistService.Vacuum(context.Background()) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicLogs, Message: "Logs were cleared", }) diff --git a/backend/api/key/key.go b/backend/api/key/key.go deleted file mode 100644 index c566c04..0000000 --- a/backend/api/key/key.go +++ /dev/null @@ -1,177 +0,0 @@ -package key - -import ( - "crypto/rand" - "encoding/hex" - "fmt" - "goaway/backend/dns/database" - "goaway/backend/logging" - "sort" - "sync" - "time" -) - -type ApiKey struct { - Name string `json:"name"` - Key string `json:"key"` - CreatedAt time.Time `json:"createdAt"` -} - -type ApiKeyManager struct { - dbManager *database.DatabaseManager - keyCache map[string]ApiKey - cacheMu sync.RWMutex - cacheTime time.Time - cacheTTL time.Duration -} - -var log = logging.GetLogger() - -func NewApiKeyManager(dbManager *database.DatabaseManager) *ApiKeyManager { - return &ApiKeyManager{ - dbManager: dbManager, - keyCache: make(map[string]ApiKey), - cacheTTL: 1 * time.Hour, - } -} - -func (m *ApiKeyManager) refreshCache() error { - m.cacheMu.RLock() - if time.Since(m.cacheTime) < m.cacheTTL && len(m.keyCache) > 0 { - m.cacheMu.RUnlock() - return nil - } - m.cacheMu.RUnlock() - - m.cacheMu.Lock() - defer m.cacheMu.Unlock() - - if time.Since(m.cacheTime) < m.cacheTTL && len(m.keyCache) > 0 { - return nil - } - - var apiKeys []database.APIKey - result := m.dbManager.Conn.Find(&apiKeys) - if result.Error != nil { - return result.Error - } - - newCache := make(map[string]ApiKey) - for _, apiKey := range apiKeys { - newCache[apiKey.Key] = ApiKey{ - Name: apiKey.Name, - Key: apiKey.Key, - CreatedAt: apiKey.CreatedAt, - } - } - - m.keyCache = newCache - m.cacheTime = time.Now() - return nil -} - -func (m *ApiKeyManager) VerifyKey(apiKey string) bool { - if err := m.refreshCache(); err != nil { - log.Warning("Failed to refresh API key cache: %v", err) - - var count int64 - result := m.dbManager.Conn.Model(&database.APIKey{}).Where("key = ?", apiKey).Count(&count) - if result.Error != nil { - log.Warning("Failed to verify API key in database: %v", result.Error) - return false - } - return count > 0 - } - - m.cacheMu.RLock() - defer m.cacheMu.RUnlock() - for _, value := range m.keyCache { - if value.Key == apiKey { - return true - } - } - - return false -} - -func generateKey() (string, error) { - bytes := make([]byte, 32) - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return hex.EncodeToString(bytes), nil -} - -func (m *ApiKeyManager) CreateKey(name string) (string, error) { - apiKey, err := generateKey() - if err != nil { - return "", err - } - - newAPIKey := database.APIKey{ - Name: name, - Key: apiKey, - CreatedAt: time.Now(), - } - - result := m.dbManager.Conn.Create(&newAPIKey) - if result.Error != nil { - return "", fmt.Errorf("key with name '%s' already exists", name) - } - - m.cacheMu.Lock() - m.keyCache[apiKey] = ApiKey{ - Name: name, - Key: apiKey, - CreatedAt: newAPIKey.CreatedAt, - } - m.cacheMu.Unlock() - - log.Info("Created new API key with name: %s", name) - - return apiKey, nil -} - -func (m *ApiKeyManager) DeleteKey(keyName string) error { - result := m.dbManager.Conn.Where("name = ?", keyName).Delete(&database.APIKey{}) - if result.Error != nil { - return result.Error - } - - m.cacheMu.Lock() - for key, value := range m.keyCache { - if value.Name == keyName { - delete(m.keyCache, key) - break - } - } - m.cacheMu.Unlock() - - if err := m.refreshCache(); err != nil { - log.Warning("%v", err) - } - - return nil -} - -func (m *ApiKeyManager) GetAllKeys() ([]ApiKey, error) { - if err := m.refreshCache(); err != nil { - return nil, err - } - - m.cacheMu.RLock() - defer m.cacheMu.RUnlock() - - keys := make([]ApiKey, 0, len(m.keyCache)) - for _, apiKey := range m.keyCache { - keyCopy := apiKey - keyCopy.Key = "redacted" - keys = append(keys, keyCopy) - } - - sort.Slice(keys, func(i, j int) bool { - return keys[j].CreatedAt.Before(keys[i].CreatedAt) - }) - - return keys, nil -} diff --git a/backend/api/key/models.go b/backend/api/key/models.go new file mode 100644 index 0000000..dd8571a --- /dev/null +++ b/backend/api/key/models.go @@ -0,0 +1,9 @@ +package key + +import "time" + +type APIKey struct { + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + Key string `json:"key"` +} diff --git a/backend/api/key/repository.go b/backend/api/key/repository.go new file mode 100644 index 0000000..6a972e1 --- /dev/null +++ b/backend/api/key/repository.go @@ -0,0 +1,55 @@ +package key + +import ( + "goaway/backend/database" + + "gorm.io/gorm" +) + +// Repository handles all database operations for API keys +type Repository interface { + Create(apiKey *database.APIKey) error + FindByKey(key string) (*database.APIKey, error) + FindAll() ([]database.APIKey, error) + DeleteByName(name string) error + CountByKey(key string) (int64, error) +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{ + db: db, + } +} + +func (r *repository) Create(apiKey *database.APIKey) error { + return r.db.Create(apiKey).Error +} + +func (r *repository) FindByKey(key string) (*database.APIKey, error) { + var apiKey database.APIKey + err := r.db.Where("key = ?", key).First(&apiKey).Error + if err != nil { + return nil, err + } + return &apiKey, nil +} + +func (r *repository) FindAll() ([]database.APIKey, error) { + var apiKeys []database.APIKey + err := r.db.Find(&apiKeys).Error + return apiKeys, err +} + +func (r *repository) DeleteByName(name string) error { + return r.db.Where("name = ?", name).Delete(&database.APIKey{}).Error +} + +func (r *repository) CountByKey(key string) (int64, error) { + var count int64 + err := r.db.Model(&database.APIKey{}).Where("key = ?", key).Count(&count).Error + return count, err +} diff --git a/backend/api/key/service.go b/backend/api/key/service.go new file mode 100644 index 0000000..244d59c --- /dev/null +++ b/backend/api/key/service.go @@ -0,0 +1,176 @@ +package key + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "goaway/backend/database" + "goaway/backend/logging" + "sort" + "sync" + "time" +) + +// Service handles business logic for API keys +type Service struct { + repository Repository + cacheTime time.Time + cacheTTL time.Duration + + cacheMu sync.RWMutex + keyCache map[string]APIKey +} + +var log = logging.GetLogger() + +func NewService(repo Repository) *Service { + return &Service{ + repository: repo, + keyCache: make(map[string]APIKey), + cacheTTL: 1 * time.Hour, + } +} + +func (s *Service) VerifyKey(apiKey string) bool { + if err := s.refreshCache(); err != nil { + log.Warning("Failed to refresh API key cache: %v", err) + + count, err := s.repository.CountByKey(apiKey) + if err != nil { + log.Warning("Failed to verify API key in database: %v", err) + return false + } + return count > 0 + } + + s.cacheMu.RLock() + defer s.cacheMu.RUnlock() + + for _, value := range s.keyCache { + if value.Key == apiKey { + return true + } + } + + return false +} + +// CreateKey generates and stores a new API key +func (s *Service) CreateKey(name string) (string, error) { + apiKey, err := generateKey() + if err != nil { + return "", err + } + + newAPIKey := database.APIKey{ + Name: name, + Key: apiKey, + CreatedAt: time.Now(), + } + + if err := s.repository.Create(&newAPIKey); err != nil { + return "", fmt.Errorf("key with name '%s' already exists", name) + } + + s.cacheMu.Lock() + s.keyCache[apiKey] = APIKey{ + Name: name, + Key: apiKey, + CreatedAt: newAPIKey.CreatedAt, + } + s.cacheMu.Unlock() + + log.Info("Created new API key with name: %s", name) + + return apiKey, nil +} + +// DeleteKey removes an API key by name +func (s *Service) DeleteKey(keyName string) error { + if err := s.repository.DeleteByName(keyName); err != nil { + return err + } + + s.cacheMu.Lock() + for key, value := range s.keyCache { + if value.Name == keyName { + delete(s.keyCache, key) + break + } + } + s.cacheMu.Unlock() + + if err := s.refreshCache(); err != nil { + log.Warning("%v", err) + } + + return nil +} + +// GetAllKeys returns all API keys with redacted key values +func (s *Service) GetAllKeys() ([]APIKey, error) { + if err := s.refreshCache(); err != nil { + return nil, err + } + + s.cacheMu.RLock() + defer s.cacheMu.RUnlock() + + keys := make([]APIKey, 0, len(s.keyCache)) + for _, apiKey := range s.keyCache { + keyCopy := apiKey + keyCopy.Key = "redacted" + keys = append(keys, keyCopy) + } + + sort.Slice(keys, func(i, j int) bool { + return keys[j].CreatedAt.Before(keys[i].CreatedAt) + }) + + return keys, nil +} + +// refreshCache updates the in-memory cache from the database +func (s *Service) refreshCache() error { + s.cacheMu.RLock() + if time.Since(s.cacheTime) < s.cacheTTL && len(s.keyCache) > 0 { + s.cacheMu.RUnlock() + return nil + } + s.cacheMu.RUnlock() + + s.cacheMu.Lock() + defer s.cacheMu.Unlock() + + // Double-check after acquiring write lock + if time.Since(s.cacheTime) < s.cacheTTL && len(s.keyCache) > 0 { + return nil + } + + apiKeys, err := s.repository.FindAll() + if err != nil { + return err + } + + newCache := make(map[string]APIKey) + for _, apiKey := range apiKeys { + newCache[apiKey.Key] = APIKey{ + Name: apiKey.Name, + Key: apiKey.Key, + CreatedAt: apiKey.CreatedAt, + } + } + + s.keyCache = newCache + s.cacheTime = time.Now() + return nil +} + +// generateKey creates a random hex-encoded API key +func generateKey() (string, error) { + bytes := make([]byte, 32) + if _, err := rand.Read(bytes); err != nil { + return "", err + } + return hex.EncodeToString(bytes), nil +} diff --git a/backend/api/list.go b/backend/api/list.go index c60539a..76e9851 100644 --- a/backend/api/list.go +++ b/backend/api/list.go @@ -48,13 +48,13 @@ func (api *API) updateCustom(c *gin.Context) { return } - err = api.Blacklist.AddCustomDomains(request.Domains) + err = api.BlacklistService.AddCustomDomains(context.Background(), request.Domains) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to update custom blocklist."}) return } - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicList, Message: fmt.Sprintf("Added %d domains to custom blacklist", len(request.Domains)), }) @@ -62,7 +62,7 @@ func (api *API) updateCustom(c *gin.Context) { } func (api *API) getLists(c *gin.Context) { - lists, err := api.Blacklist.GetAllListStatistics() + lists, err := api.BlacklistService.GetAllListStatistics(context.Background()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -85,46 +85,46 @@ func (api *API) addList(c *gin.Context) { return } - err = api.ValidateURLAndName(newList.URL, newList.Name, c) + err = api.validateURLAndName(newList.URL, newList.Name) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if err = api.Blacklist.FetchAndLoadHosts(newList.URL, newList.Name); err != nil { + if err = api.BlacklistService.FetchAndLoadHosts(context.Background(), newList.URL, newList.Name); err != nil { log.Error("Failed to fetch and load hosts: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if _, err := api.Blacklist.PopulateBlocklistCache(); err != nil { + if err := api.BlacklistService.PopulateCache(context.Background()); err != nil { log.Error("Failed to populate blocklist cache: %v", err) c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } - if err := api.Blacklist.AddSource(newList.Name, newList.URL); err != nil { + if err := api.BlacklistService.AddSource(context.Background(), newList.Name, newList.URL); err != nil { log.Error("Failed to add source: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if !newList.Active { - if err := api.Blacklist.ToggleBlocklistStatus(newList.Name); err != nil { + if err := api.BlacklistService.ToggleBlocklistStatus(context.Background(), newList.Name); err != nil { log.Error("Failed to toggle blocklist status: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to toggle status for " + newList.Name}) return } } - _, addedList, err := api.Blacklist.GetListStatistics(newList.Name) + _, addedList, err := api.BlacklistService.GetListStatistics(context.Background(), newList.Name) if err != nil { log.Error("Failed to get list statistics: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get list statistics"}) return } - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicList, Message: fmt.Sprintf("New blacklist with name '%s' was added", addedList.Name), }) @@ -150,31 +150,31 @@ func (api *API) addLists(c *gin.Context) { var addedList []NewList var ignoredList []NewList for _, list := range payload.Lists { - if api.Blacklist.URLExists(list.URL) { + if api.BlacklistService.URLExists(list.URL) { ignoredList = append(ignoredList, list) continue } - if err := api.Blacklist.FetchAndLoadHosts(list.URL, list.Name); err != nil { + if err := api.BlacklistService.FetchAndLoadHosts(context.Background(), list.URL, list.Name); err != nil { log.Error("Failed to fetch and load hosts: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if _, err := api.Blacklist.PopulateBlocklistCache(); err != nil { + if err := api.BlacklistService.PopulateCache(context.Background()); err != nil { log.Error("Failed to populate blocklist cache: %v", err) c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } - if err := api.Blacklist.AddSource(list.Name, list.URL); err != nil { + if err := api.BlacklistService.AddSource(context.Background(), list.Name, list.URL); err != nil { log.Error("Failed to add source: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if !list.Active { - if err := api.Blacklist.ToggleBlocklistStatus(list.Name); err != nil { + if err := api.BlacklistService.ToggleBlocklistStatus(context.Background(), list.Name); err != nil { log.Error("Failed to toggle blocklist status: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to toggle status for " + list.Name}) return @@ -185,7 +185,7 @@ func (api *API) addLists(c *gin.Context) { } if len(addedList) > 0 { - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicList, Message: fmt.Sprintf("Added %d new blacklists in bulk", len(addedList)), }) @@ -197,19 +197,19 @@ func (api *API) addLists(c *gin.Context) { func (api *API) updateListName(c *gin.Context) { oldName := c.Query("old") newName := c.Query("new") - url := c.Query("url") + listURL := c.Query("url") if oldName == "" || newName == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "New and old names are required"}) return } - if !api.Blacklist.NameExists(oldName, url) { + if !api.BlacklistService.NameExists(oldName, listURL) { c.JSON(http.StatusBadRequest, gin.H{"error": "List with that name and url combination does not exist"}) return } - err := api.Blacklist.UpdateSourceName(oldName, newName, url) + err := api.BlacklistService.UpdateSourceName(context.Background(), oldName, newName, listURL) if err != nil { log.Warning("%s", err) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -221,19 +221,19 @@ func (api *API) updateListName(c *gin.Context) { func (api *API) fetchUpdatedList(c *gin.Context) { name := c.Query("name") - url := c.Query("url") + listURL := c.Query("url") - if !api.Blacklist.NameExists(name, url) { + if !api.BlacklistService.NameExists(name, listURL) { c.JSON(http.StatusBadRequest, gin.H{"error": "List with that name and url combination does not exist"}) return } - if name == "" || url == "" { + if name == "" || listURL == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Missing 'name' or 'url' query parameter"}) return } - availableUpdate, err := api.Blacklist.CheckIfUpdateAvailable(url, name) + availableUpdate, err := api.BlacklistService.CheckIfUpdateAvailable(context.Background(), listURL, name) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return @@ -249,33 +249,33 @@ func (api *API) fetchUpdatedList(c *gin.Context) { func (api *API) runUpdateList(c *gin.Context) { name := c.Query("name") - url := c.Query("url") + listURL := c.Query("url") - if !api.Blacklist.NameExists(name, url) { + if !api.BlacklistService.NameExists(name, listURL) { c.JSON(http.StatusBadRequest, gin.H{"error": "List does not exist"}) return } - if name == "" || url == "" { + if name == "" || listURL == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Missing 'name' or 'url' query parameter"}) return } - err := api.Blacklist.RemoveSourceAndDomains(name, url) + err := api.BlacklistService.RemoveSourceAndDomains(context.Background(), name, listURL) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - err = api.Blacklist.FetchAndLoadHosts(url, name) + err = api.BlacklistService.FetchAndLoadHosts(context.Background(), listURL, name) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } go func() { - _ = api.DNSServer.Alerts.SendToAll(context.Background(), alert.Message{ + _ = api.DNSServer.AlertService.SendToAll(context.Background(), alert.Message{ Title: "System", - Content: fmt.Sprintf("List '%s' with url '%s' was updated! ", name, url), + Content: fmt.Sprintf("List '%s' with url '%s' was updated! ", name, listURL), Severity: SeveritySuccess, }) }() @@ -291,7 +291,7 @@ func (api *API) toggleBlocklist(c *gin.Context) { return } - err := api.Blacklist.ToggleBlocklistStatus(blocklist) + err := api.BlacklistService.ToggleBlocklistStatus(context.Background(), blocklist) if err != nil { log.Error("Failed to toggle blocklist status: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Failed to toggle status for %s", blocklist)}) @@ -309,9 +309,9 @@ func (api *API) handleUpdateBlockStatus(c *gin.Context) { return } - action := map[string]func(string) error{ - "true": api.Blacklist.AddBlacklistedDomain, - "false": api.Blacklist.RemoveDomain, + action := map[string]func(context.Context, string) error{ + "true": api.BlacklistService.AddBlacklistedDomain, + "false": api.BlacklistService.RemoveDomain, }[blocked] if action == nil { @@ -319,7 +319,7 @@ func (api *API) handleUpdateBlockStatus(c *gin.Context) { return } - if err := action(domain); err != nil { + if err := action(context.Background(), domain); err != nil { c.JSON(http.StatusOK, gin.H{"message": err.Error()}) return } @@ -334,42 +334,42 @@ func (api *API) handleUpdateBlockStatus(c *gin.Context) { func (api *API) removeList(c *gin.Context) { name := c.Query("name") - url := c.Query("url") + listURL := c.Query("url") - if !api.Blacklist.NameExists(name, url) { + if !api.BlacklistService.NameExists(name, listURL) { c.JSON(http.StatusBadRequest, gin.H{"error": "List does not exist"}) return } - err := api.Blacklist.RemoveSourceAndDomains(name, url) + err := api.BlacklistService.RemoveSourceAndDomains(context.Background(), name, listURL) if err != nil { log.Error("%v", err.Error()) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } - if removed := api.Blacklist.RemoveSourceByNameAndURL(name, url); !removed { - log.Error("Failed to remove source with name '%s' and url '%s'", name, url) + if removed := api.BlacklistService.RemoveSourceByNameAndURL(name, listURL); !removed { + log.Error("Failed to remove source with name '%s' and url '%s'", name, listURL) c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to remove the list"}) return } - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicList, Message: fmt.Sprintf("Blacklist with name '%s' was deleted", name), }) c.Status(http.StatusOK) } -func (api *API) ValidateURLAndName(URL, name string, c *gin.Context) error { - if name == "" || URL == "" { +func (api *API) validateURLAndName(listURL, name string) error { + if name == "" || listURL == "" { return fmt.Errorf("name and URL are required") } - if _, err := url.ParseRequestURI(URL); err != nil { + if _, err := url.ParseRequestURI(listURL); err != nil { return fmt.Errorf("invalid URL format") } - if api.Blacklist.URLExists(URL) { + if api.BlacklistService.URLExists(listURL) { return fmt.Errorf("list with the same URL already exists") } diff --git a/backend/api/middleware.go b/backend/api/middleware.go index 1ca54a5..20b8b1e 100644 --- a/backend/api/middleware.go +++ b/backend/api/middleware.go @@ -11,8 +11,8 @@ import ( ) const ( - TokenDuration = 5 * time.Minute - Secret = "kMNSRwKip7Yet4rb2z8" + tokenDuration = 5 * time.Minute + secret = "kMNSRwKip7Yet4rb2z8" ) func (api *API) authMiddleware() gin.HandlerFunc { @@ -23,7 +23,7 @@ func (api *API) authMiddleware() gin.HandlerFunc { } if apiKey := c.GetHeader("api-key"); apiKey != "" { - if api.KeyManager.VerifyKey(apiKey) { + if api.KeyService.VerifyKey(apiKey) { c.Next() return } @@ -62,7 +62,7 @@ func (api *API) authMiddleware() gin.HandlerFunc { return } - halfDurationSeconds := int64(TokenDuration.Seconds() / 2) + halfDurationSeconds := int64(tokenDuration.Seconds() / 2) timeUntilExpiration := expiration - now if timeUntilExpiration <= halfDurationSeconds { @@ -85,7 +85,7 @@ func parseToken(tokenString string) (jwt.MapClaims, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } - return []byte(Secret), nil + return []byte(secret), nil }) if err != nil || !token.Valid { return nil, err @@ -103,11 +103,11 @@ func generateToken(username string) (string, error) { now := time.Now() claims := jwt.MapClaims{ "username": username, - "exp": now.Add(TokenDuration).Unix(), + "exp": now.Add(tokenDuration).Unix(), "iat": now.Unix(), } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString([]byte(Secret)) + return token.SignedString([]byte(secret)) } func setAuthCookie(w http.ResponseWriter, token string) { @@ -118,7 +118,7 @@ func setAuthCookie(w http.ResponseWriter, token string) { HttpOnly: true, Secure: false, SameSite: http.SameSiteLaxMode, - Expires: time.Now().Add(TokenDuration), - MaxAge: int(TokenDuration.Seconds()), + Expires: time.Now().Add(tokenDuration), + MaxAge: int(tokenDuration.Seconds()), }) } diff --git a/backend/api/models/models.go b/backend/api/models/models.go index 355e7ac..5653087 100644 --- a/backend/api/models/models.go +++ b/backend/api/models/models.go @@ -1,11 +1,11 @@ package models type QueryParams struct { - Page int - PageSize int - Offset int Search string Column string Direction string FilterClient string + Page int + PageSize int + Offset int } diff --git a/backend/api/notification.go b/backend/api/notification.go index 5d7a18b..391497b 100644 --- a/backend/api/notification.go +++ b/backend/api/notification.go @@ -1,9 +1,7 @@ package api import ( - "encoding/json" "fmt" - "io" "net/http" "github.com/gin-gonic/gin" @@ -16,7 +14,7 @@ func (api *API) registerNotificationRoutes() { } func (api *API) fetchNotifications(c *gin.Context) { - notifications, err := api.Notifications.ReadNotifications() + notifications, err := api.NotificationService.GetNotifications() if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) } @@ -28,21 +26,14 @@ func (api *API) markNotificationAsRead(c *gin.Context) { NotificationIDs []int `json:"notificationIds"` } - notificationsRead, err := io.ReadAll(c.Request.Body) - if err != nil { - log.Error("Failed to read request body: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) - return - } - var request NotificationsRead - if err := json.Unmarshal(notificationsRead, &request); err != nil { - log.Error("Failed to parse JSON: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON format"}) + err := c.BindJSON(&request) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Unable to parse request"}) return } - err = api.Notifications.MarkNotificationsAsRead(request.NotificationIDs) + err = api.NotificationService.MarkNotificationsAsRead(request.NotificationIDs) if err != nil { log.Warning("Unable to mark notifications as read %v", err) c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Unable to mark notifications as read %v", err.Error())}) diff --git a/backend/api/ratelimit/ratelimit.go b/backend/api/ratelimit/ratelimit.go index d39bda7..3c8ecd3 100644 --- a/backend/api/ratelimit/ratelimit.go +++ b/backend/api/ratelimit/ratelimit.go @@ -15,12 +15,12 @@ type RateLimiterConfig struct { } type RateLimiter struct { - Config RateLimiterConfig mutex *sync.RWMutex attempts map[string][]time.Time + Config RateLimiterConfig } -func NewRateLimiter(enabled bool, maxTries int, window int) *RateLimiter { +func NewRateLimiter(enabled bool, maxTries, window int) *RateLimiter { config := RateLimiterConfig{ Enabled: enabled, MaxTries: maxTries, diff --git a/backend/api/resolution.go b/backend/api/resolution.go index 8f828ed..9390975 100644 --- a/backend/api/resolution.go +++ b/backend/api/resolution.go @@ -3,7 +3,6 @@ package api import ( "fmt" "goaway/backend/audit" - "goaway/backend/dns/database" "net/http" "github.com/gin-gonic/gin" @@ -31,14 +30,14 @@ func (api *API) createResolution(c *gin.Context) { return } - err := database.CreateNewResolution(api.DBManager.Conn, newResolution.IP, newResolution.Domain) + err := api.ResolutionService.CreateResolution(newResolution.IP, newResolution.Domain) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } api.DNSServer.RemoveCachedDomain(newResolution.Domain) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicResolution, Message: fmt.Sprintf("Added new resolution '%s'", newResolution.Domain), }) @@ -46,7 +45,7 @@ func (api *API) createResolution(c *gin.Context) { } func (api *API) getResolutions(c *gin.Context) { - resolutions, err := database.FetchResolutions(api.DBManager.Conn) + resolutions, err := api.ResolutionService.GetResolutions() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -59,7 +58,7 @@ func (api *API) deleteResolution(c *gin.Context) { domain := c.Query("domain") ip := c.Query("ip") - rowsAffected, err := database.DeleteResolution(api.DBManager.Conn, ip, domain) + rowsAffected, err := api.ResolutionService.DeleteResolution(ip, domain) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), @@ -74,7 +73,7 @@ func (api *API) deleteResolution(c *gin.Context) { api.DNSServer.RemoveCachedDomain(domain) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicResolution, Message: fmt.Sprintf("Removed resolution '%s'", domain), }) diff --git a/backend/api/server.go b/backend/api/server.go index 82847e7..5636783 100644 --- a/backend/api/server.go +++ b/backend/api/server.go @@ -1,8 +1,8 @@ package api import ( + "context" "fmt" - "goaway/backend/dns/database" "goaway/backend/dns/server" "goaway/backend/updater" "net/http" @@ -10,6 +10,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" @@ -18,7 +19,7 @@ import ( ) func (api *API) registerServerRoutes() { - api.setupWSLiveCommunication(api.PrefetchedDomainsManager.DNS) + api.setupWSLiveCommunication(api.DNS) api.router.GET("/api/server", api.handleServer) api.router.GET("/api/dnsMetrics", api.handleMetrics) @@ -47,7 +48,7 @@ func (api *API) handleServer(c *gin.Context) { } c.JSON(http.StatusOK, gin.H{ - "portDNS": api.Config.DNS.Port, + "portDNS": api.Config.DNS.Ports.TCPUDP, "portWebsite": api.DNSPort, "totalMem": float64(vMem.Total) / 1024 / 1024 / 1024, "usedMem": float64(vMem.Used) / 1024 / 1024 / 1024, @@ -56,7 +57,7 @@ func (api *API) handleServer(c *gin.Context) { "cpuTemp": temp, "dbSize": dbSize, "version": api.Version, - "inAppUpdate": api.Config.InAppUpdate, + "inAppUpdate": api.Config.Misc.InAppUpdate, "commit": api.Commit, "date": api.Date, }) @@ -110,7 +111,7 @@ func getDBSizeMB() (float64, error) { } func (api *API) handleMetrics(c *gin.Context) { - allowed, blocked, err := api.Blacklist.GetAllowedAndBlocked() + allowed, blocked, err := api.BlacklistService.GetAllowedAndBlocked(context.Background()) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return @@ -122,14 +123,14 @@ func (api *API) handleMetrics(c *gin.Context) { percentageBlocked = (float64(blocked) / float64(total)) * 100 } - domainsLength, _ := api.Blacklist.CountDomains() + domainsLength, _ := api.BlacklistService.CountDomains(context.Background()) c.JSON(http.StatusOK, gin.H{ "allowed": allowed, "blocked": blocked, "total": total, "percentageBlocked": percentageBlocked, "domainBlockLen": domainsLength, - "clients": database.GetDistinctRequestIP(api.DBManager.Conn), + "clients": api.RequestService.GetDistinctRequestIP(), }) } @@ -169,16 +170,47 @@ func (api *API) setupWSLiveCommunication(dnsServer *server.DNSServer) { var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, + CheckOrigin: func(_ *http.Request) bool { + return true + }, } conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { return } + + _ = conn.SetReadDeadline(time.Now().Add(60 * time.Second)) + conn.SetPongHandler(func(string) error { + _ = conn.SetReadDeadline(time.Now().Add(60 * time.Second)) + return nil + }) + api.WSCommunication = conn if dnsServer != nil { dnsServer.WSCommunication = conn } + + go func() { + defer func() { + _ = conn.Close() + if dnsServer != nil { + dnsServer.WSCommunicationLock.Lock() + dnsServer.WSCommunication = nil + dnsServer.WSCommunicationLock.Unlock() + } + }() + + for { + _, _, err := conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Warning("Websocket closed unexpectedly: %v", err) + } + break + } + } + }() }) } diff --git a/backend/api/settings.go b/backend/api/settings.go index 4ecc645..893ac44 100644 --- a/backend/api/settings.go +++ b/backend/api/settings.go @@ -36,11 +36,11 @@ func (api *API) updateSettings(c *gin.Context) { return } - api.Config.UpdateSettings(updatedSettings) - settingsJson, _ := json.MarshalIndent(updatedSettings, "", " ") - log.Debug("%s", string(settingsJson)) + api.Config.Update(updatedSettings) + settingsJSON, _ := json.MarshalIndent(updatedSettings, "", " ") + log.Debug("%s", string(settingsJSON)) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicSettings, Message: "Settings was updated", }) @@ -65,7 +65,7 @@ func (api *API) exportDatabase(c *gin.Context) { _ = os.Remove(tempExport) // Create a new connection to a temp file and vacuum into it - if err := api.DBManager.Conn.Exec(fmt.Sprintf("VACUUM INTO '%s';", tempExport)).Error; err != nil { + if err := api.DBConn.Exec(fmt.Sprintf("VACUUM INTO '%s';", tempExport)).Error; err != nil { log.Error("Failed to write WAL to temp export: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to prepare database for export"}) return @@ -116,7 +116,7 @@ func (api *API) exportDatabase(c *gin.Context) { return n > 0 }) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicDatabase, Message: "Database was exported", }) @@ -125,7 +125,7 @@ func (api *API) exportDatabase(c *gin.Context) { func validateSQLiteFile(filePath string) error { file, err := os.Open(filePath) if err != nil { - return fmt.Errorf("cannot open file: %v", err) + return fmt.Errorf("cannot open file: %w", err) } go func() { _ = file.Close() @@ -133,7 +133,7 @@ func validateSQLiteFile(filePath string) error { stat, err := file.Stat() if err != nil { - return fmt.Errorf("cannot stat file: %v", err) + return fmt.Errorf("cannot stat file: %w", err) } if stat.Size() < 50 { @@ -143,7 +143,7 @@ func validateSQLiteFile(filePath string) error { header := make([]byte, 16) _, err = file.Read(header) if err != nil { - return fmt.Errorf("cannot read file header: %v", err) + return fmt.Errorf("cannot read file header: %w", err) } expectedHeader := "SQLite format 3\x00" @@ -184,7 +184,7 @@ func (api *API) importDatabase(c *gin.Context) { } _, err = io.Copy(tempFile, file) - defer func(tempfile *os.File) { + defer func(tempFile *os.File) { _ = tempFile.Close() }(tempFile) @@ -229,7 +229,7 @@ func (api *API) importDatabase(c *gin.Context) { return } - sqlDB, err := api.DBManager.Conn.DB() + sqlDB, err := api.DBConn.DB() if err != nil { log.Error("Failed to get underlying sql.DB: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to close current database"}) @@ -260,15 +260,15 @@ func (api *API) importDatabase(c *gin.Context) { if err != nil { log.Error("Failed to open imported database with GORM: %v", err) _ = copyFile(backupPath, currentDBPath) - api.DBManager.Conn, _ = gorm.Open(sqlite.Open(currentDBPath), &gorm.Config{}) + api.DBConn, _ = gorm.Open(sqlite.Open(currentDBPath), &gorm.Config{}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open imported database, restored from backup"}) return } - api.DBManager.Conn = newDB + *api.DBConn = *newDB log.Info("Database imported successfully from %s", header.Filename) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicDatabase, Message: "Database was imported", }) diff --git a/backend/api/upstream.go b/backend/api/upstream.go index a23c15f..8996bbe 100644 --- a/backend/api/upstream.go +++ b/backend/api/upstream.go @@ -18,14 +18,14 @@ import ( probing "github.com/prometheus-community/pro-bing" ) -type PingResult struct { - Duration time.Duration +type pingResult struct { Error error Method string + Duration time.Duration Successful bool } -func (pr PingResult) String() string { +func (pr pingResult) String() string { if !pr.Successful { return fmt.Sprintf("Failed (%s)", pr.Method) } @@ -68,16 +68,16 @@ func (api *API) createUpstream(c *gin.Context) { upstream += ":53" } - if slices.Contains(api.Config.DNS.UpstreamDNS, upstream) { + if slices.Contains(api.Config.DNS.Upstream.Fallback, upstream) { c.JSON(http.StatusBadRequest, gin.H{"error": "Upstream already exists"}) return } - api.Config.DNS.UpstreamDNS = append(api.Config.DNS.UpstreamDNS, upstream) + api.Config.DNS.Upstream.Fallback = append(api.Config.DNS.Upstream.Fallback, upstream) api.Config.Save() log.Info("Added %s as a new upstream", upstream) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicUpstream, Message: fmt.Sprintf("Added a new upstream '%s'", request.Upstream), }) @@ -87,9 +87,9 @@ func (api *API) createUpstream(c *gin.Context) { func (api *API) getUpstreams(c *gin.Context) { var ( - upstreams = api.Config.DNS.UpstreamDNS + upstreams = api.Config.DNS.Upstream.Fallback results = make([]map[string]any, len(upstreams)) - preferredUpstream = api.Config.DNS.PreferredUpstream + preferredUpstream = api.Config.DNS.Upstream.Preferred wg sync.WaitGroup ) wg.Add(len(upstreams)) @@ -158,7 +158,7 @@ func resolveHostname(host string) string { return "No IP found" } -func measureDNSPing(upstream string) PingResult { +func measureDNSPing(upstream string) pingResult { var ( testDomains = []string{"google.com", "cloudflare.com", "quad9.net"} client = &dns.Client{ @@ -193,7 +193,7 @@ func measureDNSPing(upstream string) PingResult { } if successCount == 0 { - return PingResult{ + return pingResult{ Duration: 0, Error: lastError, Method: "dns", @@ -202,7 +202,7 @@ func measureDNSPing(upstream string) PingResult { } avgDuration := totalDuration / time.Duration(successCount) - return PingResult{ + return pingResult{ Duration: avgDuration, Error: nil, Method: "dns", @@ -210,7 +210,7 @@ func measureDNSPing(upstream string) PingResult { } } -func measureICMPPing(host string) PingResult { +func measureICMPPing(host string) pingResult { icmpResult := tryICMPPing(host) if icmpResult.Successful { return icmpResult @@ -220,10 +220,10 @@ func measureICMPPing(host string) PingResult { return tcpResult } -func tryICMPPing(host string) PingResult { +func tryICMPPing(host string) pingResult { pinger, err := probing.NewPinger(host) if err != nil { - return PingResult{ + return pingResult{ Duration: 0, Error: err, Method: "icmp", @@ -237,7 +237,7 @@ func tryICMPPing(host string) PingResult { err = pinger.Run() if err != nil { - return PingResult{ + return pingResult{ Duration: 0, Error: err, Method: "icmp", @@ -247,7 +247,7 @@ func tryICMPPing(host string) PingResult { stats := pinger.Statistics() if stats.PacketsRecv == 0 { - return PingResult{ + return pingResult{ Duration: 0, Error: fmt.Errorf("no packets received"), Method: "icmp", @@ -255,7 +255,7 @@ func tryICMPPing(host string) PingResult { } } - return PingResult{ + return pingResult{ Duration: stats.AvgRtt, Error: nil, Method: "icmp", @@ -263,12 +263,12 @@ func tryICMPPing(host string) PingResult { } } -func tryTCPPing(host string) PingResult { +func tryTCPPing(host string) pingResult { start := time.Now() conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, "53"), 2*time.Second) if err != nil { - return PingResult{ + return pingResult{ Duration: 0, Error: err, Method: "tcp", @@ -281,7 +281,7 @@ func tryTCPPing(host string) PingResult { _ = conn.Close() }() - return PingResult{ + return pingResult{ Duration: duration, Error: nil, Method: "tcp", @@ -333,22 +333,22 @@ func (api *API) updatePreferredUpstream(c *gin.Context) { return } - if !slices.Contains(api.Config.DNS.UpstreamDNS, request.Upstream) { + if !slices.Contains(api.Config.DNS.Upstream.Fallback, request.Upstream) { c.JSON(http.StatusNotFound, gin.H{"error": "Upstream not found"}) return } - if api.Config.DNS.PreferredUpstream == request.Upstream { + if api.Config.DNS.Upstream.Preferred == request.Upstream { c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Preferred upstream already set to %s", request.Upstream)}) return } - api.Config.DNS.PreferredUpstream = request.Upstream + api.Config.DNS.Upstream.Preferred = request.Upstream message := fmt.Sprintf("Preferred upstream set to %s", request.Upstream) log.Info("%s", message) api.Config.Save() - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicUpstream, Message: fmt.Sprintf("New preferred upstream '%s'", request.Upstream), }) @@ -364,23 +364,23 @@ func (api *API) deleteUpstream(c *gin.Context) { return } - if !slices.Contains(api.Config.DNS.UpstreamDNS, upstreamToDelete) { + if !slices.Contains(api.Config.DNS.Upstream.Fallback, upstreamToDelete) { c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("Upstream %s not found", upstreamToDelete)}) return } var updatedUpstreams []string - for _, upstream := range api.Config.DNS.UpstreamDNS { + for _, upstream := range api.Config.DNS.Upstream.Fallback { if upstream != upstreamToDelete { updatedUpstreams = append(updatedUpstreams, upstream) } } - api.Config.DNS.UpstreamDNS = updatedUpstreams + api.Config.DNS.Upstream.Fallback = updatedUpstreams api.Config.Save() log.Info("Removed upstream: %s", upstreamToDelete) - api.DNSServer.Audits.CreateAudit(&audit.Entry{ + api.DNSServer.AuditService.CreateAudit(&audit.Entry{ Topic: audit.TopicUpstream, Message: fmt.Sprintf("Removed upstream '%s'", upstreamToDelete), }) diff --git a/backend/api/user/models.go b/backend/api/user/models.go deleted file mode 100644 index 2488c88..0000000 --- a/backend/api/user/models.go +++ /dev/null @@ -1,6 +0,0 @@ -package user - -type User struct { - Username string - Password string -} diff --git a/backend/api/user/user.go b/backend/api/user/user.go deleted file mode 100644 index 891c654..0000000 --- a/backend/api/user/user.go +++ /dev/null @@ -1,119 +0,0 @@ -package user - -import ( - "context" - "errors" - "goaway/backend/dns/database" - "goaway/backend/logging" - "strings" - - "golang.org/x/crypto/bcrypt" - "gorm.io/gorm" -) - -var log = logging.GetLogger() - -type Credentials struct { - Username string `json:"username" binding:"required"` - Password string `json:"password" binding:"required"` -} - -func (user *User) Create(db *gorm.DB) error { - log.Info("Creating a new user with name '%s'", user.Username) - - hashedPassword, err := hashPassword(user.Password) - if err != nil { - log.Error("Failed to hash password: %v", err) - return err - } - - user.Password = hashedPassword - - result := db.Create(user) - if result.Error != nil { - log.Error("Failed to create user: %v", result.Error) - return result.Error - } - - if result.RowsAffected == 0 { - log.Error("User creation failed: no rows affected") - return errors.New("user creation failed: no rows affected") - } - - log.Debug("User created successfully") - return nil -} - -func hashPassword(password string) (string, error) { - hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - return string(hashed), err -} - -func (user *User) Exists(db *gorm.DB) bool { - query := database.User{} - db.Where("username = ?", user.Username).Find(&query) - return query.Username != "" -} - -func (user *User) Authenticate(db *gorm.DB) bool { - var dbUser User - if err := db.Where("username = ?", user.Username).First(&dbUser).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - log.Error("User not found: %s", user.Username) - return false - } - log.Error("Query error: %v", err) - return false - } - - if err := bcrypt.CompareHashAndPassword([]byte(dbUser.Password), []byte(user.Password)); err != nil { - return false - } - return true -} - -func (user *User) UpdatePassword(db *gorm.DB) error { - hashedPassword, err := hashPassword(user.Password) - if err != nil { - log.Error("Failed to hash new password: %v", err) - return err - } - - affected, err := gorm.G[database.User](db).Where("username = ?", user.Username).Update(context.Background(), "password", hashedPassword) - if err != nil { - log.Error("Failed to update password: %v", err) - return err - } - - if affected == 0 { - log.Error("Password update failed: no rows affected") - return errors.New("password update failed: no rows affected") - } - - log.Debug("Password updated successfully") - return nil -} - -func (c *Credentials) Validate() error { - c.Username = strings.TrimSpace(c.Username) - c.Password = strings.TrimSpace(c.Password) - - if c.Username == "" || c.Password == "" { - return errors.New("username and password cannot be empty") - } - - if len(c.Username) > 60 { - return errors.New("username too long") - } - if len(c.Password) > 120 { - return errors.New("password too long") - } - - for _, r := range c.Username { - if r < 32 || r == 127 { - return errors.New("username contains invalid characters") - } - } - - return nil -} diff --git a/backend/api/whitelist.go b/backend/api/whitelist.go index b2cae98..55c7c09 100644 --- a/backend/api/whitelist.go +++ b/backend/api/whitelist.go @@ -2,7 +2,7 @@ package api import ( "encoding/json" - "goaway/backend/dns/database" + "goaway/backend/database" "io" "net/http" @@ -34,7 +34,7 @@ func (api *API) addWhitelisted(c *gin.Context) { return } - err = api.Whitelist.AddDomain(newDomain.Domain) + err = api.WhitelistService.AddDomain(newDomain.Domain) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return @@ -46,7 +46,7 @@ func (api *API) addWhitelisted(c *gin.Context) { func (api *API) getWhitelistedDomains(c *gin.Context) { var domains []string - err := api.DBManager.Conn.Model(&database.Whitelist{}).Pluck("domain", &domains).Error + err := api.DBConn.Model(&database.Whitelist{}).Pluck("domain", &domains).Error if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": "Failed to retrieve whitelisted domains"}) return @@ -58,7 +58,7 @@ func (api *API) getWhitelistedDomains(c *gin.Context) { func (api *API) deleteWhitelistedDomain(c *gin.Context) { newDomain := c.Query("domain") - err := api.Whitelist.RemoveDomain(newDomain) + err := api.WhitelistService.RemoveDomain(newDomain) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return diff --git a/backend/app.go b/backend/app.go new file mode 100644 index 0000000..f18d43e --- /dev/null +++ b/backend/app.go @@ -0,0 +1,111 @@ +package app + +import ( + "context" + "embed" + "fmt" + "goaway/backend/alert" + "goaway/backend/api/key" + "goaway/backend/audit" + "goaway/backend/blacklist" + "goaway/backend/lifecycle" + "goaway/backend/logging" + "goaway/backend/mac" + "goaway/backend/notification" + "goaway/backend/prefetch" + "goaway/backend/request" + "goaway/backend/resolution" + "goaway/backend/services" + "goaway/backend/settings" + "goaway/backend/setup" + "goaway/backend/user" + "goaway/backend/whitelist" +) + +var log = logging.GetLogger() + +type Application struct { + config *settings.Config + context *services.AppContext + services *services.ServiceRegistry + lifecycle *lifecycle.Manager + content embed.FS + version string + commit string + date string +} + +func New(setFlags *setup.SetFlags, version, commit, date string, content embed.FS) *Application { + config := setup.InitializeSettings(setFlags) + + return &Application{ + config: config, + version: version, + commit: commit, + date: date, + content: content, + } +} + +func (a *Application) Start() error { + + ctx, err := services.NewAppContext(a.config) + if err != nil { + return fmt.Errorf("failed to initialize application context: %w", err) + } + a.context = ctx + + dbConn := a.context.DBConn + alertService := alert.NewService(alert.NewRepository(dbConn)) + auditService := audit.NewService(audit.NewRepository(dbConn)) + blacklistService := blacklist.NewService(blacklist.NewRepository(dbConn)) + keyService := key.NewService(key.NewRepository(dbConn)) + macService := mac.NewService(mac.NewRepository(dbConn)) + notificationService := notification.NewService(notification.NewRepository(dbConn)) + prefetchService := prefetch.NewService(prefetch.NewRepository(dbConn), a.context.DNSServer) + requestService := request.NewService(request.NewRepository(dbConn)) + resolutionService := resolution.NewService(resolution.NewRepository(dbConn)) + userService := user.NewService(user.NewRepository(dbConn)) + whitelistService := whitelist.NewService(whitelist.NewRepository(dbConn)) + + a.context.DNSServer.AlertService = alertService + a.context.DNSServer.AuditService = auditService + a.context.DNSServer.BlacklistService = blacklistService + a.context.DNSServer.MACService = macService + a.context.DNSServer.NotificationService = notificationService + a.context.DNSServer.RequestService = requestService + a.context.DNSServer.UserService = userService + a.context.DNSServer.ResolutionService = resolutionService + a.context.DNSServer.WhitelistService = whitelistService + + a.displayStartupInfo() + + a.services = services.NewServiceRegistry(a.context, a.version, a.commit, a.date, a.content) + a.services.ResolutionService = resolutionService + a.services.BlacklistService = blacklistService + a.services.NotificationService = notificationService + a.services.PrefetchService = prefetchService + a.services.RequestService = requestService + a.services.UserService = userService + a.services.KeyService = keyService + a.services.WhitelistService = whitelistService + a.lifecycle = lifecycle.NewManager(a.services) + + runServices := a.lifecycle.Run() + return runServices +} + +func (a *Application) displayStartupInfo() { + domains, err := a.context.DNSServer.BlacklistService.CountDomains(context.Background()) + if err != nil { + log.Warning("Failed to count blacklist domains: %v", err) + } + + currentVersion := setup.GetVersionOrDefault(a.version) + ASCIIArt( + a.config, + domains, + currentVersion.Original(), + a.config.API.Authentication, + ) +} diff --git a/backend/asciiart/asciiart.go b/backend/asciiart.go similarity index 51% rename from backend/asciiart/asciiart.go rename to backend/asciiart.go index 07e00ed..5c549f6 100644 --- a/backend/asciiart/asciiart.go +++ b/backend/asciiart.go @@ -1,7 +1,8 @@ -package asciiart +package app import ( "fmt" + "goaway/backend/logging" "goaway/backend/settings" ) @@ -15,9 +16,19 @@ const ( Magenta = "\033[35m" ) -func AsciiArt(config *settings.Config, blockedDomains int, version string, disableAuth bool, ansi bool) { +var art = ` + __ _ ___ __ ___ ____ _ _ _ DNS port: %s + / _' |/ _ \ / _' \ \ /\ / / _' | | | | Web port: %s +| (_| | (_) | (_| |\ V V / (_| | |_| | Upstream: %s + \__, |\___/ \__,_| \_/\_/ \__,_|\__, | Authentication: %s + __/ | __/ | Cache TTL: %s + |___/ %s |___/ Blocked Domains: %s +` + +func ASCIIArt(config *settings.Config, blockedDomains int, version string, disableAuth bool) { const versionSpace = 7 + var ansi = logging.GetLogger().Ansi colorize := func(color, text string) string { if !ansi { return text @@ -28,20 +39,21 @@ func AsciiArt(config *settings.Config, blockedDomains int, version string, disab versionFormatted := fmt.Sprintf("%-*s%s%-*s", (versionSpace-len(version))/2, "", colorize(Cyan, version), (versionSpace-len(version)+1)/2, "") - portFormatted := colorize(Green, fmt.Sprintf("%d", config.DNS.Port)) + portFormatted := colorize(Green, fmt.Sprintf("%d", config.DNS.Ports.TCPUDP)) adminPanelFormatted := colorize(Red, fmt.Sprintf("%d", config.API.Port)) - upstreamFormatted := colorize(Cyan, config.DNS.PreferredUpstream) + upstreamFormatted := colorize(Cyan, config.DNS.Upstream.Preferred) authFormatted := colorize(Yellow, fmt.Sprintf("%v", disableAuth)) cacheTTLFormatted := colorize(Blue, fmt.Sprintf("%d", config.DNS.CacheTTL)) blockedDomainsFormatted := colorize(Magenta, fmt.Sprintf("%d", blockedDomains)) - fmt.Printf(` - __ _ ___ __ ___ ____ _ _ _ DNS port: %s - / _' |/ _ \ / _' \ \ /\ / / _' | | | | Web port: %s - | (_| | (_) | (_| |\ V V / (_| | |_| | Upstream: %s - \__, |\___/ \__,_| \_/\_/ \__,_|\__, | Authentication: %s - __/ | __/ | Cache TTL: %s - |___/ %s |___/ Blocked Domains: %s - -`, portFormatted, adminPanelFormatted, upstreamFormatted, authFormatted, cacheTTLFormatted, versionFormatted, blockedDomainsFormatted) + fmt.Printf(art, + portFormatted, + adminPanelFormatted, + upstreamFormatted, + authFormatted, + cacheTTLFormatted, + versionFormatted, + blockedDomainsFormatted, + ) + fmt.Println() } diff --git a/backend/audit/audit.go b/backend/audit/audit.go index b43df59..60390bb 100644 --- a/backend/audit/audit.go +++ b/backend/audit/audit.go @@ -1,64 +1,40 @@ package audit import ( - "goaway/backend/dns/database" - "goaway/backend/logging" + "goaway/backend/database" "time" + + "gorm.io/gorm" ) type Manager struct { - dbManager *database.DatabaseManager + dbConn *gorm.DB } -type Topic string - -const ( - TopicServer Topic = "server" - TopicDNS Topic = "dns" - TopicAPI Topic = "api" - TopicResolution Topic = "resolution" - TopicPrefetch Topic = "prefetch" - TopicUpstream Topic = "upstream" - TopicUser Topic = "user" - TopicList Topic = "list" - TopicLogs Topic = "logs" - TopicSettings Topic = "settings" - TopicDatabase Topic = "database" -) - -type Entry struct { - Id int `json:"id"` - Topic Topic `json:"topic"` - Message string `json:"message"` - CreatedAt time.Time `json:"createdAt"` +func NewAuditManager(dbconn *gorm.DB) *Manager { + return &Manager{dbConn: dbconn} } -var logger = logging.GetLogger() - -func NewAuditManager(dbManager *database.DatabaseManager) *Manager { - return &Manager{dbManager: dbManager} -} - -func (nm *Manager) CreateAudit(newAudit *Entry) { +func (m *Manager) CreateAudit(newAudit *Entry) { audit := database.Audit{ Topic: string(newAudit.Topic), Message: newAudit.Message, CreatedAt: time.Now(), } - result := nm.dbManager.Conn.Create(&audit) + result := m.dbConn.Create(&audit) if result.Error != nil { - logger.Warning("Unable to create new audit, error: %v", result.Error) + log.Warning("Unable to create new audit, error: %v", result.Error) return } - logger.Debug("Created new audit, %+v", newAudit) + log.Debug("Created new audit, %+v", newAudit) } -func (nm *Manager) ReadAudits() ([]Entry, error) { +func (m *Manager) ReadAudits() ([]Entry, error) { var audits []database.Audit - result := nm.dbManager.Conn.Order("created_at DESC").Find(&audits) + result := m.dbConn.Order("created_at DESC").Find(&audits) if result.Error != nil { return nil, result.Error } @@ -66,7 +42,7 @@ func (nm *Manager) ReadAudits() ([]Entry, error) { entries := make([]Entry, len(audits)) for i, audit := range audits { entries[i] = Entry{ - Id: int(audit.ID), + ID: audit.ID, Topic: Topic(audit.Topic), Message: audit.Message, CreatedAt: audit.CreatedAt, diff --git a/backend/audit/repository.go b/backend/audit/repository.go new file mode 100644 index 0000000..3e7f3b9 --- /dev/null +++ b/backend/audit/repository.go @@ -0,0 +1,51 @@ +package audit + +import ( + "goaway/backend/database" + + "gorm.io/gorm" +) + +type Repository interface { + CreateAudit(audit *Entry) error + ReadAudits() ([]Entry, error) +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) CreateAudit(audit *Entry) error { + dbAudit := database.Audit{ + Topic: string(audit.Topic), + Message: audit.Message, + CreatedAt: audit.CreatedAt, + } + + result := r.db.Create(&dbAudit) + return result.Error +} + +func (r *repository) ReadAudits() ([]Entry, error) { + var dbAudits []database.Audit + result := r.db.Order("created_at DESC").Find(&dbAudits) + if result.Error != nil { + return nil, result.Error + } + + audits := make([]Entry, len(dbAudits)) + for i, dbAudit := range dbAudits { + audits[i] = Entry{ + ID: dbAudit.ID, + Topic: Topic(dbAudit.Topic), + Message: dbAudit.Message, + CreatedAt: dbAudit.CreatedAt, + } + } + + return audits, nil +} diff --git a/backend/audit/service.go b/backend/audit/service.go new file mode 100644 index 0000000..ed73f98 --- /dev/null +++ b/backend/audit/service.go @@ -0,0 +1,50 @@ +package audit + +import ( + "goaway/backend/logging" + "time" +) + +type Topic string + +const ( + TopicServer Topic = "server" + TopicDNS Topic = "dns" + TopicAPI Topic = "api" + TopicResolution Topic = "resolution" + TopicPrefetch Topic = "prefetch" + TopicUpstream Topic = "upstream" + TopicUser Topic = "user" + TopicList Topic = "list" + TopicLogs Topic = "logs" + TopicSettings Topic = "settings" + TopicDatabase Topic = "database" +) + +type Entry struct { + CreatedAt time.Time `json:"createdAt"` + Topic Topic `json:"topic"` + Message string `json:"message"` + ID uint `json:"id"` +} + +var log = logging.GetLogger() + +type Service struct { + repository Repository +} + +func NewService(repo Repository) *Service { + return &Service{repository: repo} +} + +func (s *Service) CreateAudit(entry *Entry) { + err := s.repository.CreateAudit(entry) + if err != nil { + log.Warning("Could not create audit: %v", err) + } +} + +func (s *Service) ReadAudits() ([]Entry, error) { + return s.repository.ReadAudits() +} diff --git a/backend/blacklist/model.go b/backend/blacklist/model.go new file mode 100644 index 0000000..1c2d183 --- /dev/null +++ b/backend/blacklist/model.go @@ -0,0 +1,16 @@ +package blacklist + +type ListUpdateAvailable struct { + RemoteChecksum string `json:"remoteChecksum"` + DBChecksum string `json:"dbChecksum"` + RemoteDomains []string `json:"remoteDomains"` + DBDomains []string `json:"dbDomains"` + DiffAdded []string `json:"diffAdded"` + DiffRemoved []string `json:"diffRemoved"` + UpdateAvailable bool `json:"updateAvailable"` +} + +type BlocklistSource struct { + Name string `json:"name"` + URL string `json:"url"` +} diff --git a/backend/blacklist/repository.go b/backend/blacklist/repository.go new file mode 100644 index 0000000..39a374f --- /dev/null +++ b/backend/blacklist/repository.go @@ -0,0 +1,383 @@ +package blacklist + +import ( + "context" + "errors" + "fmt" + "goaway/backend/database" + "strings" + "time" + + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type SourceRepository interface { + GetSources(ctx context.Context, excludeCustom bool) ([]database.Source, error) + GetSourceByName(ctx context.Context, name string) (*database.Source, error) + GetSourceByNameAndURL(ctx context.Context, name, url string) (*database.Source, error) + CreateOrUpdateSource(ctx context.Context, source *database.Source) error + UpdateSourceName(ctx context.Context, oldName, newName, url string) error + UpdateSourceLastUpdated(ctx context.Context, url string, timestamp time.Time) error + ToggleSourceActive(ctx context.Context, name string) error + DeleteSource(ctx context.Context, name, url string) error + UpsertSource(ctx context.Context, source *database.Source) error +} + +type DomainRepository interface { + GetAllDomains(ctx context.Context) ([]string, error) + GetDomainsForSource(ctx context.Context, sourceName string) ([]string, error) + GetPaginatedDomains(ctx context.Context, page, pageSize int, search string) ([]database.Blacklist, int64, error) + CountDomains(ctx context.Context) (int64, error) + CreateDomain(ctx context.Context, domain *database.Blacklist) error + CreateDomainsInBatches(ctx context.Context, domains []database.Blacklist, batchSize int) error + DeleteDomain(ctx context.Context, domain string) error + DeleteDomainsBySourceID(ctx context.Context, sourceID uint) error + DeleteCustomDomain(ctx context.Context, domain string, sourceID uint) error +} + +type StatsRepository interface { + GetAllSourceStats(ctx context.Context) ([]SourceWithCount, error) + GetSourceStats(ctx context.Context, listname string) (*SourceWithCount, error) + GetRequestStats(ctx context.Context) ([]RequestStats, error) +} + +type TransactionRepository interface { + WithTransaction(ctx context.Context, fn func(*gorm.DB) error) error + Vacuum(ctx context.Context) error +} + +type Repository interface { + SourceRepository + DomainRepository + StatsRepository + TransactionRepository +} + +type SourceWithCount struct { + Name string `json:"name"` + URL string `json:"url"` + ID uint `json:"id"` + LastUpdated time.Time `json:"lastUpdated"` + BlockedCount int `json:"blockedCount"` + Active bool `json:"active"` +} + +type RequestStats struct { + Blocked bool `json:"blocked"` + Count int `json:"count"` +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) GetSources(ctx context.Context, excludeCustom bool) ([]database.Source, error) { + var sources []database.Source + query := r.db.WithContext(ctx) + + if excludeCustom { + query = query.Where("name != ?", "Custom") + } + + if err := query.Find(&sources).Error; err != nil { + return nil, fmt.Errorf("failed to query sources: %w", err) + } + + return sources, nil +} + +func (r *repository) GetSourceByName(ctx context.Context, name string) (*database.Source, error) { + var source database.Source + if err := r.db.WithContext(ctx).Where("name = ?", name).First(&source).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("source '%s' not found", name) + } + return nil, fmt.Errorf("failed to get source: %w", err) + } + return &source, nil +} + +func (r *repository) GetSourceByNameAndURL(ctx context.Context, name, url string) (*database.Source, error) { + var source database.Source + if err := r.db.WithContext(ctx).Where("name = ? AND url = ?", name, url).First(&source).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("source '%s' with URL '%s' not found", name, url) + } + return nil, fmt.Errorf("failed to get source: %w", err) + } + return &source, nil +} + +func (r *repository) CreateOrUpdateSource(ctx context.Context, source *database.Source) error { + result := r.db.WithContext(ctx).Where(database.Source{Name: source.Name, URL: source.URL}).FirstOrCreate(source) + if result.Error != nil { + return fmt.Errorf("failed to create or update source: %w", result.Error) + } + return nil +} + +func (r *repository) UpdateSourceName(ctx context.Context, oldName, newName, url string) error { + if strings.TrimSpace(newName) == "" { + return fmt.Errorf("new name cannot be empty") + } + + result := r.db.WithContext(ctx).Model(&database.Source{}). + Where("name = ? AND url = ?", oldName, url). + Update("name", newName) + + if result.Error != nil { + return fmt.Errorf("failed to update source name: %w", result.Error) + } + + if result.RowsAffected == 0 { + return fmt.Errorf("list with name '%s' not found", oldName) + } + + return nil +} + +func (r *repository) UpdateSourceLastUpdated(ctx context.Context, url string, timestamp time.Time) error { + result := r.db.WithContext(ctx).Model(&database.Source{}). + Where("url = ?", url). + Update("last_updated", timestamp) + + if result.Error != nil { + return fmt.Errorf("failed to update source: %w", result.Error) + } + + return nil +} + +func (r *repository) ToggleSourceActive(ctx context.Context, name string) error { + var source database.Source + if err := r.db.WithContext(ctx).Where("name = ?", name).First(&source).Error; err != nil { + return fmt.Errorf("failed to find source %s: %w", name, err) + } + + result := r.db.WithContext(ctx).Model(&source).Update("active", !source.Active) + if result.Error != nil { + return fmt.Errorf("failed to toggle status for %s: %w", name, result.Error) + } + + return nil +} + +func (r *repository) DeleteSource(ctx context.Context, name, url string) error { + var source database.Source + if err := r.db.WithContext(ctx).Where("name = ? AND url = ?", name, url).First(&source).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fmt.Errorf("source '%s' not found", name) + } + return fmt.Errorf("failed to get source: %w", err) + } + + if err := r.db.WithContext(ctx).Delete(&source).Error; err != nil { + return fmt.Errorf("failed to remove source '%s': %w", name, err) + } + + return nil +} + +func (r *repository) GetAllDomains(ctx context.Context) ([]string, error) { + var domains []string + result := r.db.WithContext(ctx).Model(&database.Blacklist{}). + Distinct("domain"). + Pluck("domain", &domains) + + if result.Error != nil { + return nil, fmt.Errorf("failed to query blacklist: %w", result.Error) + } + + return domains, nil +} + +func (r *repository) GetDomainsForSource(ctx context.Context, sourceName string) ([]string, error) { + var blacklistEntries []database.Blacklist + result := r.db.WithContext(ctx).Select("blacklists.domain"). + Joins("JOIN sources ON blacklists.source_id = sources.id"). + Where("sources.name = ?", sourceName). + Find(&blacklistEntries) + + if result.Error != nil { + return nil, fmt.Errorf("failed to query domains for list: %w", result.Error) + } + + domains := make([]string, len(blacklistEntries)) + for i, entry := range blacklistEntries { + domains[i] = entry.Domain + } + + return domains, nil +} + +func (r *repository) GetPaginatedDomains(ctx context.Context, page, pageSize int, search string) ([]database.Blacklist, int64, error) { + searchPattern := "%" + search + "%" + offset := (page - 1) * pageSize + + var blacklistEntries []database.Blacklist + result := r.db.WithContext(ctx).Select("domain"). + Where("domain LIKE ?", searchPattern). + Order("domain DESC"). + Limit(pageSize). + Offset(offset). + Find(&blacklistEntries) + + if result.Error != nil { + return nil, 0, fmt.Errorf("failed to query blacklist: %w", result.Error) + } + + var total int64 + countResult := r.db.WithContext(ctx).Model(&database.Blacklist{}). + Where("domain LIKE ?", searchPattern). + Count(&total) + + if countResult.Error != nil { + return nil, 0, fmt.Errorf("failed to count domains: %w", countResult.Error) + } + + return blacklistEntries, total, nil +} + +func (r *repository) CountDomains(ctx context.Context) (int64, error) { + var count int64 + result := r.db.WithContext(ctx).Model(&database.Blacklist{}).Count(&count) + if result.Error != nil { + return 0, fmt.Errorf("failed to count domains: %w", result.Error) + } + return count, nil +} + +func (r *repository) CreateDomain(ctx context.Context, domain *database.Blacklist) error { + result := r.db.WithContext(ctx).Create(domain) + if result.Error != nil { + if strings.Contains(result.Error.Error(), "UNIQUE constraint failed") || + strings.Contains(result.Error.Error(), "duplicate key") { + return fmt.Errorf("%s is already blacklisted", domain.Domain) + } + return fmt.Errorf("failed to add domain to blacklist: %w", result.Error) + } + return nil +} + +func (r *repository) CreateDomainsInBatches(ctx context.Context, domains []database.Blacklist, batchSize int) error { + if len(domains) == 0 { + return nil + } + + if err := r.db.WithContext(ctx).CreateInBatches(domains, batchSize).Error; err != nil { + if !strings.Contains(err.Error(), "UNIQUE constraint failed") && + !strings.Contains(err.Error(), "duplicate key") { + return fmt.Errorf("failed to add domains: %w", err) + } + } + return nil +} + +func (r *repository) DeleteDomain(ctx context.Context, domain string) error { + result := r.db.WithContext(ctx).Where("domain = ?", domain).Delete(&database.Blacklist{}) + if result.Error != nil { + return fmt.Errorf("failed to remove domain from blacklist: %w", result.Error) + } + if result.RowsAffected == 0 { + return fmt.Errorf("domain not found: %s", domain) + } + return nil +} + +func (r *repository) DeleteDomainsBySourceID(ctx context.Context, sourceID uint) error { + if err := r.db.WithContext(ctx).Where("source_id = ?", sourceID).Delete(&database.Blacklist{}).Error; err != nil { + return fmt.Errorf("failed to remove domains for source ID %d: %w", sourceID, err) + } + return nil +} + +func (r *repository) DeleteCustomDomain(ctx context.Context, domain string, sourceID uint) error { + result := r.db.WithContext(ctx). + Where("domain = ? AND source_id = ?", domain, sourceID). + Delete(&database.Blacklist{}) + + if result.Error != nil { + return fmt.Errorf("failed to delete domain '%s': %w", domain, result.Error) + } + + if result.RowsAffected == 0 { + return fmt.Errorf("domain '%s' not found in custom blacklist", domain) + } + + return nil +} + +func (r *repository) GetAllSourceStats(ctx context.Context) ([]SourceWithCount, error) { + var results []SourceWithCount + result := r.db.WithContext(ctx).Table("sources s"). + Select("s.id, s.name, s.url, s.last_updated, s.active, COALESCE(bc.blocked_count, 0) as blocked_count"). + Joins("LEFT JOIN (SELECT source_id, COUNT(*) as blocked_count FROM blacklists GROUP BY source_id) bc ON s.id = bc.source_id"). + Order("s.name, s.id"). + Scan(&results) + + if result.Error != nil { + return nil, fmt.Errorf("failed to query source statistics: %w", result.Error) + } + + return results, nil +} + +func (r *repository) GetSourceStats(ctx context.Context, listname string) (*SourceWithCount, error) { + var result SourceWithCount + err := r.db.WithContext(ctx).Table("sources s"). + Select("s.name, s.url, s.last_updated, s.active, COALESCE(bc.blocked_count, 0) as blocked_count"). + Joins("LEFT JOIN (SELECT source_id, COUNT(*) as blocked_count FROM blacklists GROUP BY source_id) bc ON s.id = bc.source_id"). + Where("s.name = ?", listname). + First(&result).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("list not found") + } + return nil, fmt.Errorf("failed to query list statistics: %w", err) + } + + return &result, nil +} + +func (r *repository) GetRequestStats(ctx context.Context) ([]RequestStats, error) { + var stats []RequestStats + result := r.db.WithContext(ctx).Model(&database.RequestLog{}). + Select("blocked, COUNT(*) as count"). + Group("blocked"). + Scan(&stats) + + if result.Error != nil { + return nil, fmt.Errorf("failed to query request_logs: %w", result.Error) + } + + return stats, nil +} + +func (r *repository) Vacuum(ctx context.Context) error { + if err := r.db.WithContext(ctx).Exec("VACUUM").Error; err != nil { + return fmt.Errorf("error while vacuuming database: %w", err) + } + return nil +} + +func (r *repository) WithTransaction(ctx context.Context, fn func(*gorm.DB) error) error { + return r.db.WithContext(ctx).Transaction(fn) +} + +func (r *repository) UpsertSource(ctx context.Context, source *database.Source) error { + if err := r.db.WithContext(ctx).Clauses( + clause.OnConflict{ + Columns: []clause.Column{{Name: "url"}}, + DoUpdates: clause.AssignmentColumns([]string{"name", "last_updated", "active"}), + }, + ).Create(source).Error; err != nil { + return fmt.Errorf("failed to upsert source: %w", err) + } + return nil +} diff --git a/backend/blacklist/service.go b/backend/blacklist/service.go new file mode 100644 index 0000000..1bff9da --- /dev/null +++ b/backend/blacklist/service.go @@ -0,0 +1,693 @@ +package blacklist + +import ( + "bufio" + "context" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "goaway/backend/database" + "goaway/backend/logging" + "io" + "net/http" + "sort" + "strings" + "sync" + "time" + + "gorm.io/gorm" +) + +type HTTPClient interface { + Get(url string) (*http.Response, error) +} + +type Config struct { + DefaultSources []BlocklistSource + CacheTTL time.Duration + BatchSize int + UpdateInterval time.Duration +} + +type Service struct { + repository Repository + httpClient HTTPClient + cache map[string]bool + cacheMu sync.RWMutex + blocklistURL []BlocklistSource + config Config +} + +var log = logging.GetLogger() + +const ( + blacklistedIP = "0.0.0.0" + IPv4Loopback = "127.0.0.1" + defaultBatchSize = 1000 +) + +var defaultConfig = Config{ + DefaultSources: []BlocklistSource{ + { + Name: "StevenBlack", + URL: "https://raw.githubusercontent.com/StevenBlack/hosts/refs/heads/master/hosts", + }, + }, + CacheTTL: 24 * time.Hour, + BatchSize: defaultBatchSize, + UpdateInterval: 24 * time.Hour, +} + +func NewService(repo Repository) *Service { + config := defaultConfig + service := &Service{ + repository: repo, + httpClient: http.DefaultClient, + cache: make(map[string]bool), + config: config, + } + + if len(service.blocklistURL) == 0 { + service.blocklistURL = config.DefaultSources + } + + if err := service.initialize(context.Background()); err != nil { + log.Error("Could not initialize blacklist: %v", err) + } + + return service +} + +func (s *Service) initialize(ctx context.Context) error { + count, err := s.repository.CountDomains(ctx) + if err != nil { + return fmt.Errorf("failed to count domains: %w", err) + } + + if count == 0 { + log.Info("No domains in blacklist. Running initialization...") + if err := s.initializeBlockedDomains(ctx); err != nil { + return fmt.Errorf("failed to initialize blocked domains: %w", err) + } + } + + if err := s.InitializeBlocklist(ctx, "Custom", ""); err != nil { + return fmt.Errorf("failed to initialize custom blocklist: %w", err) + } + + if _, err := s.GetBlocklistUrls(ctx); err != nil { + log.Error("Failed to fetch blocklist URLs: %v", err) + return fmt.Errorf("failed to fetch blocklist URLs: %w", err) + } + + if err := s.PopulateCache(ctx); err != nil { + log.Error("Failed to initialize blocklist cache: %v", err) + return fmt.Errorf("failed to initialize blocklist cache: %w", err) + } + + return nil +} + +func (s *Service) initializeBlockedDomains(ctx context.Context) error { + start := time.Now() + for _, source := range s.blocklistURL { + if source.Name == "Custom" { + continue + } + if err := s.FetchAndLoadHosts(ctx, source.URL, source.Name); err != nil { + return err + } + } + + log.Info("Blocked domains initialized in %.2fs", time.Since(start).Seconds()) + return nil +} + +func (s *Service) PopulateCache(ctx context.Context) error { + domains, err := s.repository.GetAllDomains(ctx) + if err != nil { + return fmt.Errorf("failed to populate cache: %w", err) + } + + s.cacheMu.Lock() + defer s.cacheMu.Unlock() + + s.cache = make(map[string]bool, len(domains)) + for _, domain := range domains { + s.cache[domain] = true + } + + return nil +} + +func (s *Service) IsBlacklisted(domain string) bool { + s.cacheMu.RLock() + defer s.cacheMu.RUnlock() + + if exists, found := s.cache[domain]; found { + return exists + } + return false +} + +func (s *Service) GetBlocklistUrls(ctx context.Context) ([]BlocklistSource, error) { + sources, err := s.repository.GetSources(ctx, true) + if err != nil { + return nil, fmt.Errorf("failed to get blocklist URLs: %w", err) + } + + blocklistURL := make([]BlocklistSource, len(sources)) + for i, source := range sources { + blocklistURL[i] = BlocklistSource{ + Name: source.Name, + URL: source.URL, + } + } + + s.blocklistURL = blocklistURL + return blocklistURL, nil +} + +func (s *Service) NameExists(name, url string) bool { + for _, source := range s.blocklistURL { + if source.Name == name && source.URL == url { + return true + } + } + return false +} + +func (s *Service) URLExists(url string) bool { + for _, source := range s.blocklistURL { + if source.URL == url { + return true + } + } + return false +} + +func (s *Service) CheckIfUpdateAvailable(ctx context.Context, remoteListURL, listName string) (ListUpdateAvailable, error) { + listUpdateAvailable := ListUpdateAvailable{} + + remoteDomains, remoteChecksum, err := s.FetchRemoteHostsList(ctx, remoteListURL) + if err != nil { + log.Warning("Failed to fetch remote hosts list: %v", err) + return listUpdateAvailable, fmt.Errorf("failed to fetch remote hosts list: %w", err) + } + + dbDomains, dbChecksum, err := s.FetchDBHostsList(ctx, listName) + if err != nil { + log.Warning("Failed to fetch database hosts list: %v", err) + return listUpdateAvailable, fmt.Errorf("failed to fetch database hosts list: %w", err) + } + + if remoteChecksum == dbChecksum { + log.Debug("No updates available for %s", listName) + return listUpdateAvailable, nil + } + + diff := func(a, b []string) []string { + mb := make(map[string]struct{}, len(b)) + for _, x := range b { + mb[x] = struct{}{} + } + diff := make([]string, 0) + for _, x := range a { + if _, found := mb[x]; !found { + diff = append(diff, x) + } + } + return diff + } + + return ListUpdateAvailable{ + RemoteDomains: remoteDomains, + DBDomains: dbDomains, + RemoteChecksum: remoteChecksum, + DBChecksum: dbChecksum, + UpdateAvailable: true, + DiffAdded: diff(remoteDomains, dbDomains), + DiffRemoved: diff(dbDomains, remoteDomains), + }, nil +} + +func (s *Service) FetchRemoteHostsList(ctx context.Context, url string) ([]string, string, error) { + resp, err := s.httpClient.Get(url) + if err != nil { + return nil, "", fmt.Errorf("failed to fetch hosts file from %s: %w", url, err) + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + domains, err := s.ExtractDomains(resp.Body) + if err != nil { + return nil, "", fmt.Errorf("failed to extract domains from %s: %w", url, err) + } + + return domains, calculateDomainsChecksum(domains), nil +} + +func (s *Service) FetchDBHostsList(ctx context.Context, name string) ([]string, string, error) { + domains, err := s.repository.GetDomainsForSource(ctx, name) + if err != nil { + return nil, "", fmt.Errorf("could not fetch domains from database: %w", err) + } + + return domains, calculateDomainsChecksum(domains), nil +} + +func calculateDomainsChecksum(domains []string) string { + sort.Strings(domains) + data := strings.Join(domains, "\n") + hash := sha256.Sum256([]byte(data)) + return hex.EncodeToString(hash[:]) +} + +func (s *Service) FetchAndLoadHosts(ctx context.Context, url, name string) error { + resp, err := s.httpClient.Get(url) + if err != nil { + return fmt.Errorf("failed to fetch hosts file from %s: %w", url, err) + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + domains, err := s.ExtractDomains(resp.Body) + if err != nil { + return fmt.Errorf("failed to extract domains from %s: %w", url, err) + } + + if err := s.InitializeBlocklist(ctx, name, url); err != nil { + return fmt.Errorf("failed to initialize blocklist: %w", err) + } + + if err := s.AddDomains(ctx, name, domains, url); err != nil { + return fmt.Errorf("failed to add domains to database: %w", err) + } + + log.Info("Added %d domains from list '%s' with url '%s'", len(domains), name, url) + return nil +} + +func (s *Service) isValidDomain(domain string) bool { + invalidDomains := map[string]bool{ + "localhost": true, + "localhost.localdomain": true, + "broadcasthost": true, + "local": true, + blacklistedIP: true, + } + return !invalidDomains[domain] +} + +func (s *Service) ExtractDomains(body io.Reader) ([]string, error) { + scanner := bufio.NewScanner(body) + domainSet := make(map[string]struct{}) + var domains []string + + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) == 0 || strings.HasPrefix(fields[0], "#") { + continue + } + + domain := fields[0] + if (domain == blacklistedIP || domain == IPv4Loopback) && len(fields) > 1 { + domain = fields[1] + if !s.isValidDomain(domain) { + continue + } + } else if domain == blacklistedIP || domain == IPv4Loopback { + continue + } + + if _, exists := domainSet[domain]; !exists { + domainSet[domain] = struct{}{} + domains = append(domains, domain) + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading hosts file: %w", err) + } + if len(domains) == 0 { + return nil, errors.New("zero results when parsing") + } + + return domains, nil +} + +func (s *Service) updateCache(domains []string, add bool) { + s.cacheMu.Lock() + defer s.cacheMu.Unlock() + + for _, domain := range domains { + if add { + s.cache[domain] = true + } else { + delete(s.cache, domain) + } + } +} + +func (s *Service) AddBlacklistedDomain(ctx context.Context, domain string) error { + blacklistEntry := &database.Blacklist{Domain: domain} + + if err := s.repository.CreateDomain(ctx, blacklistEntry); err != nil { + return err + } + + s.updateCache([]string{domain}, true) + return nil +} + +func (s *Service) AddDomains(ctx context.Context, name string, domains []string, url string) error { + return s.repository.WithTransaction(ctx, func(tx *gorm.DB) error { + currentTime := time.Now() + + if err := s.repository.UpdateSourceLastUpdated(ctx, url, currentTime); err != nil { + return err + } + + source, err := s.repository.GetSourceByNameAndURL(ctx, name, url) + if err != nil { + return err + } + + blacklistEntries := make([]database.Blacklist, 0, len(domains)) + for _, domain := range domains { + blacklistEntries = append(blacklistEntries, database.Blacklist{ + Domain: domain, + SourceID: source.ID, + }) + } + + if len(blacklistEntries) > 0 { + batchSize := s.config.BatchSize + if batchSize == 0 { + batchSize = defaultBatchSize + } + if err := s.repository.CreateDomainsInBatches(ctx, blacklistEntries, batchSize); err != nil { + return err + } + } + + s.updateCache(domains, true) + return nil + }) +} + +func (s *Service) RemoveDomain(ctx context.Context, domain string) error { + if err := s.repository.DeleteDomain(ctx, domain); err != nil { + return err + } + + s.updateCache([]string{domain}, false) + return nil +} + +func (s *Service) AddCustomDomains(ctx context.Context, domains []string) error { + return s.repository.WithTransaction(ctx, func(tx *gorm.DB) error { + currentTime := time.Now() + + source, err := s.repository.GetSourceByName(ctx, "Custom") + if err != nil { + if strings.Contains(err.Error(), "not found") { + newSource := &database.Source{ + Name: "Custom", + LastUpdated: currentTime, + Active: true, + } + if err := s.repository.CreateOrUpdateSource(ctx, newSource); err != nil { + return fmt.Errorf("failed to create custom source: %w", err) + } + source = newSource + } else { + return fmt.Errorf("failed to get custom source: %w", err) + } + } else { + if err := s.repository.UpdateSourceLastUpdated(ctx, "", currentTime); err != nil { + return fmt.Errorf("failed to update custom source: %w", err) + } + } + + for _, domain := range domains { + entry := &database.Blacklist{ + Domain: domain, + SourceID: source.ID, + } + // Ignore duplicate errors + if err := s.repository.CreateDomain(ctx, entry); err != nil && + !strings.Contains(err.Error(), "already blacklisted") { + log.Warning("Failed to add domain %s: %v", domain, err) + } else { + s.updateCache([]string{domain}, true) + } + } + + return nil + }) +} + +func (s *Service) RemoveCustomDomain(ctx context.Context, domain string) error { + source, err := s.repository.GetSourceByName(ctx, "Custom") + if err != nil { + return fmt.Errorf("custom source not found: %w", err) + } + + if err := s.repository.DeleteCustomDomain(ctx, domain, source.ID); err != nil { + return err + } + + s.updateCache([]string{domain}, false) + + currentTime := time.Now() + if err := s.repository.UpdateSourceLastUpdated(ctx, "", currentTime); err != nil { + log.Warning("Failed to update custom source timestamp: %v", err) + } + + return nil +} + +func (s *Service) InitializeBlocklist(ctx context.Context, name, url string) error { + source := &database.Source{ + Name: name, + URL: url, + LastUpdated: time.Now(), + Active: true, + } + + return s.repository.CreateOrUpdateSource(ctx, source) +} + +func (s *Service) AddSource(ctx context.Context, name, url string) error { + if strings.TrimSpace(name) == "" || strings.TrimSpace(url) == "" { + return fmt.Errorf("name and url cannot be empty") + } + + source := &database.Source{ + Name: name, + URL: url, + LastUpdated: time.Now(), + Active: true, + } + + if err := s.repository.UpsertSource(ctx, source); err != nil { + return err + } + + // Update in-memory list + found := false + for _, existing := range s.blocklistURL { + if existing.Name == name && existing.URL == url { + found = true + break + } + } + if !found { + s.blocklistURL = append(s.blocklistURL, BlocklistSource{Name: name, URL: url}) + } + + return nil +} + +func (s *Service) UpdateSourceName(ctx context.Context, oldName, newName, url string) error { + if oldName == newName { + return fmt.Errorf("new name is the same as the old name") + } + + if err := s.repository.UpdateSourceName(ctx, oldName, newName, url); err != nil { + return err + } + + // Update in-memory list + for i, source := range s.blocklistURL { + if source.Name == oldName { + s.blocklistURL[i].Name = newName + } + } + + log.Info("Updated blocklist name from '%s' to '%s'", oldName, newName) + return nil +} + +func (s *Service) ToggleBlocklistStatus(ctx context.Context, name string) error { + return s.repository.ToggleSourceActive(ctx, name) +} + +func (s *Service) RemoveSourceAndDomains(ctx context.Context, name, url string) error { + return s.repository.WithTransaction(ctx, func(tx *gorm.DB) error { + source, err := s.repository.GetSourceByNameAndURL(ctx, name, url) + if err != nil { + return err + } + + if err := s.repository.DeleteDomainsBySourceID(ctx, source.ID); err != nil { + return fmt.Errorf("failed to remove domains for source '%s': %w", name, err) + } + + if err := s.repository.DeleteSource(ctx, name, url); err != nil { + return err + } + + return nil + }) +} + +func (s *Service) RemoveSourceByNameAndURL(name, url string) bool { + for i := len(s.blocklistURL) - 1; i >= 0; i-- { + if s.blocklistURL[i].Name == name && s.blocklistURL[i].URL == url { + s.blocklistURL = append(s.blocklistURL[:i], s.blocklistURL[i+1:]...) + return true + } + } + return false +} + +func (s *Service) CountDomains(ctx context.Context) (int, error) { + count, err := s.repository.CountDomains(ctx) + if err != nil { + return 0, err + } + return int(count), nil +} + +func (s *Service) GetAllowedAndBlocked(ctx context.Context) (allowed, blocked int, err error) { + stats, err := s.repository.GetRequestStats(ctx) + if err != nil { + return 0, 0, err + } + + for _, stat := range stats { + if stat.Blocked { + blocked = stat.Count + } else { + allowed = stat.Count + } + } + + return allowed, blocked, nil +} + +func (s *Service) GetAllListStatistics(ctx context.Context) ([]SourceWithCount, error) { + results, err := s.repository.GetAllSourceStats(ctx) + if err != nil { + return nil, err + } + + stats := make([]SourceWithCount, len(results)) + for i, r := range results { + stats[i] = SourceWithCount{ + Name: r.Name, + URL: r.URL, + BlockedCount: r.BlockedCount, + LastUpdated: r.LastUpdated, + Active: r.Active, + } + } + + return stats, nil +} + +func (s *Service) GetListStatistics(ctx context.Context, listname string) (string, SourceWithCount, error) { + result, err := s.repository.GetSourceStats(ctx, listname) + if err != nil { + return "", SourceWithCount{}, err + } + + stats := SourceWithCount{ + URL: result.URL, + BlockedCount: result.BlockedCount, + LastUpdated: result.LastUpdated, + Active: result.Active, + } + + return result.Name, stats, nil +} + +func (s *Service) LoadPaginatedBlacklist(ctx context.Context, page, pageSize int, search string) ([]string, int, error) { + blacklistEntries, total, err := s.repository.GetPaginatedDomains(ctx, page, pageSize, search) + if err != nil { + return nil, 0, err + } + + domains := make([]string, len(blacklistEntries)) + for i, entry := range blacklistEntries { + domains[i] = entry.Domain + } + + return domains, int(total), nil +} + +func (s *Service) Vacuum(ctx context.Context) { + log.Debug("Vacuuming database...") + if err := s.repository.Vacuum(ctx); err != nil { + log.Warning("Error while vacuuming database: %v", err) + } +} + +func (s *Service) ScheduleAutomaticListUpdates() { + ticker := time.NewTicker(s.config.UpdateInterval) + defer ticker.Stop() + + for range ticker.C { + ctx := context.Background() + log.Info("Starting automatic list updates...") + + for _, source := range s.blocklistURL { + if source.Name == "Custom" { + continue + } + log.Info("Checking for updates for blocklist %s from %s", source.Name, source.URL) + + availableUpdate, err := s.CheckIfUpdateAvailable(ctx, source.URL, source.Name) + if err != nil { + log.Warning("Failed to check for updates for %s: %v", source.Name, err) + continue + } + + if !availableUpdate.UpdateAvailable { + log.Info("No updates available for %s", source.Name) + continue + } + + if err := s.RemoveSourceAndDomains(ctx, source.Name, source.URL); err != nil { + log.Warning("Failed to remove old domains for %s: %v", source.Name, err) + continue + } + + if err := s.FetchAndLoadHosts(ctx, source.URL, source.Name); err != nil { + log.Warning("Failed to fetch and load hosts for %s: %v", source.Name, err) + continue + } + + log.Info("Successfully updated %s with %d new domains", source.Name, len(availableUpdate.DiffAdded)) + } + + if err := s.PopulateCache(ctx); err != nil { + log.Warning("Failed to populate blocklist cache after auto-update: %v", err) + } + } +} diff --git a/backend/database/database.go b/backend/database/database.go new file mode 100644 index 0000000..59a6112 --- /dev/null +++ b/backend/database/database.go @@ -0,0 +1,46 @@ +package database + +import ( + "log" + "os" + "path/filepath" + + "github.com/glebarez/sqlite" + "gorm.io/gorm" +) + +func Initialize() *gorm.DB { + if err := os.MkdirAll("data", 0755); err != nil { + log.Fatal("failed to create data directory: %w", err) + } + + databasePath := filepath.Join("data", "database.db") + db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) + if err != nil { + log.Fatal("failed while initializing database: %w", err) + } + + if err := AutoMigrate(db); err != nil { + log.Fatal("auto migrate failed: %w", err) + } + + return db +} + +func AutoMigrate(db *gorm.DB) error { + return db.AutoMigrate( + &Source{}, + &Blacklist{}, + &Whitelist{}, + &RequestLog{}, + &RequestLogIP{}, + &Resolution{}, + &MacAddress{}, + &User{}, + &APIKey{}, + &Notification{}, + &Prefetch{}, + &Audit{}, + &Alert{}, + ) +} diff --git a/backend/database/model.go b/backend/database/model.go new file mode 100644 index 0000000..9cf5649 --- /dev/null +++ b/backend/database/model.go @@ -0,0 +1,118 @@ +package database + +import ( + "time" +) + +type Source struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + Name string `json:"name" validate:"required"` + URL string `gorm:"unique;not null" json:"url" validate:"required,url"` + Active bool `gorm:"default:true" json:"active"` + LastUpdated time.Time `gorm:"not null" json:"lastUpdated"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type Blacklist struct { + Domain string `gorm:"primaryKey" json:"domain" validate:"required,fqdn"` + SourceID uint `gorm:"primaryKey;not null" json:"sourceID" validate:"required"` + Source Source `gorm:"foreignKey:SourceID;constraint:OnDelete:CASCADE" json:"source"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type Whitelist struct { + Domain string `gorm:"primaryKey" json:"domain" validate:"required,fqdn"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type RequestLog struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + Timestamp time.Time `gorm:"not null;index:idx_timestamp_response_size,priority:1" json:"timestamp"` + Domain string `gorm:"type:varchar(255);not null;index:idx_domain_timestamp,priority:1;index:idx_client_ip_domain,priority:2" json:"domain" validate:"required"` + ClientIP string `gorm:"type:varchar(45);not null;index:idx_client_ip;index:idx_client_ip_domain,priority:1" json:"clientIP" validate:"required,ip"` + ClientName string `gorm:"type:varchar(255)" json:"clientName"` + QueryType string `gorm:"type:varchar(10)" json:"queryType"` + Status string `gorm:"type:varchar(50)" json:"status"` + Protocol string `gorm:"type:varchar(10)" json:"protocol"` + ResponseTimeNs int64 `gorm:"not null" json:"repsonseTimeNS"` + ResponseSizeBytes int `gorm:"index:idx_timestamp_response_size,priority:2" json:"responseSizeBytes"` + Blocked bool `gorm:"not null;index:idx_timestamp_covering,priority:2;default:false" json:"blocked"` + Cached bool `gorm:"not null;index:idx_timestamp_covering,priority:3;default:false" json:"cached"` + IPs []RequestLogIP `gorm:"foreignKey:RequestLogID;constraint:OnDelete:CASCADE" json:"ips"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` +} + +type RequestLogIP struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + RequestLogID uint `gorm:"not null;index" json:"requestLogID" validate:"required"` + IP string `gorm:"type:varchar(45);not null" json:"ip" validate:"required,ip"` + RecordType string `gorm:"type:varchar(10);not null" json:"recordType" validate:"required"` + RequestLog RequestLog `gorm:"foreignKey:RequestLogID;constraint:OnDelete:CASCADE" json:"requestLog"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` +} + +type Resolution struct { + Domain string `gorm:"primaryKey" json:"domain" validate:"required,fqdn"` + IP string `gorm:"index" json:"ip" validate:"required,ip"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type MacAddress struct { + MAC string `gorm:"primaryKey" json:"mac" validate:"required,mac"` + IP string `gorm:"index" json:"ip" validate:"required,ip"` + Vendor string `json:"vendor"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type User struct { + Username string `gorm:"primaryKey" json:"username" validate:"required,min=3,max=50"` + Password string `json:"password" validate:"required,min=8"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type APIKey struct { + Name string `gorm:"primaryKey" json:"name" validate:"required"` + Key string `gorm:"unique;not null" json:"key" validate:"required"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type Notification struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + Severity string `gorm:"type:varchar(20);not null" json:"severity" validate:"required,oneof=info warning error critical"` + Category string `gorm:"type:varchar(50);not null" json:"category" validate:"required"` + Text string `gorm:"type:text;not null" json:"text" validate:"required"` + Read bool `gorm:"default:false;index" json:"read"` + CreatedAt time.Time `gorm:"not null;index" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type Prefetch struct { + Domain string `gorm:"primaryKey" json:"domain" validate:"required,fqdn"` + QueryType int `gorm:"not null" json:"queryType" validate:"required,min=1"` + Refresh int `gorm:"not null" json:"refresh" validate:"required,min=1"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} + +type Audit struct { + ID uint `gorm:"primaryKey;autoIncrement" json:"id"` + Topic string `gorm:"type:varchar(100);not null;index" json:"topic" validate:"required"` + Message string `gorm:"type:text;not null" json:"message" validate:"required"` + CreatedAt time.Time `gorm:"not null;index" json:"createdAt"` +} + +type Alert struct { + Type string `gorm:"primaryKey" json:"type" validate:"required"` + Name string `gorm:"not null" json:"name" validate:"required"` + Webhook string `json:"webhook" validate:"omitempty,url"` + Enabled bool `gorm:"default:false" json:"enabled"` + CreatedAt time.Time `gorm:"not null" json:"createdAt"` + UpdatedAt time.Time `gorm:"not null" json:"updatedAt"` +} diff --git a/backend/dns/arp.go b/backend/dns/arp.go index d8e0a7d..c393883 100644 --- a/backend/dns/arp.go +++ b/backend/dns/arp.go @@ -17,18 +17,34 @@ import ( var log = logging.GetLogger() type vendorResponse struct { + Company string `json:"company"` Success bool `json:"success"` Found bool `json:"found"` - Company string `json:"company"` } -type ARPCache struct { - mu sync.RWMutex +type Cache struct { table map[string]string + mu sync.RWMutex +} + +type vendorCacheEntry struct { + vendor string + err error + timestamp time.Time +} + +type VendorCache struct { + entries map[string]*vendorCacheEntry + mu sync.RWMutex + ttl time.Duration } var ( - cache = &ARPCache{table: make(map[string]string)} + cache = &Cache{table: make(map[string]string)} + vendorCache = &VendorCache{ + entries: make(map[string]*vendorCacheEntry), + ttl: 60 * time.Second, + } httpClient = &http.Client{Timeout: 5 * time.Second} ) @@ -44,6 +60,14 @@ func ProcessARPTable() { } } +func CleanVendorResponseCache() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + for range ticker.C { + vendorCache.cleanup() + } +} + func updateARPTable() { ctx, cancel := context.WithTimeout(context.Background(), 40*time.Second) defer cancel() @@ -130,39 +154,56 @@ func GetMacVendor(mac string) (string, error) { mac = strings.ReplaceAll(mac, "-", "") mac = strings.ToLower(mac) + if vendor, err, found := vendorCache.get(mac); found { + return vendor, err + } + url := fmt.Sprintf("https://api.maclookup.app/v2/macs/%s", mac) - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequest(http.MethodGet, url, http.NoBody) if err != nil { - return "", fmt.Errorf("failed to create request: %w", err) + reqErr := fmt.Errorf("failed to create request: %w", err) + vendorCache.set(mac, "", reqErr) + return "", reqErr } resp, err := httpClient.Do(req) if err != nil { - return "", fmt.Errorf("failed to fetch MAC vendor: %w", err) + apiErr := fmt.Errorf("failed to fetch MAC vendor: %w", err) + vendorCache.set(mac, "", apiErr) + return "", apiErr } defer func(Body io.ReadCloser) { _ = Body.Close() }(resp.Body) if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode) + statusErr := fmt.Errorf("unexpected status code: %d", resp.StatusCode) + vendorCache.set(mac, "", statusErr) + return "", statusErr } body, err := io.ReadAll(resp.Body) if err != nil { - return "", fmt.Errorf("failed to read response body: %w", err) + readErr := fmt.Errorf("failed to read response body: %w", err) + vendorCache.set(mac, "", readErr) + return "", readErr } var result vendorResponse if err := json.Unmarshal(body, &result); err != nil { - return "", fmt.Errorf("failed to unmarshal response: %w", err) + unmarshalErr := fmt.Errorf("failed to unmarshal response: %w", err) + vendorCache.set(mac, "", unmarshalErr) + return "", unmarshalErr } if result.Found { + vendorCache.set(mac, result.Company, nil) return result.Company, nil } - return "", fmt.Errorf("vendor not found") + notFoundErr := fmt.Errorf("vendor not found for mac %s", mac) + vendorCache.set(mac, "", notFoundErr) + return "", notFoundErr } func isValidMAC(mac string) bool { @@ -171,3 +212,42 @@ func isValidMAC(mac string) bool { return len(cleanMAC) == 12 && cleanMAC != "000000000000" } + +func (vc *VendorCache) get(mac string) (string, error, bool) { + vc.mu.RLock() + defer vc.mu.RUnlock() + + entry, exists := vc.entries[mac] + if !exists { + return "", nil, false + } + + if time.Since(entry.timestamp) > vc.ttl { + return "", nil, false + } + + return entry.vendor, entry.err, true +} + +func (vc *VendorCache) set(mac, vendor string, err error) { + vc.mu.Lock() + defer vc.mu.Unlock() + + vc.entries[mac] = &vendorCacheEntry{ + vendor: vendor, + err: err, + timestamp: time.Now(), + } +} + +func (vc *VendorCache) cleanup() { + vc.mu.Lock() + defer vc.mu.Unlock() + + now := time.Now() + for mac, entry := range vc.entries { + if now.Sub(entry.timestamp) > vc.ttl { + delete(vc.entries, mac) + } + } +} diff --git a/backend/dns/database/database.go b/backend/dns/database/database.go deleted file mode 100644 index 50c340d..0000000 --- a/backend/dns/database/database.go +++ /dev/null @@ -1,148 +0,0 @@ -package database - -import ( - "os" - "path/filepath" - "sync" - "time" - - "github.com/glebarez/sqlite" - "gorm.io/gorm" -) - -type DatabaseManager struct { - Conn *gorm.DB - Mutex *sync.RWMutex -} - -type Source struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - Name string `json:"name"` - URL string `gorm:"unique" json:"url"` - Active bool `json:"active"` - LastUpdated int64 `json:"lastUpdated"` -} - -type Blacklist struct { - Domain string `gorm:"primaryKey" json:"domain"` - SourceID uint `gorm:"primaryKey" json:"source_id"` - Source Source `gorm:"foreignKey:SourceID;references:ID" json:"source"` -} - -type Whitelist struct { - Domain string `gorm:"primaryKey" json:"domain"` -} - -type RequestLog struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - Timestamp time.Time `gorm:"not null;index:idx_request_log_timestamp_covering,priority:1;index:idx_request_log_timestamp_desc;index:idx_request_log_domain_timestamp,priority:2" json:"timestamp"` - Domain string `gorm:"type:varchar(255);not null;index:idx_request_log_domain_timestamp,priority:1;index:idx_client_ip_domain,priority:2" json:"domain"` - Blocked bool `gorm:"not null;index:idx_request_log_timestamp_covering,priority:2" json:"blocked"` - Cached bool `gorm:"not null;index:idx_request_log_timestamp_covering,priority:3" json:"cached"` - ResponseTimeNs int64 `gorm:"not null" json:"response_time_ns"` - ClientIP string `gorm:"type:varchar(45);index:idx_client_ip;index:idx_client_ip_domain,priority:1" json:"client_ip"` - ClientName string `gorm:"type:varchar(255)" json:"client_name"` - Status string `gorm:"type:varchar(50)" json:"status"` - QueryType string `gorm:"type:varchar(10)" json:"query_type"` - ResponseSizeBytes int `json:"response_size_bytes"` - Protocol string `gorm:"type:varchar(10)" json:"protocol"` - IPs []RequestLogIP `gorm:"foreignKey:RequestLogID;constraint:OnDelete:CASCADE" json:"ips"` -} - -type RequestLogIP struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - RequestLogID uint `gorm:"not null;index" json:"request_log_id"` - IP string `gorm:"type:varchar(45);not null" json:"ip"` - RType string `gorm:"type:varchar(10);not null" json:"rtype"` - RequestLog RequestLog `gorm:"foreignKey:RequestLogID;references:ID" json:"request_log"` -} - -type Resolution struct { - Domain string `gorm:"primaryKey" json:"domain"` - IP string `json:"ip"` -} - -type MacAddress struct { - MAC string `gorm:"primaryKey" json:"mac"` - IP string `json:"ip"` - Vendor string `json:"vendor"` -} - -type User struct { - Username string `gorm:"primaryKey" json:"username"` - Password string `json:"password"` -} - -type APIKey struct { - Name string `gorm:"primaryKey" json:"name"` - Key string `json:"key"` - CreatedAt time.Time `gorm:"not null" json:"created_at"` -} - -type Notification struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - Severity string `json:"severity"` - Category string `json:"category"` - Text string `json:"text"` - Read bool `json:"read"` - CreatedAt time.Time `gorm:"not null" json:"created_at"` -} - -type Prefetch struct { - Domain string `gorm:"primaryKey" json:"domain"` - Refresh int `json:"refresh"` - QType int `json:"qtype"` -} - -type Audit struct { - ID uint `gorm:"primaryKey;autoIncrement" json:"id"` - Topic string `json:"topic"` - Message string `json:"message"` - CreatedAt time.Time `gorm:"not null" json:"created_at"` -} - -type Alert struct { - Type string `gorm:"primaryKey" json:"type"` - Enabled bool `json:"enabled"` - Name string `json:"name"` - Webhook string `json:"webhook"` -} - -func Initialize() *DatabaseManager { - if err := os.MkdirAll("data", 0755); err != nil { - log.Fatal("failed to create data directory: %v", err) - } - - databasePath := filepath.Join("data", "database.db") - db, err := gorm.Open(sqlite.Open(databasePath), &gorm.Config{}) - if err != nil { - log.Fatal("failed while initializing database: %v", err) - } - - if err := AutoMigrate(db); err != nil { - log.Fatal("auto migrate failed: %v", err) - } - - return &DatabaseManager{ - Conn: db, - Mutex: &sync.RWMutex{}, - } -} - -func AutoMigrate(db *gorm.DB) error { - return db.AutoMigrate( - &Source{}, - &Blacklist{}, - &Whitelist{}, - &RequestLog{}, - &RequestLogIP{}, - &Resolution{}, - &MacAddress{}, - &User{}, - &APIKey{}, - &Notification{}, - &Prefetch{}, - &Audit{}, - &Alert{}, - ) -} diff --git a/backend/dns/database/mac.go b/backend/dns/database/mac.go deleted file mode 100644 index 7e0bf62..0000000 --- a/backend/dns/database/mac.go +++ /dev/null @@ -1,34 +0,0 @@ -package database - -import ( - "errors" - - "gorm.io/gorm" -) - -func FindVendor(db *gorm.DB, mac string) (string, error) { - var query MacAddress - tx := db.Find(&query, "mac = ?", mac) - - if errors.Is(tx.Error, gorm.ErrRecordNotFound) { - return "", nil - } - if tx.Error != nil { - return "", tx.Error - } - - return query.Vendor, nil -} - -func SaveMacEntry(db *gorm.DB, clientIP, mac, vendor string) { - entry := MacAddress{ - MAC: mac, - IP: clientIP, - Vendor: vendor, - } - tx := db.Create(&entry) - - if tx.Error != nil { - log.Warning("Unable to save new MAC entry %v", tx.Error) - } -} diff --git a/backend/dns/database/models/models.go b/backend/dns/database/models/models.go deleted file mode 100644 index 1d09870..0000000 --- a/backend/dns/database/models/models.go +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import "time" - -type Client struct { - Name, Mac, Vendor string - LastSeen time.Time -} - -type ClientDetails struct { - IP, Name, MAC string -} - -type ClientRequestDetails struct { - TotalRequests, UniqueDomains, BlockedRequests, CachedRequests int - AvgResponseTimeMs float64 - LastSeen, MostQueriedDomain string -} - -type Resolution struct { - IP string `json:"ip"` - Domain string `json:"domain"` -} diff --git a/backend/dns/database/resolution.go b/backend/dns/database/resolution.go deleted file mode 100644 index 8cc45ef..0000000 --- a/backend/dns/database/resolution.go +++ /dev/null @@ -1,67 +0,0 @@ -package database - -import ( - "errors" - "fmt" - "strings" - - "gorm.io/gorm" -) - -func FetchResolutions(db *gorm.DB) ([]Resolution, error) { - var resolutions []Resolution - if err := db.Find(&resolutions).Error; err != nil { - return nil, fmt.Errorf("failed to fetch resolutions: %w", err) - } - return resolutions, nil -} - -func FetchResolution(db *gorm.DB, domain string) (string, error) { - log.Debug("Finding resolution for domain: %s", domain) - var res Resolution - - db.Where("domain = ?", domain).Find(&res) - if res.IP != "" { - return res.IP, nil - } - - parts := strings.Split(domain, ".") - for i := 1; i < len(parts); i++ { - wildcardDomain := "*." + strings.Join(parts[i:], ".") - if err := db.Where("domain = ?", wildcardDomain).Find(&res).Error; err == nil { - return res.IP, nil - } else if !errors.Is(err, gorm.ErrRecordNotFound) { - return "", err - } - } - - return "", nil -} - -func CreateNewResolution(db *gorm.DB, ip, domain string) error { - res := Resolution{ - Domain: domain, - IP: ip, - } - - if err := db.Create(&res).Error; err != nil { - if strings.Contains(err.Error(), "UNIQUE") { - return fmt.Errorf("domain already exists, must be unique") - } - return fmt.Errorf("could not create new resolution: %w", err) - } - return nil -} - -func DeleteResolution(db *gorm.DB, ip, domain string) (int, error) { - result := db.Where("ip = ? AND domain = ?", ip, domain).Delete(&Resolution{}) - if result.Error != nil { - return 0, fmt.Errorf("could not delete resolution: %w", result.Error) - } - - if result.RowsAffected == 0 { - log.Warning("No resolution found with IP: %s and Domain: %s", ip, domain) - } - - return int(result.RowsAffected), nil -} diff --git a/backend/dns/lists/blacklist.go b/backend/dns/lists/blacklist.go deleted file mode 100644 index e0862ab..0000000 --- a/backend/dns/lists/blacklist.go +++ /dev/null @@ -1,794 +0,0 @@ -package lists - -import ( - "bufio" - "context" - "crypto/sha256" - "encoding/hex" - "errors" - "fmt" - "goaway/backend/dns/database" - "goaway/backend/logging" - "io" - "net/http" - "sort" - "strings" - "time" - - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -var log = logging.GetLogger() - -type BlocklistSource struct { - Name string - URL string -} - -type Blacklist struct { - DBManager *database.DatabaseManager - BlocklistURL []BlocklistSource - BlacklistCache map[string]bool -} - -type SourceStats struct { - Name string `json:"name"` - URL string `json:"url"` - BlockedCount int `json:"blockedCount"` - LastUpdated int64 `json:"lastUpdated"` - Active bool `json:"active"` -} - -type ListUpdateAvailable struct { - RemoteDomains []string `json:"remoteDomains"` - DBDomains []string `json:"dbDomains"` - RemoteChecksum string `json:"remoteChecksum"` - DBChecksum string `json:"dbChecksum"` - UpdateAvailable bool `json:"updateAvailable"` - DiffAdded []string `json:"diffAdded"` - DiffRemoved []string `json:"diffRemoved"` -} - -func InitializeBlacklist(dbManager *database.DatabaseManager) (*Blacklist, error) { - b := &Blacklist{ - DBManager: dbManager, - BlocklistURL: []BlocklistSource{ - {Name: "StevenBlack", URL: "https://raw.githubusercontent.com/StevenBlack/hosts/refs/heads/master/hosts"}, - }, - BlacklistCache: map[string]bool{}, - } - - if count, _ := b.CountDomains(); count == 0 { - log.Info("No domains in blacklist. Running initialization...") - if err := b.initializeBlockedDomains(); err != nil { - return nil, fmt.Errorf("failed to initialize blocked domains: %w", err) - } - } - - if err := b.InitializeBlocklist("Custom", ""); err != nil { - return nil, fmt.Errorf("failed to initialize custom blocklist: %w", err) - } - - _, err := b.GetBlocklistUrls() - if err != nil { - log.Error("Failed to fetch blocklist URLs: %v", err) - return nil, fmt.Errorf("failed to fetch blocklist URLs: %w", err) - } - _, err = b.PopulateBlocklistCache() - if err != nil { - log.Error("Failed to initialize blocklist cache") - return nil, fmt.Errorf("failed to initialize blocklist cache: %w", err) - } - - return b, nil -} - -func (b *Blacklist) initializeBlockedDomains() error { - for _, source := range b.BlocklistURL { - if source.Name == "Custom" { - continue - } - if err := b.FetchAndLoadHosts(source.URL, source.Name); err != nil { - return err - } - } - return nil -} - -func (b *Blacklist) Vacuum() { - b.DBManager.Mutex.Lock() - tx := b.DBManager.Conn.Raw("VACUUM") - if err := tx.Error; err != nil { - log.Warning("Error while vacuuming database: %v", err) - } - err := tx.Commit().Error - b.DBManager.Mutex.Unlock() - if err != nil { - log.Warning("Error while vacuuming database: %v", err) - } -} - -func (b *Blacklist) GetBlocklistUrls() ([]BlocklistSource, error) { - var sources []database.Source - - result := b.DBManager.Conn.Where("name != ?", "Custom").Find(&sources) - if result.Error != nil { - return nil, fmt.Errorf("failed to query sources: %w", result.Error) - } - - blocklistURL := make([]BlocklistSource, len(sources)) - for i, source := range sources { - blocklistURL[i] = BlocklistSource{ - Name: source.Name, - URL: source.URL, - } - } - - b.BlocklistURL = blocklistURL - return blocklistURL, nil -} - -func (b *Blacklist) CheckIfUpdateAvailable(remoteListURL, listName string) (ListUpdateAvailable, error) { - listUpdateAvailable := ListUpdateAvailable{} - remoteDomains, remoteChecksum, err := b.FetchRemoteHostsList(remoteListURL) - if err != nil { - log.Warning("Failed to fetch remote hosts list: %v", err) - return listUpdateAvailable, fmt.Errorf("failed to fetch remote hosts list: %w", err) - } - - dbDomains, dbChecksum, err := b.FetchDBHostsList(listName) - if err != nil { - log.Warning("Failed to fetch database hosts list: %v", err) - return listUpdateAvailable, fmt.Errorf("failed to fetch database hosts list: %w", err) - } - - if remoteChecksum == dbChecksum { - log.Debug("No updates available for %s", listName) - return listUpdateAvailable, nil - } - - diff := func(a, b []string) []string { - mb := make(map[string]struct{}, len(b)) - for _, x := range b { - mb[x] = struct{}{} - } - diff := make([]string, 0) - for _, x := range a { - if _, found := mb[x]; !found { - diff = append(diff, x) - } - } - return diff - } - - return ListUpdateAvailable{ - RemoteDomains: remoteDomains, - DBDomains: dbDomains, - RemoteChecksum: remoteChecksum, - DBChecksum: dbChecksum, - UpdateAvailable: true, - DiffAdded: diff(remoteDomains, dbDomains), - DiffRemoved: diff(dbDomains, remoteDomains), - }, nil -} - -func (b *Blacklist) FetchRemoteHostsList(url string) ([]string, string, error) { - resp, err := http.Get(url) - if err != nil { - return nil, "", fmt.Errorf("failed to fetch hosts file from %s: %w", url, err) - } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - domains, err := b.ExtractDomains(resp.Body) - if err != nil { - return nil, "", fmt.Errorf("failed to extract domains from %s: %w", url, err) - } - - return domains, calculateDomainsChecksum(domains), nil -} - -func (b *Blacklist) FetchDBHostsList(name string) ([]string, string, error) { - domains, err := b.GetDomainsForList(name) - if err != nil { - return nil, "", fmt.Errorf("could not fetch domains from database") - } - - return domains, calculateDomainsChecksum(domains), nil -} - -func calculateDomainsChecksum(domains []string) string { - sort.Strings(domains) - data := strings.Join(domains, "\n") - - hash := sha256.Sum256([]byte(data)) - return hex.EncodeToString(hash[:]) -} - -func (b *Blacklist) FetchAndLoadHosts(url, name string) error { - resp, err := http.Get(url) - if err != nil { - return fmt.Errorf("failed to fetch hosts file from %s: %w", url, err) - } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - domains, err := b.ExtractDomains(resp.Body) - if err != nil { - return fmt.Errorf("failed to extract domains from %s: %w", url, err) - } - - _ = b.InitializeBlocklist(name, url) - - if err := b.AddDomains(domains, url); err != nil { - return fmt.Errorf("failed to add domains to database: %w", err) - } - - log.Info("Added %d domains from list '%s' with url '%s'", len(domains), name, url) - return nil -} - -func (b *Blacklist) ExtractDomains(body io.Reader) ([]string, error) { - scanner := bufio.NewScanner(body) - domainSet := make(map[string]struct{}) - var domains []string - - for scanner.Scan() { - fields := strings.Fields(scanner.Text()) - if len(fields) == 0 || strings.HasPrefix(fields[0], "#") { - continue - } - - domain := fields[0] - if (domain == "0.0.0.0" || domain == "127.0.0.1") && len(fields) > 1 { - domain = fields[1] - switch domain { - case "localhost", "localhost.localdomain", "broadcasthost", "local", "0.0.0.0": - continue - } - } else if domain == "0.0.0.0" || domain == "127.0.0.1" { - continue - } - - if _, exists := domainSet[domain]; !exists { - domainSet[domain] = struct{}{} - domains = append(domains, domain) - } - } - - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("error reading hosts file: %w", err) - } - if len(domains) == 0 { - return nil, errors.New("zero results when parsing") - } - - return domains, nil -} - -func (b *Blacklist) AddBlacklistedDomain(domain string) error { - blacklistEntry := database.Blacklist{Domain: domain} - - result := b.DBManager.Conn.Create(&blacklistEntry) - if result.Error != nil { - if strings.Contains(result.Error.Error(), "UNIQUE constraint failed") || - strings.Contains(result.Error.Error(), "duplicate key") { - return fmt.Errorf("%s is already blacklisted", domain) - } - return fmt.Errorf("failed to add domain to blacklist: %w", result.Error) - } - - b.BlacklistCache[domain] = true - return nil -} - -func (b *Blacklist) AddDomains(domains []string, url string) error { - return b.DBManager.Conn.Transaction(func(tx *gorm.DB) error { - var source database.Source - currentTime := time.Now().Unix() - - result := tx.Model(&source).Where("url = ?", url).Update("last_updated", currentTime) - if result.Error != nil { - return fmt.Errorf("failed to update source: %w", result.Error) - } - - if err := tx.Where("url = ?", url).First(&source).Error; err != nil { - return fmt.Errorf("failed to find source: %w", err) - } - - blacklistEntries := make([]database.Blacklist, 0, len(domains)) - for _, domain := range domains { - blacklistEntries = append(blacklistEntries, database.Blacklist{ - Domain: domain, - SourceID: source.ID, - }) - } - - if len(blacklistEntries) > 0 { - if err := tx.CreateInBatches(blacklistEntries, 1000).Error; err != nil { - if !strings.Contains(err.Error(), "UNIQUE constraint failed") && - !strings.Contains(err.Error(), "duplicate key") { - return fmt.Errorf("failed to add domains: %w", err) - } - } - } - - return nil - }) -} - -func (b *Blacklist) PopulateBlocklistCache() (int, error) { - var databaseDomains []string - result := b.DBManager.Conn.Model(&database.Blacklist{}). - Distinct("domain"). - Pluck("domain", &databaseDomains) - - if result.Error != nil { - return 0, fmt.Errorf("failed to query blacklist: %w", result.Error) - } - - b.BlacklistCache = make(map[string]bool, len(databaseDomains)) - for _, domain := range databaseDomains { - b.BlacklistCache[domain] = true - } - - return len(b.BlacklistCache), nil -} - -func (b *Blacklist) CountDomains() (int, error) { - var count int64 - result := b.DBManager.Conn.Model(&database.Blacklist{}).Count(&count) - if result.Error != nil { - return 0, fmt.Errorf("failed to count domains: %w", result.Error) - } - return int(count), nil -} - -func (b *Blacklist) GetAllowedAndBlocked() (allowed, blocked int, err error) { - type RequestStats struct { - Blocked bool - Count int - } - - var stats []RequestStats - result := b.DBManager.Conn.Model(&database.RequestLog{}). - Select("blocked, COUNT(*) as count"). - Group("blocked"). - Scan(&stats) - - if result.Error != nil { - return 0, 0, fmt.Errorf("failed to query request_logs: %w", result.Error) - } - - for _, stat := range stats { - if stat.Blocked { - blocked = stat.Count - } else { - allowed = stat.Count - } - } - - return allowed, blocked, nil -} - -func (b *Blacklist) RemoveDomain(domain string) error { - result := b.DBManager.Conn.Where("domain = ?", domain).Delete(&database.Blacklist{}) - if result.Error != nil { - return fmt.Errorf("failed to remove domain from blacklist: %w", result.Error) - } - - if result.RowsAffected == 0 { - return fmt.Errorf("%s is already whitelisted", domain) - } - - delete(b.BlacklistCache, domain) - return nil -} - -func (b *Blacklist) UpdateSourceName(oldName, newName, url string) error { - if strings.TrimSpace(newName) == "" { - return fmt.Errorf("new name cannot be empty") - } - - if oldName == newName { - return fmt.Errorf("new name is the same as the old name") - } - - result := b.DBManager.Conn.Model(&database.Source{}). - Where("name = ? AND url = ?", oldName, url). - Update("name", newName) - - if result.Error != nil { - return fmt.Errorf("failed to update source name: %w", result.Error) - } - - if result.RowsAffected == 0 { - return fmt.Errorf("list with name '%s' not found", oldName) - } - - for i, source := range b.BlocklistURL { - if source.Name == oldName { - b.BlocklistURL[i].Name = newName - } - } - - log.Info("Updated blocklist name from '%s' to '%s'", oldName, newName) - return nil -} - -func (b *Blacklist) NameExists(name, url string) bool { - for _, source := range b.BlocklistURL { - if source.Name == name && source.URL == url { - return true - } - } - - return false -} - -func (b *Blacklist) URLExists(url string) bool { - for _, source := range b.BlocklistURL { - if source.URL == url { - return true - } - } - return false -} - -func (b *Blacklist) IsBlacklisted(domain string) bool { - return b.BlacklistCache[domain] -} - -func (b *Blacklist) LoadPaginatedBlacklist(page, pageSize int, search string) ([]string, int, error) { - searchPattern := "%" + search + "%" - offset := (page - 1) * pageSize - - var blacklistEntries []database.Blacklist - result := b.DBManager.Conn.Select("domain"). - Where("domain LIKE ?", searchPattern). - Order("domain DESC"). - Limit(pageSize). - Offset(offset). - Find(&blacklistEntries) - - if result.Error != nil { - return nil, 0, fmt.Errorf("failed to query blacklist: %w", result.Error) - } - - domains := make([]string, len(blacklistEntries)) - for i, entry := range blacklistEntries { - domains[i] = entry.Domain - } - - var total int64 - countResult := b.DBManager.Conn.Model(&database.Blacklist{}). - Where("domain LIKE ?", searchPattern). - Count(&total) - - if countResult.Error != nil { - return nil, 0, fmt.Errorf("failed to count domains: %w", countResult.Error) - } - - return domains, int(total), nil -} - -func (b *Blacklist) InitializeBlocklist(name, url string) error { - return b.DBManager.Conn.Transaction(func(tx *gorm.DB) error { - source := database.Source{ - Name: name, - URL: url, - LastUpdated: time.Now().Unix(), - Active: true, - } - - result := tx.Where(database.Source{Name: name, URL: url}).FirstOrCreate(&source) - if result.Error != nil { - return fmt.Errorf("failed to initialize new blocklist: %w", result.Error) - } - - return nil - }) -} - -func (b *Blacklist) AddCustomDomains(domains []string) error { - return b.DBManager.Conn.Transaction(func(tx *gorm.DB) error { - var source database.Source - currentTime := time.Now().Unix() - - err := tx.Where("name = ?", "Custom").First(&source).Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - source = database.Source{ - Name: "Custom", - LastUpdated: currentTime, - } - if err := tx.Create(&source).Error; err != nil { - return fmt.Errorf("failed to insert custom source: %w", err) - } - } else { - return fmt.Errorf("failed to get custom source ID: %w", err) - } - } else { - if err := tx.Model(&source).Update("last_updated", currentTime).Error; err != nil { - return fmt.Errorf("failed to update lastUpdated for custom source: %w", err) - } - } - - blacklistEntries := make([]database.Blacklist, 0, len(domains)) - for _, domain := range domains { - blacklistEntries = append(blacklistEntries, database.Blacklist{ - Domain: domain, - SourceID: source.ID, - }) - } - - for _, entry := range blacklistEntries { - if err := tx.Where(database.Blacklist{Domain: entry.Domain, SourceID: entry.SourceID}).FirstOrCreate(&entry).Error; err != nil { - return fmt.Errorf("failed to add custom domain '%s': %w", entry.Domain, err) - } - b.BlacklistCache[entry.Domain] = true - } - - return nil - }) -} - -func (b *Blacklist) RemoveCustomDomain(domain string) error { - b.DBManager.Mutex.Lock() - defer b.DBManager.Mutex.Unlock() - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - return b.DBManager.Conn.WithContext(ctx).Transaction(func(tx *gorm.DB) error { - var source database.Source - err := tx.Where("name = ?", "Custom").First(&source).Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fmt.Errorf("custom source not found") - } - return fmt.Errorf("failed to get custom source ID: %w", err) - } - - result := tx.Where("domain = ? AND source_id = ?", domain, source.ID).Delete(&database.Blacklist{}) - if result.Error != nil { - return fmt.Errorf("failed to delete domain '%s': %w", domain, result.Error) - } - - if result.RowsAffected == 0 { - return fmt.Errorf("domain '%s' not found in custom blacklist", domain) - } - - delete(b.BlacklistCache, domain) - - currentTime := time.Now().Unix() - if err := tx.Model(&source).Update("last_updated", currentTime).Error; err != nil { - return fmt.Errorf("failed to update lastUpdated for custom source: %w", err) - } - - return nil - }) -} - -func (b *Blacklist) GetAllListStatistics() ([]SourceStats, error) { - type SourceWithCount struct { - ID int `json:"id"` - Name string `json:"name"` - URL string `json:"url"` - LastUpdated int64 `json:"last_updated"` - Active bool `json:"active"` - BlockedCount int `json:"blocked_count"` - } - - var results []SourceWithCount - result := b.DBManager.Conn.Table("sources s"). - Select("s.id, s.name, s.url, s.last_updated, s.active, COALESCE(bc.blocked_count, 0) as blocked_count"). - Joins("LEFT JOIN (SELECT source_id, COUNT(*) as blocked_count FROM blacklists GROUP BY source_id) bc ON s.id = bc.source_id"). - Order("s.name, s.id"). - Scan(&results) - - if result.Error != nil { - return nil, fmt.Errorf("failed to query source statistics: %w", result.Error) - } - - stats := make([]SourceStats, len(results)) - for i, r := range results { - stats[i] = SourceStats{ - Name: r.Name, - URL: r.URL, - BlockedCount: r.BlockedCount, - LastUpdated: r.LastUpdated, - Active: r.Active, - } - } - - return stats, nil -} - -func (b *Blacklist) GetListStatistics(listname string) (string, SourceStats, error) { - type SourceWithCount struct { - Name string `json:"name"` - URL string `json:"url"` - LastUpdated int64 `json:"last_updated"` - Active bool `json:"active"` - BlockedCount int `json:"blocked_count"` - } - - var result SourceWithCount - err := b.DBManager.Conn.Table("sources s"). - Select("s.name, s.url, s.last_updated, s.active, COALESCE(bc.blocked_count, 0) as blocked_count"). - Joins("LEFT JOIN (SELECT source_id, COUNT(*) as blocked_count FROM blacklists GROUP BY source_id) bc ON s.id = bc.source_id"). - Where("s.name = ?", listname). - First(&result).Error - - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return "", SourceStats{}, fmt.Errorf("list not found") - } - return "", SourceStats{}, fmt.Errorf("failed to query list statistics: %w", err) - } - - stats := SourceStats{ - URL: result.URL, - BlockedCount: result.BlockedCount, - LastUpdated: result.LastUpdated, - Active: result.Active, - } - - return result.Name, stats, nil -} - -func (b *Blacklist) GetDomainsForList(list string) ([]string, error) { - var blacklistEntries []database.Blacklist - result := b.DBManager.Conn.Select("blacklists.domain"). - Joins("JOIN sources ON blacklists.source_id = sources.id"). - Where("sources.name = ?", list). - Find(&blacklistEntries) - - if result.Error != nil { - return nil, fmt.Errorf("failed to query domains for list: %w", result.Error) - } - - domains := make([]string, len(blacklistEntries)) - for i, entry := range blacklistEntries { - domains[i] = entry.Domain - } - - return domains, nil -} - -func (b *Blacklist) ToggleBlocklistStatus(name string) error { - var source database.Source - if err := b.DBManager.Conn.Where("name = ?", name).First(&source).Error; err != nil { - return fmt.Errorf("failed to find source %s: %w", name, err) - } - - result := b.DBManager.Conn.Model(&source).Update("active", !source.Active) - if result.Error != nil { - return fmt.Errorf("failed to toggle status for %s: %w", name, result.Error) - } - - return nil -} - -func (b *Blacklist) RemoveSourceAndDomains(name, url string) error { - return b.DBManager.Conn.Transaction(func(tx *gorm.DB) error { - var source database.Source - err := tx.Where("name = ? AND url = ?", name, url).First(&source).Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fmt.Errorf("source '%s' not found", name) - } - return fmt.Errorf("failed to get source ID: %w", err) - } - - if err := tx.Where("source_id = ?", source.ID).Delete(&database.Blacklist{}).Error; err != nil { - return fmt.Errorf("failed to remove domains for source '%s': %w", name, err) - } - - if err := tx.Delete(&source).Error; err != nil { - return fmt.Errorf("failed to remove source '%s': %w", name, err) - } - - return nil - }) -} - -func (b *Blacklist) RemoveSourceAndDomainsWithCacheRefresh(name, url string) error { - if err := b.RemoveSourceAndDomains(name, url); err != nil { - return err - } - - if _, err := b.PopulateBlocklistCache(); err != nil { - log.Warning("Failed to clear blocklist cache after removing source: %v", err) - } - - log.Info("Removed all domains and source '%s'", name) - return nil -} -func (b *Blacklist) ScheduleAutomaticListUpdates() { - for { - next := time.Now().Add(24 * time.Hour).Truncate(24 * time.Hour) - log.Info("Next auto-update for lists scheduled for: %s", next.Format(time.DateTime)) - time.Sleep(time.Until(next)) - - for _, source := range b.BlocklistURL { - if source.Name == "Custom" { - continue - } - log.Info("Checking for updates for blocklist %s from %s", source.Name, source.URL) - - availableUpdate, err := b.CheckIfUpdateAvailable(source.URL, source.Name) - if err != nil { - log.Warning("Failed to check for updates for %s: %v", source.Name, err) - continue - } - - if !availableUpdate.UpdateAvailable { - log.Info("No updates available for %s", source.Name) - continue - } - - if err := b.RemoveSourceAndDomains(source.Name, source.URL); err != nil { - log.Warning("Failed to remove old domains for %s: %v", source.Name, err) - continue - } - if err := b.FetchAndLoadHosts(source.URL, source.Name); err != nil { - log.Warning("Failed to fetch and load hosts for %s: %v", source.Name, err) - continue - } - - log.Info("Successfully updated %s with %d new domains", source.Name, len(availableUpdate.DiffAdded)) - } - if _, err := b.PopulateBlocklistCache(); err != nil { - log.Warning("Failed to populate blocklist cache after auto-update: %v", err) - } - } -} - -func (b *Blacklist) AddSource(name, url string) error { - if strings.TrimSpace(name) == "" || strings.TrimSpace(url) == "" { - return fmt.Errorf("name and url cannot be empty") - } - - if err := b.DBManager.Conn.Clauses( - clause.OnConflict{ - Columns: []clause.Column{{Name: "url"}}, - DoUpdates: clause.AssignmentColumns([]string{"name", "last_updated", "active"}), - }, - ).Create(&database.Source{ - Name: name, - URL: url, - LastUpdated: time.Now().Unix(), - Active: true, - }).Error; err != nil { - return fmt.Errorf("failed to insert source: %w", err) - } - - found := false - for _, s := range b.BlocklistURL { - if s.Name == name && s.URL == url { - found = true - break - } - } - if !found { - b.BlocklistURL = append(b.BlocklistURL, BlocklistSource{Name: name, URL: url}) - } - - return nil -} - -func (b *Blacklist) RemoveSourceByNameAndURL(name, url string) bool { - for i := len(b.BlocklistURL) - 1; i >= 0; i-- { - if b.BlocklistURL[i].Name == name && b.BlocklistURL[i].URL == url { - b.BlocklistURL = append(b.BlocklistURL[:i], b.BlocklistURL[i+1:]...) - return true - } - } - - return false -} diff --git a/backend/dns/lists/whitelist.go b/backend/dns/lists/whitelist.go deleted file mode 100644 index ab03043..0000000 --- a/backend/dns/lists/whitelist.go +++ /dev/null @@ -1,85 +0,0 @@ -package lists - -import ( - "fmt" - "goaway/backend/dns/database" - - "gorm.io/gorm/clause" -) - -type Whitelist struct { - DBManager *database.DatabaseManager - Cache map[string]bool -} - -func InitializeWhitelist(dbManager *database.DatabaseManager) (*Whitelist, error) { - w := &Whitelist{ - DBManager: dbManager, - Cache: map[string]bool{}, - } - - return w, w.refreshCache() -} - -func (w *Whitelist) AddDomain(domain string) error { - result := w.DBManager.Conn.Clauses(clause.OnConflict{DoNothing: true}).Create(&database.Whitelist{Domain: domain}) - - if result.Error != nil { - return fmt.Errorf("failed to add domain to whitelist: %w", result.Error) - } - if result.RowsAffected == 0 { - return fmt.Errorf("%s is already whitelisted", domain) - } - - w.Cache[domain] = true - return nil -} - -func (w *Whitelist) RemoveDomain(domain string) error { - result := w.DBManager.Conn.Delete(&database.Whitelist{}, "domain = ?", domain) - - if result.Error != nil { - return fmt.Errorf("failed to remove domain from whitelist: %w", result.Error) - } - if result.RowsAffected == 0 { - return fmt.Errorf("%s does not exist", domain) - } - - delete(w.Cache, domain) - return nil -} - -func (w *Whitelist) refreshCache() error { - for k := range w.Cache { - delete(w.Cache, k) - } - - domains, err := w.GetDomains() - if err != nil { - return fmt.Errorf("could not get whitelisted domains while refreshing cache, %v", err) - } - - for domain := range domains { - w.Cache[domain] = true - } - - return nil -} - -func (w *Whitelist) GetDomains() (map[string]bool, error) { - var records []database.Whitelist - if err := w.DBManager.Conn.Find(&records).Error; err != nil { - return nil, fmt.Errorf("failed to query whitelist: %w", err) - } - - domains := make(map[string]bool, len(records)) - for _, rec := range records { - domains[rec.Domain] = true - } - - return domains, nil -} - -func (w *Whitelist) IsWhitelisted(domain string) bool { - return w.Cache[domain] -} diff --git a/backend/dns/server/cache.go b/backend/dns/server/cache.go index cdb4a97..97df971 100644 --- a/backend/dns/server/cache.go +++ b/backend/dns/server/cache.go @@ -32,7 +32,7 @@ func (s *DNSServer) getCachedRecord(cached interface{}) ([]dns.RR, bool) { if cachedRecord.Key != "" { log.Debug("Cached entry has expired, removing %s from cache", cachedRecord.Key) - s.Cache.Delete(cachedRecord.Key) + s.DomainCache.Delete(cachedRecord.Key) } return nil, false @@ -43,14 +43,14 @@ func (s *DNSServer) RemoveCachedDomain(domain string) { return } - s.Cache.Range(func(key, value interface{}) bool { + s.DomainCache.Range(func(key, value interface{}) bool { cachedRecord, ok := value.(CachedRecord) if !ok || cachedRecord.Domain != domain+"." { return true } log.Debug("Removing cached record for domain %s", domain) - s.Cache.Delete(key) + s.DomainCache.Delete(key) return true }) } @@ -69,7 +69,7 @@ func (s *DNSServer) CacheRecord(cacheKey, domain string, ipAddresses []dns.RR, t } now := time.Now() - s.Cache.Store(cacheKey, CachedRecord{ + s.DomainCache.Store(cacheKey, CachedRecord{ IPAddresses: ipAddresses, ExpiresAt: now.Add(cacheTTL), CachedAt: now, diff --git a/backend/dns/server/doh.go b/backend/dns/server/doh.go index c6ad481..e76ab92 100644 --- a/backend/dns/server/doh.go +++ b/backend/dns/server/doh.go @@ -16,11 +16,11 @@ import ( ) const ( - MaxDoHRequestSize = 4096 - DoHTimeout = 20 * time.Second - DoHReadTimeout = 8 * time.Second - DoHWriteTimeout = 8 * time.Second - MB = 1 << 20 + maxDoHRequestSize = 4096 + doHTimeout = 20 * time.Second + doHReadTimeout = 8 * time.Second + doHWriteTimeout = 8 * time.Second + megabyte = 1 << 20 ) func (s *DNSServer) InitDoH(cert tls.Certificate) (*http.Server, error) { @@ -29,7 +29,7 @@ func (s *DNSServer) InitDoH(cert tls.Certificate) (*http.Server, error) { mux.HandleFunc("/health", s.handleHealthCheck) server := &http.Server{ - Addr: fmt.Sprintf("%s:%d", s.Config.DNS.Address, s.Config.DNS.DoHPort), + Addr: fmt.Sprintf("%s:%d", s.Config.DNS.Address, s.Config.DNS.Ports.DoH), Handler: mux, TLSConfig: &tls.Config{ Certificates: []tls.Certificate{cert}, @@ -38,17 +38,17 @@ func (s *DNSServer) InitDoH(cert tls.Certificate) (*http.Server, error) { PreferServerCipherSuites: true, NextProtos: []string{"h2", "http/1.1"}, }, - ReadTimeout: DoHReadTimeout, - WriteTimeout: DoHWriteTimeout, + ReadTimeout: doHReadTimeout, + WriteTimeout: doHWriteTimeout, ReadHeaderTimeout: 5 * time.Second, IdleTimeout: 60 * time.Second, - MaxHeaderBytes: 1 * MB, + MaxHeaderBytes: 1 * megabyte, } return server, nil } -func (s *DNSServer) handleHealthCheck(w http.ResponseWriter, r *http.Request) { +func (s *DNSServer) handleHealthCheck(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) _, err := w.Write([]byte(`{"status":"healthy"}`)) @@ -60,14 +60,14 @@ func (s *DNSServer) handleHealthCheck(w http.ResponseWriter, r *http.Request) { } func (s *DNSServer) handleDoHRequest(w http.ResponseWriter, r *http.Request) { - ctx, cancel := context.WithTimeout(r.Context(), DoHTimeout) + ctx, cancel := context.WithTimeout(r.Context(), doHTimeout) defer cancel() r = r.WithContext(ctx) log.Debug("DoH request received: %s %s from %s", r.Method, r.URL.String(), r.RemoteAddr) - if r.ContentLength > MaxDoHRequestSize { + if r.ContentLength > maxDoHRequestSize { log.Warning("DoH request too large: %d bytes from %s", r.ContentLength, r.RemoteAddr) http.Error(w, "Request too large", http.StatusRequestEntityTooLarge) return @@ -79,9 +79,9 @@ func (s *DNSServer) handleDoHRequest(w http.ResponseWriter, r *http.Request) { client model.Client ) if xRealIP != "" { - go s.WSCom(communicationMessage{true, false, false, xRealIP}) + go s.WSCom(communicationMessage{IP: xRealIP, Client: true, Upstream: false, DNS: false}) } else { - go s.WSCom(communicationMessage{true, false, false, clientIP}) + go s.WSCom(communicationMessage{IP: clientIP, Client: true, Upstream: false, DNS: false}) } var ( @@ -90,9 +90,9 @@ func (s *DNSServer) handleDoHRequest(w http.ResponseWriter, r *http.Request) { ) switch r.Method { - case "GET": + case http.MethodGet: dnsQuery, err = s.handleDoHGet(r) - case "POST": + case http.MethodPost: dnsQuery, err = s.handleDoHPost(r) default: log.Warning("DoH request invalid method: %s from %s", r.Method, r.RemoteAddr) @@ -124,7 +124,7 @@ func (s *DNSServer) handleDoHRequest(w http.ResponseWriter, r *http.Request) { responseWriter := &DoHResponseWriter{ httpWriter: w, remoteAddr: r.RemoteAddr, - DoHPort: s.Config.DNS.DoHPort, + DoHPort: s.Config.DNS.Ports.DoH, } if xRealIP != "" { @@ -147,7 +147,7 @@ func (s *DNSServer) handleDoHRequest(w http.ResponseWriter, r *http.Request) { logEntry := s.processQuery(req) - go s.WSCom(communicationMessage{false, false, true, clientIP}) + go s.WSCom(communicationMessage{IP: clientIP, Client: false, Upstream: false, DNS: true}) select { case s.logEntryChannel <- logEntry: @@ -162,7 +162,7 @@ func (s *DNSServer) handleDoHGet(r *http.Request) ([]byte, error) { return nil, fmt.Errorf("missing dns parameter") } - if len(dnsParam) > MaxDoHRequestSize { + if len(dnsParam) > maxDoHRequestSize { return nil, fmt.Errorf("dns parameter too long") } @@ -184,7 +184,7 @@ func (s *DNSServer) handleDoHPost(r *http.Request) ([]byte, error) { return nil, fmt.Errorf("invalid content type: %s", contentType) } - limitedReader := io.LimitReader(r.Body, MaxDoHRequestSize) + limitedReader := io.LimitReader(r.Body, maxDoHRequestSize) dnsQuery, err := io.ReadAll(limitedReader) if err != nil { return nil, fmt.Errorf("failed to read request body: %w", err) @@ -266,4 +266,4 @@ func (w *DoHResponseWriter) Header() http.Header { return nil } func (w *DoHResponseWriter) Network() string { return "tcp" } -func (w *DoHResponseWriter) WriteHeader(statusCode int) {} +func (w *DoHResponseWriter) WriteHeader(_ int) {} diff --git a/backend/dns/server/dot.go b/backend/dns/server/dot.go index bd201c3..3348bb0 100644 --- a/backend/dns/server/dot.go +++ b/backend/dns/server/dot.go @@ -10,7 +10,7 @@ import ( func (s *DNSServer) InitDoT(cert tls.Certificate) (*dns.Server, error) { notifyReady := func() { - log.Info("Started DoT (dns-over-tls) server on port %d", s.Config.DNS.DoTPort) + log.Info("Started DoT (dns-over-tls) server on port %d", s.Config.DNS.Ports.DoT) } tlsConfig := &tls.Config{ @@ -18,7 +18,7 @@ func (s *DNSServer) InitDoT(cert tls.Certificate) (*dns.Server, error) { MinVersion: tls.VersionTLS12, } server := &dns.Server{ - Addr: fmt.Sprintf("%s:%d", s.Config.DNS.Address, s.Config.DNS.DoTPort), + Addr: fmt.Sprintf("%s:%d", s.Config.DNS.Address, s.Config.DNS.Ports.DoT), Net: "tcp-tls", Handler: s, TLSConfig: tlsConfig, diff --git a/backend/dns/server/handler.go b/backend/dns/server/handler.go index 4f1ec92..9ddb8b1 100644 --- a/backend/dns/server/handler.go +++ b/backend/dns/server/handler.go @@ -5,9 +5,8 @@ import ( "context" "fmt" arp "goaway/backend/dns" - "goaway/backend/dns/database" model "goaway/backend/dns/server/models" - notification "goaway/backend/notifications" + "goaway/backend/notification" "net" "os" "os/exec" @@ -24,8 +23,13 @@ var ( blackholeIPv6 = net.ParseIP("::") ) +const ( + IPv4Loopback = "127.0.0.1" + unknownHostname = "unknown" +) + func trimDomainDot(name string) string { - if len(name) > 0 && name[len(name)-1] == '.' { + if name != "" && name[len(name)-1] == '.' { return name[:len(name)-1] } return name @@ -37,15 +41,15 @@ func isPTRQuery(request *Request, domainName string) bool { func (s *DNSServer) checkAndUpdatePauseStatus() { if s.Config.DNS.Status.Paused && - time.Since(s.Config.DNS.Status.PausedAt).Seconds() >= float64(s.Config.DNS.Status.PauseTime) { + s.Config.DNS.Status.PausedAt.After(s.Config.DNS.Status.PauseTime) { s.Config.DNS.Status.Paused = false } } func (s *DNSServer) shouldBlockQuery(domainName, fullName string) bool { return !s.Config.DNS.Status.Paused && - s.Blacklist.IsBlacklisted(domainName) && - !s.Whitelist.IsWhitelisted(fullName) + s.BlacklistService.IsBlacklisted(domainName) && + !s.WhitelistService.IsWhitelisted(fullName) } func (s *DNSServer) processQuery(request *Request) model.RequestLogEntry { @@ -77,20 +81,6 @@ func (s *DNSServer) processQuery(request *Request) model.RequestLogEntry { return s.handleStandardQuery(request) } -func (s *DNSServer) GetVendor(mac string) (string, error) { - s.DBManager.Mutex.Lock() - defer s.DBManager.Mutex.Unlock() - return database.FindVendor(s.DBManager.Conn, mac) -} - -func (s *DNSServer) SaveMacVendor(clientIP, mac, vendor string) { - s.DBManager.Mutex.Lock() - defer s.DBManager.Mutex.Unlock() - - log.Debug("Saving new MAC address: %s %s", mac, vendor) - database.SaveMacEntry(s.DBManager.Conn, clientIP, mac, vendor) -} - func (s *DNSServer) reverseHostnameLookup(requestedHostname string) (string, bool) { trimmed := strings.TrimSuffix(requestedHostname, ".") @@ -114,24 +104,24 @@ func (s *DNSServer) getClientInfo(remoteAddr string) *model.Client { hostname := s.resolveHostname(clientIP) resultIP := clientIP - vendor, err := s.GetVendor(macAddress) - if macAddress != "unknown" { + vendor, err := s.MACService.FindVendor(macAddress) + if macAddress != unknownHostname { if err != nil || vendor == "" { log.Debug("Lookup vendor for mac %s", macAddress) vendor, err = arp.GetMacVendor(macAddress) if err == nil { - s.SaveMacVendor(clientIP, macAddress, vendor) + s.MACService.SaveMac(clientIP, macAddress, vendor) } else { - log.Warning("Error while lookup mac address vendor: %v", err) + log.Warning("Was not able to find vendor for addr '%s'. %v", remoteAddr, err) } } } - if clientIP == "127.0.0.1" || clientIP == "::1" || clientIP == "[" { + if clientIP == IPv4Loopback || clientIP == "::1" || clientIP == "[" { localIP, err := getLocalIP() if err != nil { log.Warning("Failed to get local IP: %v", err) - localIP = "127.0.0.1" + localIP = IPv4Loopback } resultIP = localIP @@ -145,7 +135,7 @@ func (s *DNSServer) getClientInfo(remoteAddr string) *model.Client { client := model.Client{IP: resultIP, Name: hostname, MAC: macAddress} s.clientCache.Store(clientIP, &client) - if client.Name != "unknown" { + if client.Name != unknownHostname { s.hostnameCache.Store(client.Name, client.IP) } @@ -153,19 +143,27 @@ func (s *DNSServer) getClientInfo(remoteAddr string) *model.Client { } func (s *DNSServer) resolveHostname(clientIP string) string { - if hostname := s.reverseDNSLookup(clientIP); hostname != "unknown" { + ip := net.ParseIP(clientIP) + if ip.IsLoopback() { + hostname, err := os.Hostname() + if err == nil { + return hostname + } + } + + if hostname := s.reverseDNSLookup(clientIP); hostname != unknownHostname { return hostname } - if hostname := s.avahiLookup(clientIP); hostname != "unknown" { + if hostname := s.avahiLookup(clientIP); hostname != unknownHostname { return hostname } - if hostname := s.sshBannerLookup(clientIP); hostname != "unknown" { + if hostname := s.sshBannerLookup(clientIP); hostname != unknownHostname { return hostname } - return "unknown" + return unknownHostname } func (s *DNSServer) avahiLookup(clientIP string) string { @@ -175,8 +173,8 @@ func (s *DNSServer) avahiLookup(clientIP string) string { cmd := exec.CommandContext(ctx, "avahi-resolve-address", clientIP) output, err := cmd.Output() if err == nil { - lines := strings.Split(string(output), "\n") - for _, line := range lines { + lines := strings.SplitSeq(string(output), "\n") + for line := range lines { if strings.Contains(line, clientIP) { parts := strings.Fields(line) if len(parts) >= 2 { @@ -190,7 +188,7 @@ func (s *DNSServer) avahiLookup(clientIP string) string { } } - return "unknown" + return unknownHostname } func (s *DNSServer) reverseDNSLookup(clientIP string) string { @@ -206,13 +204,13 @@ func (s *DNSServer) reverseDNSLookup(clientIP string) string { return hostname } } - return "unknown" + return unknownHostname } func (s *DNSServer) sshBannerLookup(clientIP string) string { conn, err := net.DialTimeout("tcp", clientIP+":22", 1*time.Second) if err != nil { - return "unknown" + return unknownHostname } defer func() { _ = conn.Close() @@ -222,13 +220,13 @@ func (s *DNSServer) sshBannerLookup(clientIP string) string { if err != nil { log.Warning("Failed to set deadline for SSH banner lookup: %v", err) _ = conn.Close() - return "unknown" + return unknownHostname } reader := bufio.NewReader(conn) banner, err := reader.ReadString('\n') if err != nil { - return "unknown" + return unknownHostname } patterns := []*regexp.Regexp{ @@ -248,7 +246,7 @@ func (s *DNSServer) sshBannerLookup(clientIP string) string { } } - return "unknown" + return unknownHostname } func getLocalIP() (string, error) { @@ -265,7 +263,7 @@ func getLocalIP() (string, error) { } } - return "127.0.0.1", fmt.Errorf("no non-loopback IPv4 address found") + return IPv4Loopback, fmt.Errorf("no non-loopback IPv4 address found") } func (s *DNSServer) handlePTRQuery(request *Request) model.RequestLogEntry { @@ -277,7 +275,7 @@ func (s *DNSServer) handlePTRQuery(request *Request) model.RequestLogEntry { } ipStr := strings.Join(parts, ".") - if ipStr == "127.0.0.1" { + if ipStr == IPv4Loopback { return s.respondWithLocalhost(request) } @@ -285,12 +283,12 @@ func (s *DNSServer) handlePTRQuery(request *Request) model.RequestLogEntry { return s.forwardPTRQueryUpstream(request) } - hostname := database.GetClientNameFromRequestLog(s.DBManager.Conn, ipStr) - if hostname == "unknown" { + hostname := s.RequestService.GetClientNameFromIP(ipStr) + if hostname == unknownHostname { hostname = s.resolveHostname(ipStr) } - if hostname != "unknown" { + if hostname != unknownHostname { return s.respondWithHostnamePTR(request, hostname) } @@ -540,11 +538,11 @@ func (s *DNSServer) handleStandardQuery(request *Request) model.RequestLogEntry err := request.ResponseWriter.WriteMsg(request.Msg) if err != nil { log.Warning("Could not write query response. client: [%s] with query [%v], err: %v", request.Client.IP, request.Msg.Answer, err.Error()) - s.Notifications.CreateNotification(¬ification.Notification{ - Severity: notification.SeverityWarning, - Category: notification.CategoryDNS, - Text: fmt.Sprintf("Could not write query response. Client: %s, err: %v", request.Client.IP, err.Error()), - }) + s.NotificationService.SendNotification( + notification.SeverityWarning, + notification.CategoryDNS, + fmt.Sprintf("Could not write query response. Client: %s, err: %v", request.Client.IP, err.Error()), + ) } return model.RequestLogEntry{ @@ -563,7 +561,7 @@ func (s *DNSServer) handleStandardQuery(request *Request) model.RequestLogEntry func (s *DNSServer) Resolve(req *Request) ([]dns.RR, bool, string) { cacheKey := req.Question.Name + ":" + strconv.Itoa(int(req.Question.Qtype)) - if cached, found := s.Cache.Load(cacheKey); found { + if cached, found := s.DomainCache.Load(cacheKey); found { if ipAddresses, valid := s.getCachedRecord(cached); valid { return ipAddresses, true, dns.RcodeToString[dns.RcodeSuccess] } @@ -593,7 +591,7 @@ func (s *DNSServer) resolveResolution(domain string) ([]dns.RR, uint32, string) status = dns.RcodeToString[dns.RcodeSuccess] ) - ipFound, err := database.FetchResolution(s.DBManager.Conn, domain) + ipFound, err := s.ResolutionService.GetResolution(domain) if err != nil { log.Error("Database lookup error for domain (%s): %v", domain, err) return nil, 0, dns.RcodeToString[dns.RcodeServerFailure] @@ -648,14 +646,14 @@ func (s *DNSServer) QueryUpstream(req *Request) ([]dns.RR, uint32, string) { errCh := make(chan error, 1) go func() { - go s.WSCom(communicationMessage{false, true, false, ""}) + go s.WSCom(communicationMessage{IP: "", Client: false, Upstream: true, DNS: false}) upstreamMsg := &dns.Msg{} upstreamMsg.SetQuestion(req.Question.Name, req.Question.Qtype) upstreamMsg.RecursionDesired = true upstreamMsg.Id = dns.Id() - upstream := s.Config.DNS.PreferredUpstream + upstream := s.Config.DNS.Upstream.Preferred if s.dnsClient.Net == "tcp-tls" { host, port, err := net.SplitHostPort(upstream) if err != nil { @@ -681,7 +679,7 @@ func (s *DNSServer) QueryUpstream(req *Request) ([]dns.RR, uint32, string) { select { case in := <-resultCh: - go s.WSCom(communicationMessage{false, false, true, ""}) + go s.WSCom(communicationMessage{IP: "", Client: false, Upstream: false, DNS: true}) status := dns.RcodeToString[dns.RcodeServerFailure] if statusStr, ok := dns.RcodeToString[in.Rcode]; ok { @@ -713,11 +711,11 @@ func (s *DNSServer) QueryUpstream(req *Request) ([]dns.RR, uint32, string) { case err := <-errCh: log.Warning("Resolution error for domain (%s): %v", req.Question.Name, err) - s.Notifications.CreateNotification(¬ification.Notification{ - Severity: notification.SeverityWarning, - Category: notification.CategoryDNS, - Text: fmt.Sprintf("Resolution error for domain (%s)", req.Question.Name), - }) + s.NotificationService.SendNotification( + notification.SeverityWarning, + notification.CategoryDNS, + fmt.Sprintf("Resolution error for domain (%s)", req.Question.Name), + ) return nil, 0, dns.RcodeToString[dns.RcodeServerFailure] case <-time.After(5 * time.Second): @@ -733,8 +731,13 @@ func (s *DNSServer) LocalForwardLookup(req *Request) (model.RequestLogEntry, err hostname += "." } + queryType := req.Question.Qtype + if queryType == 0 { + queryType = dns.TypeA + } + dnsMsg := new(dns.Msg) - dnsMsg.SetQuestion(hostname, dns.TypeA) + dnsMsg.SetQuestion(hostname, queryType) client := &dns.Client{Net: "udp"} start := time.Now() @@ -759,7 +762,7 @@ func (s *DNSServer) LocalForwardLookup(req *Request) (model.RequestLogEntry, err } } - if len(ips) == 0 { + if len(ips) == 0 && queryType == dns.TypeA { return model.RequestLogEntry{}, fmt.Errorf("no A records found for hostname: %s", hostname) } @@ -772,7 +775,7 @@ func (s *DNSServer) LocalForwardLookup(req *Request) (model.RequestLogEntry, err entry := model.RequestLogEntry{ Domain: req.Question.Name, Status: dns.RcodeToString[in.Rcode], - QueryType: dns.TypeToString[dns.TypeA], + QueryType: dns.TypeToString[queryType], IP: ips, ResponseSizeBytes: in.Len(), Timestamp: start, diff --git a/backend/dns/server/logs.go b/backend/dns/server/logs.go index 202c41e..59d2953 100644 --- a/backend/dns/server/logs.go +++ b/backend/dns/server/logs.go @@ -2,14 +2,13 @@ package server import ( "encoding/json" - "goaway/backend/dns/database" model "goaway/backend/dns/server/models" "time" "github.com/gorilla/websocket" ) -const BatchSize = 1000 +const batchSize = 1000 func (s *DNSServer) ProcessLogEntries() { var batch []model.RequestLogEntry @@ -26,7 +25,7 @@ func (s *DNSServer) ProcessLogEntries() { } batch = append(batch, entry) - if len(batch) >= BatchSize { + if len(batch) >= batchSize { s.saveBatch(batch) batch = nil } @@ -40,14 +39,13 @@ func (s *DNSServer) ProcessLogEntries() { } func (s *DNSServer) saveBatch(entries []model.RequestLogEntry) { - s.DBManager.Mutex.Lock() - err := database.SaveRequestLog(s.DBManager.Conn, entries) - s.DBManager.Mutex.Unlock() + err := s.RequestService.SaveRequestLog(entries) if err != nil { log.Warning("Error while saving logs, reason: %v", err) } } +// Removes old log entries based on the configured retention period. func (s *DNSServer) ClearOldEntries() { const ( maxRetries = 10 @@ -56,10 +54,10 @@ func (s *DNSServer) ClearOldEntries() { ) for { - requestThreshold := ((60 * 60) * 24) * s.Config.StatisticsRetention + requestThreshold := ((60 * 60) * 24) * s.Config.Misc.StatisticsRetention log.Debug("Next cleanup running at %s", time.Now().Add(cleanupInterval).Format(time.DateTime)) time.Sleep(cleanupInterval) - database.DeleteRequestLogsTimebased(s.Blacklist.Vacuum, s.DBManager.Conn, requestThreshold, maxRetries, retryDelay) + s.RequestService.DeleteRequestLogsTimebased(s.BlacklistService.Vacuum, requestThreshold, maxRetries, retryDelay) } } diff --git a/backend/dns/server/models/request.go b/backend/dns/server/models/request.go index 12cc060..28c6a8e 100644 --- a/backend/dns/server/models/request.go +++ b/backend/dns/server/models/request.go @@ -3,18 +3,18 @@ package model import "time" type RequestLogEntry struct { - ID int64 `json:"id"` + Timestamp time.Time `json:"timestamp"` + ClientInfo *Client `json:"client"` Domain string `json:"domain"` Status string `json:"status"` QueryType string `json:"queryType"` + Protocol Protocol `json:"protocol"` IP []ResolvedIP `json:"ip"` + ID uint `json:"id"` ResponseSizeBytes int `json:"responseSizeBytes"` - Timestamp time.Time `json:"timestamp"` ResponseTime time.Duration `json:"responseTimeNS"` Blocked bool `json:"blocked"` Cached bool `json:"cached"` - ClientInfo *Client `json:"client"` - Protocol Protocol `json:"protocol"` } type Protocol string @@ -39,8 +39,8 @@ type RequestLogIntervalSummary struct { } type ResponseSizeSummary struct { - StartUnix int64 `json:"-"` Start time.Time `json:"start"` + StartUnix int64 `json:"-"` TotalSizeBytes int `json:"total_size_bytes"` AvgResponseSizeBytes int `json:"avg_response_size_bytes"` MinResponseSizeBytes int `json:"min_response_size_bytes"` diff --git a/backend/dns/server/prefetch/prefetch.go b/backend/dns/server/prefetch/prefetch.go deleted file mode 100644 index 722e7b9..0000000 --- a/backend/dns/server/prefetch/prefetch.go +++ /dev/null @@ -1,204 +0,0 @@ -package prefetch - -import ( - "fmt" - "goaway/backend/dns/database" - "goaway/backend/dns/server" - "goaway/backend/logging" - "strconv" - "time" - - "github.com/miekg/dns" -) - -var log = logging.GetLogger() - -type Manager struct { - dbManager *database.DatabaseManager - DNS *server.DNSServer - Domains map[string]PrefetchedDomain -} - -type PrefetchedDomain struct { - Domain string `json:"domain"` - Refresh int `json:"refresh"` - Qtype int `json:"qtype"` -} - -func (manager *Manager) LoadPrefetchedDomains() { - var prefetched []database.Prefetch - if err := manager.dbManager.Conn.Find(&prefetched).Error; err != nil { - log.Warning("Failed to query prefetch table: %v", err) - return - } - - for _, p := range prefetched { - manager.Domains[p.Domain] = PrefetchedDomain{ - Domain: p.Domain, - Refresh: p.Refresh, - Qtype: p.QType, - } - } - - if len(manager.Domains) > 0 { - log.Info("Loaded %d prefetched domain(s)", len(manager.Domains)) - } -} - -func (manager *Manager) AddPrefetchedDomain(domain string, refresh, qtype int) error { - prefetch := database.Prefetch{ - Domain: domain, - Refresh: refresh, - QType: qtype, - } - - result := manager.dbManager.Conn.FirstOrCreate(&prefetch, database.Prefetch{Domain: domain}) - if result.Error != nil { - return fmt.Errorf("failed to add new domain to prefetch table: %w", result.Error) - } - - if result.RowsAffected == 0 { - return fmt.Errorf("%s already exists", domain) - } - - manager.Domains[domain] = PrefetchedDomain{ - Domain: domain, - Refresh: refresh, - Qtype: qtype, - } - - log.Info("%s was added as a prefetched domain", domain) - return nil -} - -func (manager *Manager) RemovePrefetchedDomain(domain string) error { - result := manager.dbManager.Conn.Delete(&database.Prefetch{}, "domain = ?", domain) - if result.Error != nil { - return fmt.Errorf("failed to remove %s from prefetch table: %w", domain, result.Error) - } - - if result.RowsAffected == 0 { - return fmt.Errorf("%s does not exist in the database", domain) - } - - delete(manager.Domains, domain) - log.Info("%s was removed as a prefetched domain", domain) - return nil -} - -func New(dnsServer *server.DNSServer) Manager { - manager := Manager{ - dbManager: dnsServer.DBManager, - DNS: dnsServer, - Domains: make(map[string]PrefetchedDomain), - } - - manager.LoadPrefetchedDomains() - return manager -} - -func (manager *Manager) Run() { - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - - for range ticker.C { - manager.checkNewDomains() - manager.processExpiredEntries() - } -} - -func (manager *Manager) checkNewDomains() { - for domain, prefetchDomain := range manager.Domains { - cacheKey := manager.buildCacheKey(domain, dns.Type(prefetchDomain.Qtype)) - if _, exists := manager.DNS.Cache.Load(cacheKey); !exists { - log.Debug("Prefetching new/missing domain: %s", domain) - manager.prefetchDomain(prefetchDomain) - } - } -} - -func (manager *Manager) processExpiredEntries() { - now := time.Now() - var expiredKeys []interface{} - var removeFromDomains []string - - manager.DNS.Cache.Range(func(key, value interface{}) bool { - cachedDomain, ok := value.(server.CachedRecord) - if !ok { - log.Debug("Cache entry type assertion failed for key: %v", key) - return true - } - - if manager.isExpired(cachedDomain, now) { - expiredKeys = append(expiredKeys, key) - - if _, isPrefetched := manager.Domains[cachedDomain.Domain]; !isPrefetched { - removeFromDomains = append(removeFromDomains, cachedDomain.Domain) - log.Debug("Non-prefetch entry '%v' expired and will be removed", key) - } else { - log.Debug("Prefetch entry '%v' expired and will be refreshed", key) - } - } - return true - }) - - manager.handleExpiredKeys(expiredKeys) - manager.removeNonPrefetchDomains(removeFromDomains) -} - -func (manager *Manager) isExpired(record server.CachedRecord, now time.Time) bool { - return now.After(record.ExpiresAt) || now.Equal(record.ExpiresAt) -} - -func (manager *Manager) handleExpiredKeys(expiredKeys []interface{}) { - for _, key := range expiredKeys { - if value, exists := manager.DNS.Cache.Load(key); exists { - if cachedDomain, ok := value.(server.CachedRecord); ok { - manager.DNS.Cache.Delete(key) - manager.handleExpiredEntry(cachedDomain) - } - } - } -} - -func (manager *Manager) removeNonPrefetchDomains(domains []string) { - for _, domain := range domains { - delete(manager.Domains, domain) - } -} - -func (manager *Manager) prefetchDomain(prefetchDomain PrefetchedDomain) { - question := dns.Question{ - Name: prefetchDomain.Domain, - Qtype: uint16(prefetchDomain.Qtype), - Qclass: 1, - } - - request := &server.Request{ - Msg: &dns.Msg{Question: []dns.Question{question}}, - Question: question, - Sent: time.Now(), - Prefetch: true, - } - - answers, ttl, _ := manager.DNS.QueryUpstream(request) - cacheKey := manager.buildCacheKey(question.Name, dns.Type(question.Qtype)) - manager.DNS.CacheRecord(cacheKey, prefetchDomain.Domain, answers, ttl) -} - -func (manager *Manager) handleExpiredEntry(record server.CachedRecord) { - domain := record.IPAddresses[0].Header().Name - prefetchDomain, exists := manager.Domains[domain] - - if !exists { - log.Debug("%s not set to be prefetched", domain) - return - } - - log.Debug("Prefetching expired domain: %s", domain) - manager.prefetchDomain(prefetchDomain) -} - -func (manager *Manager) buildCacheKey(domain string, qtype dns.Type) string { - return domain + ":" + strconv.Itoa(int(qtype)) -} diff --git a/backend/dns/server/server.go b/backend/dns/server/server.go index 1a94258..c98f148 100644 --- a/backend/dns/server/server.go +++ b/backend/dns/server/server.go @@ -3,22 +3,25 @@ package server import ( "crypto/tls" "encoding/json" - "fmt" "goaway/backend/alert" "goaway/backend/audit" - "goaway/backend/dns/database" - "goaway/backend/dns/lists" + "goaway/backend/blacklist" model "goaway/backend/dns/server/models" "goaway/backend/logging" - notification "goaway/backend/notifications" + "goaway/backend/mac" + "goaway/backend/notification" + "goaway/backend/request" + "goaway/backend/resolution" "goaway/backend/settings" + "goaway/backend/user" + "goaway/backend/whitelist" "net" "sync" "time" - "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/miekg/dns" + "gorm.io/gorm" ) var ( @@ -26,74 +29,66 @@ var ( ) type DNSServer struct { + DBConn *gorm.DB + dnsClient *dns.Client Config *settings.Config - Blacklist *lists.Blacklist - Whitelist *lists.Whitelist - DBManager *database.DatabaseManager - logIntervalSeconds int - lastLogTime time.Time - Cache sync.Map - clientCache sync.Map - hostnameCache sync.Map - WebServer *gin.Engine logEntryChannel chan model.RequestLogEntry WSQueries *websocket.Conn WSCommunication *websocket.Conn + hostnameCache sync.Map + clientCache sync.Map + DomainCache sync.Map WSCommunicationLock sync.Mutex - dnsClient *dns.Client - Notifications *notification.Manager - Alerts *alert.Manager - Audits *audit.Manager + + RequestService *request.Service + AuditService *audit.Service + UserService *user.Service + AlertService *alert.Service + MACService *mac.Service + ResolutionService *resolution.Service + NotificationService *notification.Service + BlacklistService *blacklist.Service + WhitelistService *whitelist.Service } type CachedRecord struct { - IPAddresses []dns.RR ExpiresAt time.Time CachedAt time.Time - OriginalTTL uint32 Key string Domain string + IPAddresses []dns.RR + OriginalTTL uint32 } type Request struct { + Sent time.Time ResponseWriter dns.ResponseWriter Msg *dns.Msg - Question dns.Question - Sent time.Time Client *model.Client - Prefetch bool Protocol model.Protocol + Question dns.Question + Prefetch bool } type communicationMessage struct { + IP string `json:"ip"` Client bool `json:"client"` Upstream bool `json:"upstream"` DNS bool `json:"dns"` - Ip string `json:"ip"` } -func NewDNSServer(config *settings.Config, dbManager *database.DatabaseManager, notificationsManager *notification.Manager, alertManager *alert.Manager, auditManager *audit.Manager, cert tls.Certificate) (*DNSServer, error) { - whitelistEntry, err := lists.InitializeWhitelist(dbManager) - if err != nil { - return nil, fmt.Errorf("failed to initialize whitelist: %w", err) - } - +func NewDNSServer(config *settings.Config, dbconn *gorm.DB, cert tls.Certificate) (*DNSServer, error) { var client dns.Client if cert.Certificate != nil { client = dns.Client{Net: "tcp-tls"} } server := &DNSServer{ - Config: config, - Whitelist: whitelistEntry, - DBManager: dbManager, - logIntervalSeconds: 1, - lastLogTime: time.Now(), - logEntryChannel: make(chan model.RequestLogEntry, 1000), - dnsClient: &client, - Notifications: notificationsManager, - Alerts: alertManager, - Audits: auditManager, + Config: config, + DBConn: dbconn, + logEntryChannel: make(chan model.RequestLogEntry, 1000), + dnsClient: &client, + DomainCache: sync.Map{}, } return server, nil @@ -101,7 +96,7 @@ func NewDNSServer(config *settings.Config, dbManager *database.DatabaseManager, func (s *DNSServer) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { if len(r.Question) != 1 { - log.Warning("Query container more than one question, ignoring!") + log.Warning("Query contains more than one question, ignoring!") r.SetRcode(r, dns.RcodeFormatError) _ = w.WriteMsg(r) return @@ -114,7 +109,7 @@ func (s *DNSServer) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { Client: true, Upstream: false, DNS: false, - Ip: client.IP, + IP: client.IP, }) entry := s.processQuery(&Request{ @@ -131,7 +126,7 @@ func (s *DNSServer) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { Client: false, Upstream: false, DNS: true, - Ip: client.IP, + IP: client.IP, }) s.logEntryChannel <- entry } @@ -154,27 +149,12 @@ func (s *DNSServer) detectProtocol(w dns.ResponseWriter) model.Protocol { } func (s *DNSServer) PopulateHostnameCache() error { - type Result struct { - ClientIP string - ClientName string - } - - var results []Result - - if err := s.DBManager.Conn. - Model(&database.RequestLog{}). - Select("DISTINCT client_ip, client_name"). - Where("client_name IS NOT NULL AND client_name != ?", "unknown"). - Find(&results).Error; err != nil { - return fmt.Errorf("failed to fetch hostnames: %w", err) - } - - for _, r := range results { - if _, exists := s.hostnameCache.Load(r.ClientName); !exists { - s.hostnameCache.Store(r.ClientName, r.ClientIP) - } + uniqueClients := s.RequestService.GetUniqueClientNameAndIP() + for _, client := range uniqueClients { + _, _ = s.hostnameCache.LoadOrStore(client.IP, client.Name) } + log.Debug("Populated hostname cache with %d client(s)", len(uniqueClients)) return nil } @@ -186,12 +166,7 @@ func (s *DNSServer) WSCom(message communicationMessage) { s.WSCommunicationLock.Lock() defer s.WSCommunicationLock.Unlock() - if err := s.WSCommunication.WriteControl( - websocket.PingMessage, - nil, - time.Now().Add(2*time.Second), - ); err != nil { - log.Debug("Websocket connection not alive, skipping message: %v", err) + if s.WSCommunication == nil { return } @@ -201,11 +176,13 @@ func (s *DNSServer) WSCom(message communicationMessage) { return } - if err := s.WSCommunication.SetWriteDeadline(time.Now().Add(2 * time.Second)); err != nil { + if err := s.WSCommunication.SetWriteDeadline(time.Now().Add(5 * time.Second)); err != nil { log.Warning("Failed to set websocket write deadline: %v", err) + return } if err := s.WSCommunication.WriteMessage(websocket.TextMessage, entryWSJson); err != nil { log.Debug("Failed to write websocket message: %v", err) + s.WSCommunication = nil } } diff --git a/backend/jobs/background.go b/backend/jobs/background.go new file mode 100644 index 0000000..87a0bf7 --- /dev/null +++ b/backend/jobs/background.go @@ -0,0 +1,76 @@ +package jobs + +import ( + arp "goaway/backend/dns" + "goaway/backend/logging" + "goaway/backend/services" +) + +var log = logging.GetLogger() + +type BackgroundJobs struct { + registry *services.ServiceRegistry +} + +func NewBackgroundJobs(registry *services.ServiceRegistry) *BackgroundJobs { + return &BackgroundJobs{ + registry: registry, + } +} + +func (b *BackgroundJobs) Start(readyChan <-chan struct{}) { + b.startHostnameCachePopulation() + b.cleanVendorResponseCache(readyChan) + b.startARPProcessing(readyChan) + b.startScheduledUpdates(readyChan) + b.startCacheCleanup(readyChan) + b.startPrefetcher(readyChan) +} + +func (b *BackgroundJobs) startHostnameCachePopulation() { + if err := b.registry.Context.DNSServer.PopulateHostnameCache(); err != nil { + log.Warning("Unable to populate hostname cache: %s", err) + } +} + +func (b *BackgroundJobs) startARPProcessing(readyChan <-chan struct{}) { + go func() { + <-readyChan + log.Debug("Starting ARP table processing...") + arp.ProcessARPTable() + }() +} + +func (b *BackgroundJobs) cleanVendorResponseCache(readyChan <-chan struct{}) { + go func() { + <-readyChan + log.Debug("Starting vendor response table processing...") + arp.CleanVendorResponseCache() + }() +} + +func (b *BackgroundJobs) startScheduledUpdates(readyChan <-chan struct{}) { + go func() { + <-readyChan + if b.registry.Context.Config.Misc.ScheduledBlacklistUpdates { + log.Debug("Starting scheduler for automatic list updates...") + b.registry.BlacklistService.ScheduleAutomaticListUpdates() + } + }() +} + +func (b *BackgroundJobs) startCacheCleanup(readyChan <-chan struct{}) { + go func() { + <-readyChan + log.Debug("Starting cache cleanup routine...") + b.registry.Context.DNSServer.ClearOldEntries() + }() +} + +func (b *BackgroundJobs) startPrefetcher(readyChan <-chan struct{}) { + go func() { + <-readyChan + log.Debug("Starting prefetcher...") + b.registry.PrefetchService.Run() + }() +} diff --git a/backend/lifecycle/manager.go b/backend/lifecycle/manager.go new file mode 100644 index 0000000..8a4bee6 --- /dev/null +++ b/backend/lifecycle/manager.go @@ -0,0 +1,61 @@ +package lifecycle + +import ( + "goaway/backend/jobs" + "goaway/backend/logging" + "goaway/backend/services" + "os" + "os/signal" + "syscall" +) + +var log = logging.GetLogger() + +// Coordinates startup, shutdown, and signal handling +type Manager struct { + services *services.ServiceRegistry + backgroundJobs *jobs.BackgroundJobs + signalChan chan os.Signal +} + +func NewManager(registry *services.ServiceRegistry) *Manager { + return &Manager{ + services: registry, + signalChan: make(chan os.Signal, 1), + } +} + +func (m *Manager) Run() error { + if err := m.services.Initialize(); err != nil { + return err + } + + m.backgroundJobs = jobs.NewBackgroundJobs(m.services) + + signal.Notify(m.signalChan, syscall.SIGINT, syscall.SIGTERM) + + m.services.StartAll() + m.backgroundJobs.Start(m.services.ReadyChannel()) + + go m.services.WaitGroup().Wait() + + return m.waitForTermination() +} + +func (m *Manager) waitForTermination() error { + select { + case err := <-m.services.ErrorChannel(): + log.Error("%s server failed: %s", err.Service, err.Err) + log.Fatal("Server failure detected. Exiting.") + return err.Err + case <-m.signalChan: + log.Info("Received interrupt. Shutting down.") + m.shutdown() + return nil + } +} + +func (m *Manager) shutdown() { + // TODO: Add graceful shutdown logic + os.Exit(0) +} diff --git a/backend/logging/logging.go b/backend/logging/logging.go index 3bef6c0..64c0e2a 100644 --- a/backend/logging/logging.go +++ b/backend/logging/logging.go @@ -9,11 +9,11 @@ import ( ) const ( - ColorReset = "\033[0m" - ColorGray = "\033[90m" - ColorWhite = "\033[97m" - ColorYellow = "\033[33m" - ColorRed = "\033[31m" + colorReset = "\033[0m" + colorGray = "\033[90m" + colorWhite = "\033[97m" + colorYellow = "\033[33m" + colorRed = "\033[31m" ) type LogLevel int @@ -27,12 +27,12 @@ const ( ) type Logger struct { + logger *log.Logger + JSONLoggerInstance *slog.Logger logLevel LogLevel LoggingEnabled bool Ansi bool - logger *log.Logger JSON bool - JSONLoggerInstance *slog.Logger } var ( @@ -66,7 +66,7 @@ func (l *Logger) SetLevel(level LogLevel) { } } -func (l *Logger) SetJson(json bool) { +func (l *Logger) SetJSON(json bool) { l.JSON = json } @@ -74,10 +74,10 @@ func (l *Logger) SetAnsi(ansi bool) { l.Ansi = ansi } -func (l *Logger) log(level string, color string, message string, msgLevel LogLevel) { +func (l *Logger) log(level, color, message string, msgLevel LogLevel) { if !l.JSON { if l.Ansi { - l.logger.Printf("%s%s%s%s", color, level, message, ColorReset) + l.logger.Printf("%s%s%s%s", color, level, message, colorReset) } else { l.logger.Printf("%s%s", level, message) } @@ -91,6 +91,8 @@ func (l *Logger) log(level string, color string, message string, msgLevel LogLev l.JSONLoggerInstance.Warn(message) case ERROR: l.JSONLoggerInstance.Error(message) + default: + l.JSONLoggerInstance.Info(message) } } } @@ -105,9 +107,9 @@ func (l *Logger) Debug(format string, args ...interface{}) { } if len(args) > 0 { message := fmt.Sprintf(format, args...) - l.log("[DEBUG] ", ColorGray, message, DEBUG) + l.log("[DEBUG] ", colorGray, message, DEBUG) } else { - l.log("[DEBUG] ", ColorGray, format, DEBUG) + l.log("[DEBUG] ", colorGray, format, DEBUG) } } @@ -117,9 +119,9 @@ func (l *Logger) Info(format string, args ...interface{}) { } if len(args) > 0 { message := fmt.Sprintf(format, args...) - l.log("[INFO] ", ColorWhite, message, INFO) + l.log("[INFO] ", colorWhite, message, INFO) } else { - l.log("[INFO] ", ColorWhite, format, INFO) + l.log("[INFO] ", colorWhite, format, INFO) } } @@ -129,9 +131,9 @@ func (l *Logger) Warning(format string, args ...interface{}) { } if len(args) > 0 { message := fmt.Sprintf(format, args...) - l.log("[WARN] ", ColorYellow, message, WARNING) + l.log("[WARN] ", colorYellow, message, WARNING) } else { - l.log("[WARN] ", ColorYellow, format, WARNING) + l.log("[WARN] ", colorYellow, format, WARNING) } } @@ -141,9 +143,9 @@ func (l *Logger) Error(format string, args ...interface{}) { } if len(args) > 0 { message := fmt.Sprintf(format, args...) - l.log("[ERROR] ", ColorRed, message, ERROR) + l.log("[ERROR] ", colorRed, message, ERROR) } else { - l.log("[ERROR] ", ColorRed, format, ERROR) + l.log("[ERROR] ", colorRed, format, ERROR) } } @@ -153,39 +155,9 @@ func (l *Logger) Fatal(format string, args ...interface{}) { } if len(args) > 0 { message := fmt.Sprintf(format, args...) - l.log("[FATAL] ", ColorRed, message, FATAL) + l.log("[FATAL] ", colorRed, message, FATAL) } else { - l.log("[FATAL] ", ColorRed, format, FATAL) + l.log("[FATAL] ", colorRed, format, FATAL) } os.Exit(1) } - -func FromString(logLevel string) LogLevel { - switch logLevel { - case "DEBUG": - return 0 - case "INFO": - return 1 - case "WARNING": - return 2 - case "ERROR": - return 3 - default: - return 1 - } -} - -func (l LogLevel) String() string { - switch l { - case DEBUG: - return "DEBUG" - case INFO: - return "INFO" - case WARNING: - return "WARNING" - case ERROR: - return "ERROR" - default: - return "UNKNOWN" - } -} diff --git a/backend/mac/repository.go b/backend/mac/repository.go new file mode 100644 index 0000000..093d16c --- /dev/null +++ b/backend/mac/repository.go @@ -0,0 +1,51 @@ +package mac + +import ( + "errors" + "fmt" + "goaway/backend/database" + + "gorm.io/gorm" +) + +type Repository interface { + FindVendor(mac string) (string, error) + SaveMac(clientIP, mac, vendor string) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) FindVendor(mac string) (string, error) { + var query database.MacAddress + tx := r.db.Find(&query, "mac = ?", mac) + + if errors.Is(tx.Error, gorm.ErrRecordNotFound) { + return "", nil + } + if tx.Error != nil { + return "", tx.Error + } + + return query.Vendor, nil +} + +func (r *repository) SaveMac(clientIP, mac, vendor string) error { + entry := database.MacAddress{ + MAC: mac, + IP: clientIP, + Vendor: vendor, + } + tx := r.db.Save(&entry) + + if tx.Error != nil { + return fmt.Errorf("unable to save new MAC entry %v", tx.Error) + } + + return nil +} diff --git a/backend/mac/service.go b/backend/mac/service.go new file mode 100644 index 0000000..f152bd7 --- /dev/null +++ b/backend/mac/service.go @@ -0,0 +1,24 @@ +package mac + +import "goaway/backend/logging" + +type Service struct { + repository Repository +} + +var log = logging.GetLogger() + +func NewService(repo Repository) *Service { + return &Service{repository: repo} +} + +func (s *Service) FindVendor(mac string) (string, error) { + return s.repository.FindVendor(mac) +} + +func (s *Service) SaveMac(clientIP, mac, vendor string) { + err := s.repository.SaveMac(clientIP, mac, vendor) + if err != nil { + log.Warning("Could not save MAC address, %v", err) + } +} diff --git a/backend/notification/repository.go b/backend/notification/repository.go new file mode 100644 index 0000000..72f56a0 --- /dev/null +++ b/backend/notification/repository.go @@ -0,0 +1,53 @@ +package notification + +import ( + "goaway/backend/database" + + "gorm.io/gorm" +) + +type Repository interface { + CreateNotification(newNotification *database.Notification) error + GetNotifications() ([]database.Notification, error) + MarkNotificationsAsRead(notificationIDs []int) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) CreateNotification(newNotification *database.Notification) error { + tx := r.db.Create(&newNotification) + return tx.Error +} + +func (r *repository) GetNotifications() ([]database.Notification, error) { + var notifications []database.Notification + + result := r.db.Where("read = ?", false).Find(¬ifications) + if result.Error != nil { + return nil, result.Error + } + + return notifications, nil +} + +func (r *repository) MarkNotificationsAsRead(notificationIDs []int) error { + if len(notificationIDs) == 0 { + return nil + } + + result := r.db.Model(&database.Notification{}). + Where("id IN ?", notificationIDs). + Update("read", true) + + if result.Error != nil { + return result.Error + } + + return nil +} diff --git a/backend/notification/service.go b/backend/notification/service.go new file mode 100644 index 0000000..d485531 --- /dev/null +++ b/backend/notification/service.go @@ -0,0 +1,62 @@ +package notification + +import ( + "goaway/backend/database" + "goaway/backend/logging" +) + +type Service struct { + repository Repository +} + +type Severity string +type Category string + +// Severity level of notification +// SeverityInfo: Server was upgraded, password changed... +// SeverityWarning: An error occurred on startup, database lock... +// SeverityError: Server cant start, requests cant be handled... +const ( + SeverityInfo Severity = "info" + SeverityWarning Severity = "warning" + SeverityError Severity = "error" +) + +// Categories to describe what area the notification covers +const ( + CategoryServer Category = "server" + CategoryDNS Category = "dns" + CategoryAPI Category = "api" +) + +var log = logging.GetLogger() + +func NewService(repo Repository) *Service { + return &Service{repository: repo} +} + +func (s *Service) SendNotification(severity Severity, category Category, text string) { + notification := &database.Notification{ + Severity: string(severity), + Category: string(category), + Text: text, + Read: false, + } + + err := s.repository.CreateNotification(notification) + if err != nil { + log.Warning("Could not send notification, %v", err) + return + } + + log.Info("New notification created, severity: %s", severity) +} + +func (s *Service) GetNotifications() ([]database.Notification, error) { + return s.repository.GetNotifications() +} + +func (s *Service) MarkNotificationsAsRead(notificationIDs []int) error { + log.Info("Notifications have been marked as read") + return s.repository.MarkNotificationsAsRead(notificationIDs) +} diff --git a/backend/notifications/notifications.go b/backend/notifications/notifications.go deleted file mode 100644 index 97e3f0b..0000000 --- a/backend/notifications/notifications.go +++ /dev/null @@ -1,89 +0,0 @@ -package notification - -import ( - "goaway/backend/dns/database" - "goaway/backend/logging" - "time" -) - -type Manager struct { - dbManager *database.DatabaseManager -} - -type Severity string -type Category string - -// Severity level of notification -// SeverityInfo: Server was upgraded, password changed... -// SeverityWarning: An error occurred on startup, database lock... -// SeverityError: Server cant start, requests cant be handled... -const ( - SeverityInfo Severity = "info" - SeverityWarning Severity = "warning" - SeverityError Severity = "error" -) - -// Categories to describe what area the notification covers -const ( - CategoryServer Category = "server" - CategoryDNS Category = "dns" - CategoryAPI Category = "api" -) - -type Notification struct { - Id int `json:"id"` - Severity Severity `json:"severity"` - Category Category `json:"category"` - Text string `json:"text"` - Read bool `json:"read"` - CreatedAt time.Time `json:"createdAt"` -} - -var logger = logging.GetLogger() - -func NewNotificationManager(dbManager *database.DatabaseManager) *Manager { - return &Manager{dbManager: dbManager} -} - -func (nm *Manager) CreateNotification(newNotification *Notification) { - tx := nm.dbManager.Conn.Create(&database.Notification{ - Severity: string(newNotification.Severity), - Category: string(newNotification.Category), - Text: newNotification.Text, - Read: false, - CreatedAt: time.Now(), - }) - if tx.Error != nil { - logger.Warning("Unable to create new notification, error: %v", tx.Error) - return - } - - logger.Debug("Created new notification, %+v", newNotification) -} - -func (nm *Manager) ReadNotifications() ([]database.Notification, error) { - var notifications []database.Notification - - result := nm.dbManager.Conn.Where("read = ?", true).Find(¬ifications) - if result.Error != nil { - return nil, result.Error - } - - return notifications, nil -} - -func (nm *Manager) MarkNotificationsAsRead(notificationIDs []int) error { - if len(notificationIDs) == 0 { - return nil - } - - result := nm.dbManager.Conn.Model(&database.Notification{}). - Where("id IN ?", notificationIDs). - Update("read", true) - - if result.Error != nil { - return result.Error - } - - return nil -} diff --git a/backend/prefetch/repository.go b/backend/prefetch/repository.go new file mode 100644 index 0000000..8306a24 --- /dev/null +++ b/backend/prefetch/repository.go @@ -0,0 +1,53 @@ +package prefetch + +import ( + "fmt" + "goaway/backend/database" + + "gorm.io/gorm" +) + +type Repository interface { + GetAll() ([]database.Prefetch, error) + Create(prefetch *database.Prefetch) error + Delete(domain string) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) Create(prefetch *database.Prefetch) error { + result := r.db.Create(prefetch) + if result.Error != nil { + return result.Error + } + + return nil +} + +func (r *repository) GetAll() ([]database.Prefetch, error) { + var prefetched []database.Prefetch + result := r.db.Model(&database.Prefetch{}).Find(&prefetched) + if result.Error != nil { + return nil, result.Error + } + return prefetched, nil +} + +func (r *repository) Delete(domain string) error { + result := r.db.Delete(&database.Prefetch{}, "domain = ?", domain) + if result.Error != nil { + return result.Error + } + + if result.RowsAffected == 0 { + return fmt.Errorf("%s does not exist in the database", domain) + } + + return nil +} diff --git a/backend/prefetch/service.go b/backend/prefetch/service.go new file mode 100644 index 0000000..c997ac3 --- /dev/null +++ b/backend/prefetch/service.go @@ -0,0 +1,182 @@ +package prefetch + +import ( + "fmt" + "goaway/backend/database" + "goaway/backend/dns/server" + "goaway/backend/logging" + "strconv" + "time" + + "github.com/miekg/dns" +) + +type Service struct { + repository Repository + DNS *server.DNSServer + Domains map[string]database.Prefetch +} + +var log = logging.GetLogger() + +func NewService(repo Repository, dnsServer *server.DNSServer) *Service { + service := &Service{ + repository: repo, + DNS: dnsServer, + Domains: make(map[string]database.Prefetch), + } + + service.LoadPrefetchedDomains() + return service +} + +func (s *Service) Run() { + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + + for range ticker.C { + s.checkNewDomains() + s.processExpiredEntries() + } +} + +func (s *Service) checkNewDomains() { + for domain, prefetchDomain := range s.Domains { + cacheKey := s.buildCacheKey(domain, dns.Type(prefetchDomain.QueryType)) + if _, exists := s.DNS.DomainCache.Load(cacheKey); !exists { + log.Debug("Prefetching new/missing domain: %s", domain) + s.prefetchDomain(prefetchDomain) + } + } +} + +func (s *Service) processExpiredEntries() { + now := time.Now() + var expiredKeys []interface{} + var removeFromDomains []string + + s.DNS.DomainCache.Range(func(key, value interface{}) bool { + cachedDomain, ok := value.(server.CachedRecord) + if !ok { + log.Debug("Cache entry type assertion failed for key: %v", key) + return true + } + + if s.isExpired(cachedDomain, now) { + expiredKeys = append(expiredKeys, key) + + if _, isPrefetched := s.Domains[cachedDomain.Domain]; !isPrefetched { + removeFromDomains = append(removeFromDomains, cachedDomain.Domain) + log.Debug("Non-prefetch entry '%v' expired and will be removed", key) + } else { + log.Debug("Prefetch entry '%v' expired and will be refreshed", key) + } + } + return true + }) + + s.handleExpiredKeys(expiredKeys) + s.removeNonPrefetchDomains(removeFromDomains) +} + +func (s *Service) isExpired(record server.CachedRecord, now time.Time) bool { + return now.After(record.ExpiresAt) || now.Equal(record.ExpiresAt) +} + +func (s *Service) handleExpiredKeys(expiredKeys []interface{}) { + for _, key := range expiredKeys { + if value, exists := s.DNS.DomainCache.Load(key); exists { + if cachedDomain, ok := value.(server.CachedRecord); ok { + s.DNS.DomainCache.Delete(key) + s.handleExpiredEntry(cachedDomain) + } + } + } +} + +func (s *Service) removeNonPrefetchDomains(domains []string) { + for _, domain := range domains { + delete(s.Domains, domain) + } +} + +func (s *Service) prefetchDomain(prefetchDomain database.Prefetch) { + question := dns.Question{ + Name: prefetchDomain.Domain, + Qtype: uint16(prefetchDomain.QueryType), + Qclass: 1, + } + + request := &server.Request{ + Msg: &dns.Msg{Question: []dns.Question{question}}, + Question: question, + Sent: time.Now(), + Prefetch: true, + } + + answers, ttl, _ := s.DNS.QueryUpstream(request) + cacheKey := s.buildCacheKey(question.Name, dns.Type(question.Qtype)) + s.DNS.CacheRecord(cacheKey, prefetchDomain.Domain, answers, ttl) +} + +func (s *Service) buildCacheKey(domain string, qtype dns.Type) string { + return domain + ":" + strconv.Itoa(int(qtype)) +} + +func (s *Service) handleExpiredEntry(record server.CachedRecord) { + domain := record.IPAddresses[0].Header().Name + prefetchDomain, exists := s.Domains[domain] + + if !exists { + log.Debug("%s not set to be prefetched", domain) + return + } + + log.Debug("Prefetching expired domain: %s", domain) + s.prefetchDomain(prefetchDomain) +} + +func (s *Service) LoadPrefetchedDomains() { + prefetched, err := s.repository.GetAll() + if err != nil { + log.Error("failed to load prefetched domains: %v", err) + return + } + + for _, p := range prefetched { + s.Domains[p.Domain] = p + } + + if len(s.Domains) > 0 { + log.Info("Loaded %d prefetched domain(s)", len(s.Domains)) + } +} + +func (s *Service) AddPrefetchedDomain(domain string, refresh, qtype int) error { + prefetch := database.Prefetch{ + Domain: domain, + Refresh: refresh, + QueryType: qtype, + } + + err := s.repository.Create(&prefetch) + if err != nil { + return fmt.Errorf("failed to add new domain to prefetch table: %w", err) + } + + s.Domains[domain] = prefetch + + log.Info("%s was added as a prefetched domain", domain) + return nil +} + +func (s *Service) RemovePrefetchedDomain(domain string) error { + err := s.repository.Delete(domain) + if err != nil { + return fmt.Errorf("failed to remove %s from prefetch table: %w", domain, err) + } + + delete(s.Domains, domain) + log.Info("%s was removed as a prefetched domain", domain) + return nil +} diff --git a/backend/request/model.go b/backend/request/model.go new file mode 100644 index 0000000..18dded1 --- /dev/null +++ b/backend/request/model.go @@ -0,0 +1,15 @@ +package request + +import "time" + +type Client struct { + LastSeen time.Time + Name string + Mac string + Vendor string +} + +type ClientNameAndIP struct { + Name string + IP string +} diff --git a/backend/dns/database/request.go b/backend/request/repository.go similarity index 61% rename from backend/dns/database/request.go rename to backend/request/repository.go index 7310525..8daddcd 100644 --- a/backend/dns/database/request.go +++ b/backend/request/repository.go @@ -1,24 +1,58 @@ -package database +package request import ( + "context" "database/sql" "fmt" "goaway/backend/api/models" - dbModel "goaway/backend/dns/database/models" + "goaway/backend/database" model "goaway/backend/dns/server/models" - "goaway/backend/logging" "strings" "time" "gorm.io/gorm" ) -var log = logging.GetLogger() +type Repository interface { + GetClientName(ip string) string + GetDistinctRequestIP() int + GetRequestSummaryByInterval(interval int) ([]model.RequestLogIntervalSummary, error) + GetResponseSizeSummaryByInterval(intervalMinutes int) ([]model.ResponseSizeSummary, error) + GetUniqueQueryTypes() ([]interface{}, error) + FetchQueries(q models.QueryParams) ([]model.RequestLogEntry, error) + GetUniqueClientNameAndIP() []database.RequestLog + FetchAllClients() (map[string]Client, error) + GetClientDetailsWithDomains(clientIP string) (ClientRequestDetails, string, map[string]int, error) + GetTopBlockedDomains(blockedRequests int) ([]map[string]interface{}, error) + GetTopClients() ([]map[string]interface{}, error) + CountQueries(search string) (int, error) -func GetClientNameFromRequestLog(db *gorm.DB, ip string) string { + SaveRequestLog(entries []model.RequestLogEntry) error + + DeleteRequestLogsTimebased(vacuum vacuumFunc, requestThreshold, maxRetries int, retryDelay time.Duration) error +} + +type ClientRequestDetails struct { + LastSeen string + MostQueriedDomain string + TotalRequests int + UniqueDomains int + BlockedRequests int + CachedRequests int + AvgResponseTimeMs float64 +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) *repository { + return &repository{db: db} +} + +func (r *repository) GetClientName(ip string) string { var hostname string - - err := db.Model(&RequestLog{}). + err := r.db.Model(&database.RequestLog{}). Select("client_name"). Where("client_ip = ? AND client_name != ?", ip, "unknown"). Limit(1). @@ -31,10 +65,10 @@ func GetClientNameFromRequestLog(db *gorm.DB, ip string) string { return strings.TrimSuffix(hostname, ".") } -func GetDistinctRequestIP(db *gorm.DB) int { +func (r *repository) GetDistinctRequestIP() int { var count int64 - err := db.Model(&RequestLog{}). + err := r.db.Model(&database.RequestLog{}). Select("COUNT(DISTINCT client_ip)"). Scan(&count).Error if err != nil { @@ -44,33 +78,39 @@ func GetDistinctRequestIP(db *gorm.DB) int { return int(count) } -func GetRequestSummaryByInterval(interval int, db *gorm.DB) ([]model.RequestLogIntervalSummary, error) { +func (r *repository) GetRequestSummaryByInterval(interval int) ([]model.RequestLogIntervalSummary, error) { minutes := interval * 60 - var rawSummaries []model.RequestLogIntervalSummary - err := db.Table("request_logs"). - Select(` - DATETIME((STRFTIME('%s', timestamp) / ?) * ?, 'unixepoch') AS interval_start, + type tempSummary struct { + IntervalStartUnix int64 `gorm:"column:interval_start_unix"` + BlockedCount int `gorm:"column:blocked_count"` + CachedCount int `gorm:"column:cached_count"` + AllowedCount int `gorm:"column:allowed_count"` + } + + var rawSummaries []tempSummary + + query := fmt.Sprintf(` + SELECT + ((CAST(STRFTIME('%%s', timestamp) AS INTEGER) / %d) * %d) AS interval_start_unix, SUM(blocked) AS blocked_count, SUM(cached) AS cached_count, SUM(NOT blocked AND NOT cached) AS allowed_count - `, minutes, minutes, minutes). - Where("timestamp >= DATETIME('now', '-1 day')"). - Group("(STRFTIME('%s', timestamp) / ?)"). - Order("interval_start"). - Scan(&rawSummaries).Error + FROM request_logs + WHERE timestamp >= DATETIME('now', '-1 day') + GROUP BY (CAST(STRFTIME('%%s', timestamp) AS INTEGER) / %d) + ORDER BY interval_start_unix ASC + `, minutes, minutes, minutes) + + err := r.db.Raw(query).Scan(&rawSummaries).Error if err != nil { return nil, err } summaries := make([]model.RequestLogIntervalSummary, len(rawSummaries)) for i := range rawSummaries { - t, err := time.Parse("2006-01-02 15:04:05", rawSummaries[i].IntervalStart) - if err != nil { - return nil, err - } summaries[i] = model.RequestLogIntervalSummary{ - IntervalStart: t.String(), + IntervalStart: time.Unix(rawSummaries[i].IntervalStartUnix, 0).Format("2006-01-02 15:04:05"), BlockedCount: rawSummaries[i].BlockedCount, CachedCount: rawSummaries[i].CachedCount, AllowedCount: rawSummaries[i].AllowedCount, @@ -80,27 +120,38 @@ func GetRequestSummaryByInterval(interval int, db *gorm.DB) ([]model.RequestLogI return summaries, nil } -func GetResponseSizeSummaryByInterval(intervalMinutes int, db *gorm.DB) ([]model.ResponseSizeSummary, error) { +func (r *repository) GetResponseSizeSummaryByInterval(intervalMinutes int) ([]model.ResponseSizeSummary, error) { intervalSeconds := int64(intervalMinutes * 60) - twentyFourHoursAgo := time.Now().Add(-24 * time.Hour).Unix() + twentyFourHoursAgo := time.Now().Add(-24 * time.Hour) var summaries []model.ResponseSizeSummary query := ` + WITH logs_unix AS ( + SELECT + CAST(strftime('%s', timestamp) AS INTEGER) AS ts_unix, + response_size_bytes + FROM request_logs + WHERE timestamp >= ? AND response_size_bytes IS NOT NULL + ) SELECT - ((strftime('%s', timestamp) / ?) * ?) AS start_unix, + (ts_unix / ?) * ? AS start_unix, SUM(response_size_bytes) AS total_size_bytes, ROUND(AVG(response_size_bytes)) AS avg_response_size_bytes, MIN(response_size_bytes) AS min_response_size_bytes, MAX(response_size_bytes) AS max_response_size_bytes - FROM request_logs - WHERE strftime('%s', timestamp) >= ? AND response_size_bytes IS NOT NULL - GROUP BY (strftime('%s', timestamp) / ?) + FROM logs_unix + GROUP BY ts_unix / ? ORDER BY start_unix ASC ` - err := db.Raw(query, intervalSeconds, intervalSeconds, twentyFourHoursAgo, intervalSeconds). - Scan(&summaries).Error + err := r.db.Raw(query, + twentyFourHoursAgo, + intervalSeconds, + intervalSeconds, + intervalSeconds, + ).Scan(&summaries).Error + if err != nil { return nil, fmt.Errorf("failed to query response size summary: %w", err) } @@ -112,7 +163,7 @@ func GetResponseSizeSummaryByInterval(intervalMinutes int, db *gorm.DB) ([]model return summaries, nil } -func GetUniqueQueryTypes(db *gorm.DB) ([]interface{}, error) { +func (r *repository) GetUniqueQueryTypes() ([]interface{}, error) { query := ` SELECT COUNT(*) AS count, query_type FROM request_logs @@ -120,7 +171,7 @@ func GetUniqueQueryTypes(db *gorm.DB) ([]interface{}, error) { GROUP BY query_type ORDER BY count DESC` - rows, err := db.Raw(query).Rows() + rows, err := r.db.Raw(query).Rows() if err != nil { return nil, err } @@ -134,8 +185,8 @@ func GetUniqueQueryTypes(db *gorm.DB) ([]interface{}, error) { var queries []any for rows.Next() { query := struct { - Count int `json:"count"` QueryType string `json:"queryType"` + Count int `json:"count"` }{} if err := rows.Scan(&query.Count, &query.QueryType); err != nil { @@ -152,9 +203,9 @@ func GetUniqueQueryTypes(db *gorm.DB) ([]interface{}, error) { return queries, nil } -func FetchQueries(db *gorm.DB, q models.QueryParams) ([]model.RequestLogEntry, error) { - var logs []RequestLog - query := db.Model(&RequestLog{}) +func (r *repository) FetchQueries(q models.QueryParams) ([]model.RequestLogEntry, error) { + var logs []database.RequestLog + query := r.db.Model(&database.RequestLog{}) if q.Column == "ip" { query = query.Joins("LEFT JOIN request_log_ips ri ON request_logs.id = ri.request_log_id") @@ -191,7 +242,7 @@ func FetchQueries(db *gorm.DB, q models.QueryParams) ([]model.RequestLogEntry, e results := make([]model.RequestLogEntry, len(logs)) for i, log := range logs { results[i] = model.RequestLogEntry{ - ID: int64(log.ID), + ID: log.ID, Timestamp: log.Timestamp, Domain: log.Domain, Blocked: log.Blocked, @@ -206,14 +257,14 @@ func FetchQueries(db *gorm.DB, q models.QueryParams) ([]model.RequestLogEntry, e } for j, ip := range log.IPs { - results[i].IP[j] = model.ResolvedIP{IP: ip.IP, RType: ip.RType} + results[i].IP[j] = model.ResolvedIP{IP: ip.IP, RType: ip.RecordType} } } return results, nil } -func FetchAllClients(db *gorm.DB) (map[string]dbModel.Client, error) { +func (r *repository) FetchAllClients() (map[string]Client, error) { var rows []struct { ClientIP string `gorm:"column:client_ip"` ClientName string `gorm:"column:client_name"` @@ -222,11 +273,11 @@ func FetchAllClients(db *gorm.DB) (map[string]dbModel.Client, error) { Vendor sql.NullString `gorm:"column:vendor"` } - subquery := db.Table("request_logs"). + subquery := r.db.Table("request_logs"). Select("client_ip, MAX(timestamp) as max_timestamp"). Group("client_ip") - if err := db.Table("request_logs r"). + if err := r.db.Table("request_logs r"). Select("r.client_ip, r.client_name, r.timestamp, m.mac, m.vendor"). Joins("INNER JOIN (?) latest ON r.client_ip = latest.client_ip AND r.timestamp = latest.max_timestamp", subquery). Joins("LEFT JOIN mac_addresses m ON r.client_ip = m.ip"). @@ -234,7 +285,7 @@ func FetchAllClients(db *gorm.DB) (map[string]dbModel.Client, error) { return nil, err } - uniqueClients := make(map[string]dbModel.Client, len(rows)) + uniqueClients := make(map[string]Client, len(rows)) for _, row := range rows { macStr := "" vendorStr := "" @@ -245,7 +296,7 @@ func FetchAllClients(db *gorm.DB) (map[string]dbModel.Client, error) { vendorStr = row.Vendor.String } - uniqueClients[row.ClientIP] = dbModel.Client{ + uniqueClients[row.ClientIP] = Client{ Name: row.ClientName, LastSeen: row.Timestamp, Mac: macStr, @@ -256,9 +307,20 @@ func FetchAllClients(db *gorm.DB) (map[string]dbModel.Client, error) { return uniqueClients, nil } -func GetClientDetailsWithDomains(db *gorm.DB, clientIP string) (dbModel.ClientRequestDetails, string, map[string]int, error) { - var crd dbModel.ClientRequestDetails - err := db.Table("request_logs"). +func (r *repository) GetUniqueClientNameAndIP() []database.RequestLog { + var results []database.RequestLog + + r.db.Model(&database.RequestLog{}). + Select("DISTINCT client_ip, client_name"). + Where("client_name IS NOT NULL AND client_name != ?", "unknown"). + Find(&results) + + return results +} + +func (r *repository) GetClientDetailsWithDomains(clientIP string) (ClientRequestDetails, string, map[string]int, error) { + var crd ClientRequestDetails + err := r.db.Table("request_logs"). Select(` COUNT(*) as total_requests, COUNT(DISTINCT domain) as unique_domains, @@ -278,7 +340,7 @@ func GetClientDetailsWithDomains(db *gorm.DB, clientIP string) (dbModel.ClientRe Count int `gorm:"column:query_count"` } - err = db.Table("request_logs"). + err = r.db.Table("request_logs"). Select("domain, COUNT(*) as query_count"). Where("client_ip = ?", clientIP). Group("domain"). @@ -306,13 +368,13 @@ func GetClientDetailsWithDomains(db *gorm.DB, clientIP string) (dbModel.ClientRe return crd, mostQueriedDomain, domainQueryCounts, nil } -func GetTopBlockedDomains(db *gorm.DB, blockedRequests int) ([]map[string]interface{}, error) { +func (r *repository) GetTopBlockedDomains(blockedRequests int) ([]map[string]interface{}, error) { var rows []struct { Domain string `gorm:"column:domain"` Hits int `gorm:"column:hits"` } - if err := db.Table("request_logs"). + if err := r.db.Table("request_logs"). Select("domain, COUNT(*) as hits"). Where("blocked = ?", true). Group("domain"). @@ -337,9 +399,9 @@ func GetTopBlockedDomains(db *gorm.DB, blockedRequests int) ([]map[string]interf return topBlockedDomains, nil } -func GetTopClients(db *gorm.DB) ([]map[string]interface{}, error) { +func (r *repository) GetTopClients() ([]map[string]interface{}, error) { var total int64 - if err := db.Table("request_logs").Count(&total).Error; err != nil { + if err := r.db.Table("request_logs").Count(&total).Error; err != nil { return nil, err } @@ -349,7 +411,7 @@ func GetTopClients(db *gorm.DB) ([]map[string]interface{}, error) { Frequency float32 `gorm:"column:frequency"` } - if err := db.Table("request_logs"). + if err := r.db.Table("request_logs"). Select("? as frequency, client_ip, COUNT(*) as request_count", 0). Group("client_ip"). Order("request_count DESC"). @@ -370,18 +432,18 @@ func GetTopClients(db *gorm.DB) ([]map[string]interface{}, error) { return clients, nil } -func CountQueries(db *gorm.DB, search string) (int, error) { +func (r *repository) CountQueries(search string) (int, error) { var total int64 - err := db.Table("request_logs"). + err := r.db.Table("request_logs"). Where("domain LIKE ?", "%"+search+"%"). Count(&total).Error return int(total), err } -func SaveRequestLog(db *gorm.DB, entries []model.RequestLogEntry) error { - return db.Transaction(func(tx *gorm.DB) error { +func (r *repository) SaveRequestLog(entries []model.RequestLogEntry) error { + return r.db.Transaction(func(tx *gorm.DB) error { for _, entry := range entries { - rl := RequestLog{ + rl := database.RequestLog{ Timestamp: entry.Timestamp, Domain: entry.Domain, Blocked: entry.Blocked, @@ -396,41 +458,40 @@ func SaveRequestLog(db *gorm.DB, entries []model.RequestLogEntry) error { } for _, resolvedIP := range entry.IP { - rl.IPs = append(rl.IPs, RequestLogIP{ - IP: resolvedIP.IP, - RType: resolvedIP.RType, + rl.IPs = append(rl.IPs, database.RequestLogIP{ + IP: resolvedIP.IP, + RecordType: resolvedIP.RType, }) } if err := tx.Create(&rl).Error; err != nil { - return fmt.Errorf("could not save request log: %v", err) + return fmt.Errorf("could not save request log: %w", err) } } return nil }) } -type vacuumFunc func() - -func DeleteRequestLogsTimebased(vacuum vacuumFunc, db *gorm.DB, requestThreshold, maxRetries int, retryDelay time.Duration) { +func (r *repository) DeleteRequestLogsTimebased(vacuum vacuumFunc, requestThreshold, maxRetries int, retryDelay time.Duration) error { cutoffTime := time.Now().Add(-time.Duration(requestThreshold) * time.Second) for retryCount := range maxRetries { - result := db.Where("timestamp < ?", cutoffTime).Delete(&RequestLog{}) + result := r.db.Where("timestamp < ?", cutoffTime).Delete(&database.RequestLog{}) if result.Error != nil { if result.Error.Error() == "database is locked" { log.Warning("Database is locked; retrying (%d/%d)", retryCount+1, maxRetries) time.Sleep(retryDelay) continue } - log.Error("Failed to clear old entries: %s", result.Error) - break + return fmt.Errorf("failed to clear old entries: %w", result.Error) } if affected := result.RowsAffected; affected > 0 { - vacuum() + vacuum(context.Background()) log.Debug("Cleared %d old entries", affected) } - break + return nil // Success } + + return fmt.Errorf("failed to delete after %d retries", maxRetries) } diff --git a/backend/request/service.go b/backend/request/service.go new file mode 100644 index 0000000..2a17a0e --- /dev/null +++ b/backend/request/service.go @@ -0,0 +1,89 @@ +package request + +import ( + "context" + "goaway/backend/api/models" + model "goaway/backend/dns/server/models" + "goaway/backend/logging" + "time" +) + +type Service struct { + repository Repository +} + +var log = logging.GetLogger() + +func NewService(repo Repository) *Service { + return &Service{repository: repo} +} + +func (s *Service) GetClientNameFromIP(ip string) string { + return s.repository.GetClientName(ip) +} + +func (s *Service) GetDistinctRequestIP() int { + return s.repository.GetDistinctRequestIP() +} + +func (s *Service) GetRequestSummaryByInterval(interval int) ([]model.RequestLogIntervalSummary, error) { + return s.repository.GetRequestSummaryByInterval(interval) +} + +func (s *Service) GetResponseSizeSummaryByInterval(intervalMinutes int) ([]model.ResponseSizeSummary, error) { + return s.repository.GetResponseSizeSummaryByInterval(intervalMinutes) +} + +func (s *Service) GetUniqueQueryTypes() ([]interface{}, error) { + return s.repository.GetUniqueQueryTypes() +} + +func (s *Service) FetchQueries(q models.QueryParams) ([]model.RequestLogEntry, error) { + return s.repository.FetchQueries(q) +} + +func (s *Service) FetchAllClients() (map[string]Client, error) { + return s.repository.FetchAllClients() +} + +func (s *Service) GetUniqueClientNameAndIP() []ClientNameAndIP { + queryResult := s.repository.GetUniqueClientNameAndIP() + var uniqueClients []ClientNameAndIP + + for _, client := range queryResult { + uniqueClients = append(uniqueClients, ClientNameAndIP{ + Name: client.ClientName, + IP: client.ClientIP, + }) + } + + return uniqueClients +} + +func (s *Service) GetClientDetailsWithDomains(clientIP string) (ClientRequestDetails, string, map[string]int, error) { + return s.repository.GetClientDetailsWithDomains(clientIP) +} + +func (s *Service) GetTopBlockedDomains(blockedRequests int) ([]map[string]interface{}, error) { + return s.repository.GetTopBlockedDomains(blockedRequests) +} + +func (s *Service) GetTopClients() ([]map[string]interface{}, error) { + return s.repository.GetTopClients() +} + +func (s *Service) CountQueries(search string) (int, error) { + return s.repository.CountQueries(search) +} + +func (s *Service) SaveRequestLog(entries []model.RequestLogEntry) error { + return s.repository.SaveRequestLog(entries) +} + +type vacuumFunc func(ctx context.Context) + +func (s *Service) DeleteRequestLogsTimebased(vacuum vacuumFunc, requestThreshold, maxRetries int, retryDelay time.Duration) { + if err := s.repository.DeleteRequestLogsTimebased(vacuum, requestThreshold, maxRetries, retryDelay); err != nil { + log.Warning("Error while deleting old request logs: %v", err) + } +} diff --git a/backend/resolution/repository.go b/backend/resolution/repository.go new file mode 100644 index 0000000..09fa3c3 --- /dev/null +++ b/backend/resolution/repository.go @@ -0,0 +1,77 @@ +package resolution + +import ( + "errors" + "fmt" + "goaway/backend/database" + "strings" + + "gorm.io/gorm" +) + +type Repository interface { + CreateResolution(ip, domain string) error + FindResolution(domain string) (string, error) + FindResolutions() ([]database.Resolution, error) + DeleteResolution(ip, domain string) (int, error) +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) CreateResolution(ip, domain string) error { + res := database.Resolution{ + Domain: domain, + IP: ip, + } + + if err := r.db.Create(&res).Error; err != nil { + if strings.Contains(err.Error(), "UNIQUE") { + return errors.New("domain already exists, must be unique") + } + return fmt.Errorf("could not create new resolution: %w", err) + } + return nil +} + +func (r *repository) FindResolution(domain string) (string, error) { + var res database.Resolution + + r.db.Where("domain = ?", domain).Find(&res) + if res.IP != "" { + return res.IP, nil + } + + parts := strings.Split(domain, ".") + for i := 1; i < len(parts); i++ { + wildcardDomain := "*." + strings.Join(parts[i:], ".") + if err := r.db.Where("domain = ?", wildcardDomain).Find(&res).Error; err == nil { + return res.IP, nil + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return "", err + } + } + + return "", nil +} + +func (r *repository) FindResolutions() ([]database.Resolution, error) { + var resolutions []database.Resolution + if err := r.db.Find(&resolutions).Error; err != nil { + return nil, err + } + return resolutions, nil +} + +func (r *repository) DeleteResolution(ip, domain string) (int, error) { + result := r.db.Where("domain = ? AND ip = ?", domain, ip).Delete(&database.Resolution{}) + if result.Error != nil { + return 0, result.Error + } + return int(result.RowsAffected), nil +} diff --git a/backend/resolution/service.go b/backend/resolution/service.go new file mode 100644 index 0000000..3a9ebc1 --- /dev/null +++ b/backend/resolution/service.go @@ -0,0 +1,34 @@ +package resolution + +import ( + "goaway/backend/database" + "goaway/backend/logging" +) + +type Service struct { + repository Repository +} + +var log = logging.GetLogger() + +func NewService(repo Repository) *Service { + return &Service{repository: repo} +} + +func (s *Service) CreateResolution(ip, domain string) error { + log.Debug("Creating new resolution '%s' -> '%s'", domain, ip) + return s.repository.CreateResolution(ip, domain) +} + +func (s *Service) GetResolution(domain string) (string, error) { + log.Debug("Finding resolution for domain: %s", domain) + return s.repository.FindResolution(domain) +} + +func (s *Service) GetResolutions() ([]database.Resolution, error) { + return s.repository.FindResolutions() +} + +func (s *Service) DeleteResolution(ip, domain string) (int, error) { + return s.repository.DeleteResolution(ip, domain) +} diff --git a/backend/services/context.go b/backend/services/context.go new file mode 100644 index 0000000..9729e91 --- /dev/null +++ b/backend/services/context.go @@ -0,0 +1,51 @@ +package services + +import ( + "crypto/tls" + "fmt" + "goaway/backend/database" + "goaway/backend/dns/server" + "goaway/backend/settings" + + "gorm.io/gorm" +) + +type AppContext struct { + Config *settings.Config + DBConn *gorm.DB + Certificate tls.Certificate + DNSServer *server.DNSServer +} + +func NewAppContext(config *settings.Config) (*AppContext, error) { + ctx := &AppContext{Config: config} + if err := ctx.initialize(); err != nil { + return nil, err + } + + return ctx, nil +} + +func (ctx *AppContext) initialize() error { + ctx.DBConn = database.Initialize() + + cert, err := ctx.Config.GetCertificate() + if err != nil { + return fmt.Errorf("failed to get certificate: %w", err) + } + ctx.Certificate = cert + + dnsServer, err := server.NewDNSServer( + ctx.Config, + ctx.DBConn, + cert, + ) + if err != nil { + return fmt.Errorf("failed to initialize DNS server: %w", err) + } + ctx.DNSServer = dnsServer + + go dnsServer.ProcessLogEntries() + + return nil +} diff --git a/backend/services/registry.go b/backend/services/registry.go new file mode 100644 index 0000000..f2ad70d --- /dev/null +++ b/backend/services/registry.go @@ -0,0 +1,235 @@ +package services + +import ( + "embed" + "fmt" + "goaway/backend/api" + "goaway/backend/api/key" + "goaway/backend/blacklist" + "goaway/backend/logging" + "goaway/backend/notification" + "goaway/backend/prefetch" + "goaway/backend/request" + "goaway/backend/resolution" + "goaway/backend/user" + "goaway/backend/whitelist" + "net/http" + "sync" + + "github.com/miekg/dns" +) + +var log = logging.GetLogger() + +// Manages all servers and services +type ServiceRegistry struct { + APIServer *api.API + errorChan chan ServiceError + dotServer *dns.Server + readyChan chan struct{} + content embed.FS + udpServer *dns.Server + dohServer *http.Server + tcpServer *dns.Server + Context *AppContext + version string + date string + commit string + wg sync.WaitGroup + + ResolutionService *resolution.Service + RequestService *request.Service + PrefetchService *prefetch.Service + UserService *user.Service + KeyService *key.Service + NotificationService *notification.Service + BlacklistService *blacklist.Service + WhitelistService *whitelist.Service +} + +type ServiceError struct { + Err error + Service string +} + +func NewServiceRegistry(ctx *AppContext, version, commit, date string, content embed.FS) *ServiceRegistry { + return &ServiceRegistry{ + Context: ctx, + version: version, + commit: commit, + date: date, + content: content, + readyChan: make(chan struct{}), + errorChan: make(chan ServiceError, 10), + } +} + +func (r *ServiceRegistry) Initialize() error { + r.setupDNSServers() + + if r.Context.Certificate.Certificate != nil { + if err := r.setupSecureServers(); err != nil { + return err + } + } + + r.setupAPIServer() + + return nil +} + +func (r *ServiceRegistry) setupDNSServers() { + config := r.Context.Config + + notifyReady := func() { + log.Info("Started DNS server on: %s:%d", config.DNS.Address, config.DNS.Ports.TCPUDP) + close(r.readyChan) + } + + r.udpServer = &dns.Server{ + Addr: fmt.Sprintf("%s:%d", config.DNS.Address, config.DNS.Ports.TCPUDP), + Net: "udp", + Handler: r.Context.DNSServer, + ReusePort: true, + UDPSize: config.DNS.UDPSize, + } + + r.tcpServer = &dns.Server{ + Addr: fmt.Sprintf("%s:%d", config.DNS.Address, config.DNS.Ports.TCPUDP), + Net: "tcp", + Handler: r.Context.DNSServer, + ReusePort: true, + UDPSize: config.DNS.UDPSize, + NotifyStartedFunc: notifyReady, + } +} + +func (r *ServiceRegistry) setupSecureServers() error { + dotServer, err := r.Context.DNSServer.InitDoT(r.Context.Certificate) + if err != nil { + return fmt.Errorf("failed to initialize DoT server: %w", err) + } + r.dotServer = dotServer + + dohServer, err := r.Context.DNSServer.InitDoH(r.Context.Certificate) + if err != nil { + return fmt.Errorf("failed to initialize DoH server: %w", err) + } + r.dohServer = dohServer + + return nil +} + +func (r *ServiceRegistry) setupAPIServer() { + r.APIServer = &api.API{ + DNS: r.Context.DNSServer, + Authentication: r.Context.Config.API.Authentication, + Config: r.Context.Config, + DNSPort: r.Context.Config.DNS.Ports.TCPUDP, + Version: r.version, + Commit: r.commit, + Date: r.date, + DNSServer: r.Context.DNSServer, + DBConn: r.Context.DBConn, + WSQueries: r.Context.DNSServer.WSQueries, + WSCommunication: r.Context.DNSServer.WSCommunication, + + ResolutionService: r.ResolutionService, + RequestService: r.RequestService, + PrefetchService: r.PrefetchService, + NotificationService: r.NotificationService, + UserService: r.UserService, + KeyService: r.KeyService, + BlacklistService: r.BlacklistService, + WhitelistService: r.WhitelistService, + } +} + +func (r *ServiceRegistry) StartAll() { + r.startDNSServers() + + if r.Context.Certificate.Certificate != nil { + r.startSecureServers() + } + + r.startAPIServer() +} + +func (r *ServiceRegistry) startDNSServers() { + r.wg.Add(1) + go func() { + defer r.wg.Done() + if err := r.udpServer.ListenAndServe(); err != nil { + r.errorChan <- ServiceError{Service: "UDP", Err: err} + } + }() + + r.wg.Add(1) + go func() { + defer r.wg.Done() + if err := r.tcpServer.ListenAndServe(); err != nil { + r.errorChan <- ServiceError{Service: "TCP", Err: err} + } + }() +} + +func (r *ServiceRegistry) startSecureServers() { + r.wg.Add(1) + go func() { + defer r.wg.Done() + if err := r.dotServer.ListenAndServe(); err != nil { + r.errorChan <- ServiceError{Service: "DoT", Err: err} + } + }() + + r.wg.Add(1) + go func() { + defer r.wg.Done() + + if serverIP, err := api.GetServerIP(); err == nil { + log.Info("DoH (dns-over-https) server running at https://%s:%d/dns-query", + serverIP, r.Context.Config.DNS.Ports.DoH) + } else { + log.Info("DoH (dns-over-https) server running on port :%d", r.Context.Config.DNS.Ports.DoH) + } + + if err := r.dohServer.ListenAndServeTLS( + r.Context.Config.DNS.TLS.Cert, + r.Context.Config.DNS.TLS.Key, + ); err != nil { + r.errorChan <- ServiceError{Service: "DoH", Err: err} + } + }() +} + +func (r *ServiceRegistry) startAPIServer() { + r.wg.Add(1) + go func() { + defer r.wg.Done() + <-r.readyChan + + errorChan := make(chan struct{}, 1) + go func() { + <-errorChan + r.errorChan <- ServiceError{Service: "API", Err: fmt.Errorf("API server stopped")} + }() + + r.APIServer.Start(r.content, errorChan) + }() +} + +func (r *ServiceRegistry) WaitGroup() *sync.WaitGroup { + return &r.wg +} + +func (r *ServiceRegistry) ReadyChannel() <-chan struct{} { + return r.readyChan +} + +func (r *ServiceRegistry) ErrorChannel() <-chan ServiceError { + return r.errorChan +} + +func (r *ServiceRegistry) GetPrefetcher() *prefetch.Service { + return r.APIServer.PrefetchService +} diff --git a/backend/settings/model.go b/backend/settings/model.go new file mode 100644 index 0000000..8de6882 --- /dev/null +++ b/backend/settings/model.go @@ -0,0 +1,69 @@ +package settings + +import "time" + +type Status struct { + PausedAt time.Time `json:"pausedAt"` + PauseTime time.Time `json:"pauseTime"` + Paused bool `json:"paused"` +} + +type TLSConfig struct { + Enabled bool `yaml:"enabled" json:"enabled"` + Cert string `yaml:"cert" json:"cert"` + Key string `yaml:"key" json:"key"` +} + +type UpstreamConfig struct { + Preferred string `yaml:"preferred" json:"preferred"` + Fallback []string `yaml:"fallback" json:"fallback"` +} + +type PortsConfig struct { + TCPUDP int `yaml:"udptcp" json:"udptcp"` + DoT int `yaml:"dot" json:"dot"` + DoH int `yaml:"doh" json:"doh"` +} + +type DNSConfig struct { + Status Status `yaml:"-" json:"status"` + Address string `yaml:"address" json:"address"` + Gateway string `yaml:"gateway" json:"gateway"` + CacheTTL int `yaml:"cacheTTL" json:"cacheTTL"` + UDPSize int `yaml:"udpSize" json:"udpSize"` + TLS TLSConfig `yaml:"tls" json:"tls"` + Upstream UpstreamConfig `yaml:"upstream" json:"upstream"` + Ports PortsConfig `yaml:"ports" json:"ports"` +} + +type RateLimitConfig struct { + Enabled bool `yaml:"enabled" json:"enabled"` + MaxTries int `yaml:"maxTries" json:"maxTries"` + Window int `yaml:"window" json:"window"` +} + +type APIConfig struct { + Port int `yaml:"port" json:"port"` + Authentication bool `yaml:"authentication" json:"authentication"` + RateLimit RateLimitConfig `yaml:"rateLimit" json:"rateLimit"` +} + +type LoggingConfig struct { + Enabled bool `yaml:"enabled" json:"enabled"` + Level int `yaml:"level" json:"level"` +} + +type MiscConfig struct { + InAppUpdate bool `yaml:"inAppUpdate" json:"inAppUpdate"` + StatisticsRetention int `yaml:"statisticsRetention" json:"statisticsRetention"` + Dashboard bool `yaml:"dashboard" json:"dashboard"` + ScheduledBlacklistUpdates bool `yaml:"scheduledBlacklistUpdates" json:"scheduledBlacklistUpdates"` +} + +type Config struct { + BinaryPath string `yaml:"-" json:"-"` + DNS DNSConfig `yaml:"dns" json:"dns"` + API APIConfig `yaml:"api" json:"api"` + Logging LoggingConfig `yaml:"logging" json:"logging"` + Misc MiscConfig `yaml:"misc" json:"misc"` +} diff --git a/backend/settings/settings.go b/backend/settings/settings.go index f6bc490..13fb99e 100644 --- a/backend/settings/settings.go +++ b/backend/settings/settings.go @@ -3,62 +3,17 @@ package settings import ( "crypto/tls" "fmt" - "goaway/backend/api/ratelimit" "goaway/backend/logging" "net" "os" "path/filepath" "strconv" - "time" "gopkg.in/yaml.v3" ) var log = logging.GetLogger() -type Status struct { - Paused bool `json:"paused"` - PausedAt time.Time `json:"pausedAt"` - PauseTime int `json:"pauseTime"` -} - -type DNSConfig struct { - Address string `yaml:"address" json:"address"` - Port int `yaml:"port" json:"port"` - DoTPort int `yaml:"dotPort" json:"dotPort"` - DoHPort int `yaml:"dohPort" json:"dohPort"` - CacheTTL int `yaml:"cacheTTL" json:"cacheTTL"` - PreferredUpstream string `yaml:"preferredUpstream" json:"preferredUpstream"` - Gateway string `yaml:"gateway" json:"gateway"` - UpstreamDNS []string `yaml:"upstreamDNS" json:"upstreamDNS"` - UDPSize int `yaml:"udpSize" json:"udpSize"` - Status Status `yaml:"-" json:"status"` - - TLSCertFile string `yaml:"tlsCertFile" json:"tlsCertFile"` - TLSKeyFile string `yaml:"tlsKeyFile" json:"tlsKeyFile"` -} - -type APIConfig struct { - Port int `yaml:"port" json:"port"` - Authentication bool `yaml:"authentication" json:"authentication"` - RateLimiterConfig ratelimit.RateLimiterConfig `yaml:"rateLimit" json:"-"` -} - -type Config struct { - DNS DNSConfig `yaml:"dns" json:"dns"` - API APIConfig `yaml:"api" json:"api"` - - Dashboard bool `yaml:"dashboard" json:"-"` - ScheduledBlacklistUpdates bool `yaml:"scheduledBlacklistUpdates" json:"scheduledBlacklistUpdates"` - StatisticsRetention int `yaml:"statisticsRetention" json:"statisticsRetention"` - LoggingEnabled bool `yaml:"loggingEnabled" json:"loggingEnabled"` - LogLevel logging.LogLevel `yaml:"logLevel" json:"logLevel"` - InAppUpdate bool `yaml:"inAppUpdate" json:"inAppUpdate"` - - // settings not visible in config file - BinaryPath string `yaml:"-" json:"-"` -} - func LoadSettings() (Config, error) { var config Config @@ -74,6 +29,7 @@ func LoadSettings() (Config, error) { if err != nil { return Config{}, err } + return config, nil } data, err := os.ReadFile(path) @@ -81,43 +37,10 @@ func LoadSettings() (Config, error) { return Config{}, fmt.Errorf("could not read settings file: %w", err) } - type configWithPtr struct { - DNS DNSConfig `yaml:"dns" json:"dns"` - API APIConfig `yaml:"api" json:"api"` - Dashboard *bool `yaml:"dashboard" json:"-"` - ScheduledBlacklistUpdates *bool `yaml:"scheduledBlacklistUpdates" json:"scheduledBlacklistUpdates"` - StatisticsRetention int `yaml:"statisticsRetention" json:"statisticsRetention"` - LoggingEnabled bool `yaml:"loggingEnabled" json:"loggingEnabled"` - LogLevel logging.LogLevel `yaml:"logLevel" json:"logLevel"` - InAppUpdate bool `yaml:"inAppUpdate" json:"inAppUpdate"` - } - - var temp configWithPtr - if err := yaml.Unmarshal(data, &temp); err != nil { + if err := yaml.Unmarshal(data, &config); err != nil { return Config{}, fmt.Errorf("invalid settings format: %w", err) } - config.DNS = temp.DNS - config.API = temp.API - config.StatisticsRetention = temp.StatisticsRetention - config.LoggingEnabled = temp.LoggingEnabled - config.LogLevel = temp.LogLevel - config.InAppUpdate = temp.InAppUpdate - - if temp.Dashboard == nil { - // true by default if the Dashboard field was not found in settings.yaml - config.Dashboard = true - } else { - config.Dashboard = *temp.Dashboard - } - - if temp.ScheduledBlacklistUpdates == nil { - // false by default if the ScheduledBlacklistUpdates field was not found in settings.yaml - config.ScheduledBlacklistUpdates = false - } else { - config.ScheduledBlacklistUpdates = *temp.ScheduledBlacklistUpdates - } - binaryPath, err := os.Executable() if err != nil { log.Warning("Unable to find installed binary path, err: %v", err) @@ -127,35 +50,84 @@ func LoadSettings() (Config, error) { return config, nil } +func (config *Config) Save() { + data, err := yaml.Marshal(config) + if err != nil { + log.Error("Could not parse settings %v", err) + return + } + + if err := os.WriteFile("./config/settings.yaml", data, 0644); err != nil { + log.Error("Could not save settings %v", err) + } +} + +func (config *Config) Update(updatedSettings Config) { + config.API.Port = updatedSettings.API.Port + config.API.Authentication = updatedSettings.API.Authentication + config.API.RateLimit = updatedSettings.API.RateLimit + + config.DNS.Address = updatedSettings.DNS.Address + config.DNS.Ports = updatedSettings.DNS.Ports + config.DNS.UDPSize = updatedSettings.DNS.UDPSize + config.DNS.CacheTTL = updatedSettings.DNS.CacheTTL + config.DNS.TLS = updatedSettings.DNS.TLS + config.DNS.Upstream = updatedSettings.DNS.Upstream + + config.Logging = updatedSettings.Logging + config.Misc = updatedSettings.Misc + + log.ToggleLogging(config.Logging.Enabled) + log.SetLevel(logging.LogLevel(config.Logging.Level)) + + config.Save() +} + func createDefaultSettings(filePath string) (Config, error) { defaultConfig := Config{ - StatisticsRetention: 7, - LoggingEnabled: true, - LogLevel: logging.INFO, - InAppUpdate: false, + DNS: DNSConfig{ + Address: "0.0.0.0", + Gateway: getDefaultGateway(), + CacheTTL: 3600, + UDPSize: 512, + TLS: TLSConfig{ + Enabled: false, + Cert: "", + Key: "", + }, + Upstream: UpstreamConfig{ + Preferred: "8.8.8.8:53", + Fallback: []string{ + "1.1.1.1:53", + }, + }, + Ports: PortsConfig{ + TCPUDP: getEnvAsIntWithDefault("DNS_PORT", 53), + DoT: getEnvAsIntWithDefault("DOT_PORT", 853), + DoH: getEnvAsIntWithDefault("DOH_PORT", 443), + }, + }, + API: APIConfig{ + Port: getEnvAsIntWithDefault("WEBSITE_PORT", 8080), + Authentication: true, + RateLimit: RateLimitConfig{ + Enabled: true, + MaxTries: 5, + Window: 5, + }, + }, + Logging: LoggingConfig{ + Enabled: true, + Level: int(logging.INFO), + }, + Misc: MiscConfig{ + InAppUpdate: false, + StatisticsRetention: 7, + Dashboard: true, + ScheduledBlacklistUpdates: true, + }, } - defaultConfig.DNS.Address = "0.0.0.0" - defaultConfig.DNS.Port = GetEnvAsIntWithDefault("DNS_PORT", 53) - defaultConfig.DNS.DoTPort = GetEnvAsIntWithDefault("DOT_PORT", 853) - defaultConfig.DNS.DoHPort = GetEnvAsIntWithDefault("DOH_PORT", 443) - defaultConfig.DNS.CacheTTL = 3600 - defaultConfig.DNS.PreferredUpstream = "8.8.8.8:53" - defaultConfig.DNS.Gateway = GetDefaultGateway() - defaultConfig.DNS.UpstreamDNS = []string{ - "1.1.1.1:53", - "8.8.8.8:53", - } - defaultConfig.DNS.UDPSize = 512 - defaultConfig.DNS.TLSCertFile = "" - defaultConfig.DNS.TLSKeyFile = "" - - defaultConfig.Dashboard = true - defaultConfig.ScheduledBlacklistUpdates = true - defaultConfig.API.Port = GetEnvAsIntWithDefault("WEBSITE_PORT", 8080) - defaultConfig.API.Authentication = true - defaultConfig.API.RateLimiterConfig = ratelimit.RateLimiterConfig{Enabled: true, MaxTries: 5, Window: 5} - data, err := yaml.Marshal(&defaultConfig) if err != nil { return Config{}, fmt.Errorf("failed to marshal default config: %w", err) @@ -174,45 +146,7 @@ func createDefaultSettings(filePath string) (Config, error) { return defaultConfig, nil } -func (config *Config) Save() { - data, err := yaml.Marshal(config) - if err != nil { - log.Error("Could not parse settings %v", err) - return - } - - if err := os.WriteFile("./config/settings.yaml", data, 0644); err != nil { - log.Error("Could not save settings %v", err) - } -} - -func (config *Config) UpdateSettings(updatedSettings Config) { - config.API.Port = updatedSettings.API.Port - config.API.Authentication = updatedSettings.API.Authentication - - config.DNS.Address = updatedSettings.DNS.Address - config.DNS.Port = updatedSettings.DNS.Port - config.DNS.DoTPort = updatedSettings.DNS.DoTPort - config.DNS.DoHPort = updatedSettings.DNS.DoHPort - config.DNS.UDPSize = updatedSettings.DNS.UDPSize - config.DNS.CacheTTL = updatedSettings.DNS.CacheTTL - config.DNS.TLSCertFile = updatedSettings.DNS.TLSCertFile - config.DNS.TLSKeyFile = updatedSettings.DNS.TLSKeyFile - - config.LogLevel = updatedSettings.LogLevel - config.StatisticsRetention = updatedSettings.StatisticsRetention - config.LoggingEnabled = updatedSettings.LoggingEnabled - - config.ScheduledBlacklistUpdates = updatedSettings.ScheduledBlacklistUpdates - config.InAppUpdate = updatedSettings.InAppUpdate - - log.ToggleLogging(config.LoggingEnabled) - log.SetLevel(config.LogLevel) - - config.Save() -} - -func GetEnvAsIntWithDefault(envVariable string, defaultValue int) int { +func getEnvAsIntWithDefault(envVariable string, defaultValue int) int { val, found := os.LookupEnv(envVariable) if !found { return defaultValue @@ -227,10 +161,10 @@ func GetEnvAsIntWithDefault(envVariable string, defaultValue int) int { } func (config *Config) GetCertificate() (tls.Certificate, error) { - if config.DNS.TLSCertFile != "" && config.DNS.TLSKeyFile != "" { - cert, err := tls.LoadX509KeyPair(config.DNS.TLSCertFile, config.DNS.TLSKeyFile) + if config.DNS.TLS.Enabled && config.DNS.TLS.Cert != "" && config.DNS.TLS.Key != "" { + cert, err := tls.LoadX509KeyPair(config.DNS.TLS.Cert, config.DNS.TLS.Key) if err != nil { - return tls.Certificate{}, fmt.Errorf("failed to load TLS certificate: %s", err) + return tls.Certificate{}, fmt.Errorf("failed to load TLS certificate: %w", err) } return cert, nil @@ -239,7 +173,7 @@ func (config *Config) GetCertificate() (tls.Certificate, error) { return tls.Certificate{}, nil } -func GetDefaultGateway() string { +func getDefaultGateway() string { conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil { return "192.168.0.1:53" diff --git a/backend/setup/setup.go b/backend/setup/setup.go index 0768486..569b060 100644 --- a/backend/setup/setup.go +++ b/backend/setup/setup.go @@ -2,18 +2,19 @@ package setup import ( "fmt" - "goaway/backend/logging" - "goaway/backend/settings" "os" "strconv" + "goaway/backend/logging" + "goaway/backend/settings" + "github.com/Masterminds/semver" ) var log = logging.GetLogger() type SetFlags struct { - DnsPort *int + DNSPort *int DoTPort *int DoHPort *int WebserverPort *int @@ -37,15 +38,15 @@ func UpdateConfig(config *settings.Config, flags *SetFlags) { fmt.Println("Flag --log-level can't be greater than 3 or below 0.") os.Exit(1) } - if flags.DnsPort != nil || os.Getenv("DNS_PORT") != "" { + if flags.DNSPort != nil || os.Getenv("DNS_PORT") != "" { if port, found := os.LookupEnv("DNS_PORT"); found { dnsPort, err := strconv.Atoi(port) if err != nil { log.Fatal("Could not parse DNS_PORT environment variable") } - config.DNS.Port = dnsPort + config.DNS.Ports.TCPUDP = dnsPort } else { - config.DNS.Port = *flags.DnsPort + config.DNS.Ports.TCPUDP = *flags.DNSPort } } if flags.DoTPort != nil || os.Getenv("DOT_PORT") != "" { @@ -54,9 +55,9 @@ func UpdateConfig(config *settings.Config, flags *SetFlags) { if err != nil { log.Fatal("Could not parse DOT_PORT environment variable") } - config.DNS.DoTPort = dotPort + config.DNS.Ports.DoT = dotPort } else { - config.DNS.DoTPort = *flags.DoTPort + config.DNS.Ports.DoT = *flags.DoTPort } } if flags.DoHPort != nil || os.Getenv("DOH_PORT") != "" { @@ -65,9 +66,9 @@ func UpdateConfig(config *settings.Config, flags *SetFlags) { if err != nil { log.Fatal("Could not parse DOH_PORT environment variable") } - config.DNS.DoHPort = dohPort + config.DNS.Ports.DoH = dohPort } else { - config.DNS.DoHPort = *flags.DoHPort + config.DNS.Ports.DoH = *flags.DoHPort } } if flags.WebserverPort != nil || os.Getenv("WEBSITE_PORT") != "" { @@ -82,27 +83,27 @@ func UpdateConfig(config *settings.Config, flags *SetFlags) { } } if flags.StatisticsRetention != nil { - config.StatisticsRetention = *flags.StatisticsRetention + config.Misc.StatisticsRetention = *flags.StatisticsRetention } if flags.Authentication != nil { config.API.Authentication = *flags.Authentication } if flags.Dashboard != nil { - config.Dashboard = *flags.Dashboard + config.Misc.Dashboard = *flags.Dashboard } if flags.LoggingEnabled != nil { - config.LoggingEnabled = *flags.LoggingEnabled + config.Logging.Enabled = *flags.LoggingEnabled } if flags.LogLevel != nil { - config.LogLevel = logging.LogLevel(*flags.LogLevel) + config.Logging.Level = *flags.LogLevel } if flags.InAppUpdate != nil { - config.InAppUpdate = *flags.InAppUpdate + config.Misc.InAppUpdate = *flags.InAppUpdate } if flags.JSON != nil { log.JSON = *flags.JSON - log.SetJson(log.JSON) + log.SetJSON(log.JSON) } else { log.Ansi = flags.Ansi == nil || *flags.Ansi log.SetAnsi(log.Ansi) diff --git a/backend/updater/updater.go b/backend/updater/updater.go index 9b974a8..5ee6ba9 100644 --- a/backend/updater/updater.go +++ b/backend/updater/updater.go @@ -18,15 +18,15 @@ func SelfUpdate(sse sendSSE, binaryPath string) error { stdoutPipe, err := cmd.StdoutPipe() if err != nil { - return fmt.Errorf("failed to create stdout pipe: %v", err) + return fmt.Errorf("failed to create stdout pipe: %w", err) } stderrPipe, err := cmd.StderrPipe() if err != nil { - return fmt.Errorf("failed to create stderr pipe: %v", err) + return fmt.Errorf("failed to create stderr pipe: %w", err) } if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to start command: %v", err) + return fmt.Errorf("failed to start command: %w", err) } done := make(chan struct{}) @@ -55,7 +55,7 @@ func SelfUpdate(sse sendSSE, binaryPath string) error { return nil case err := <-waitCmd(cmd): if err != nil { - return fmt.Errorf("update failed: %v", err) + return fmt.Errorf("update failed: %w", err) } } diff --git a/backend/user/models.go b/backend/user/models.go new file mode 100644 index 0000000..63c52da --- /dev/null +++ b/backend/user/models.go @@ -0,0 +1,6 @@ +package user + +type User struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} diff --git a/backend/user/repository.go b/backend/user/repository.go new file mode 100644 index 0000000..33c140f --- /dev/null +++ b/backend/user/repository.go @@ -0,0 +1,66 @@ +package user + +import ( + "context" + "errors" + "goaway/backend/database" + + "gorm.io/gorm" +) + +type Repository interface { + Create(user *database.User) error + FindByUsername(username string) (*User, error) + UpdatePassword(username string, hashedPassword string) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) Create(user *database.User) error { + result := r.db.Create(user) + if result.Error != nil { + return result.Error + } + + if result.RowsAffected == 0 { + return errors.New("user creation failed: no rows affected") + } + + return nil +} + +func (r *repository) FindByUsername(username string) (*User, error) { + var user database.User + err := r.db.Where("username = ?", username).Find(&user).Error + if err != nil { + return nil, err + } + + if user.Username == "" { + return nil, errors.New("user not found") + } + + return &User{ + Username: user.Username, + Password: user.Password, + }, nil +} + +func (r *repository) UpdatePassword(username, hashedPassword string) error { + affected, err := gorm.G[database.User](r.db).Where("username = ?", username).Update(context.Background(), "password", hashedPassword) + if err != nil { + return err + } + + if affected == 0 { + return errors.New("password update failed: no rows affected") + } + + return nil +} diff --git a/backend/user/service.go b/backend/user/service.go new file mode 100644 index 0000000..9442cec --- /dev/null +++ b/backend/user/service.go @@ -0,0 +1,114 @@ +package user + +import ( + "errors" + "goaway/backend/database" + "goaway/backend/logging" + "strings" + + "golang.org/x/crypto/bcrypt" +) + +type Service struct { + repository Repository +} + +var log = logging.GetLogger() + +func NewService(repo Repository) *Service { + return &Service{repository: repo} +} + +func (s *Service) CreateUser(username, password string) error { + log.Info("Creating a new user with name '%s'", username) + + hashedPassword, err := hashPassword(password) + if err != nil { + log.Error("Failed to hash password: %v", err) + return err + } + + newUser := &database.User{Username: username, Password: hashedPassword} + + if err := s.repository.Create(newUser); err != nil { + log.Error("Failed to create user: %v", err) + return err + } + + log.Debug("User created successfully") + return nil +} + +func (s *Service) Exists(username string) bool { + user, err := s.repository.FindByUsername(username) + if err != nil { + return false + } + + if user != nil { + return true + } + + return false +} + +func (s *Service) Authenticate(username, password string) bool { + user, err := s.repository.FindByUsername(username) + if err != nil { + log.Error("Authentication failed for user '%s': %v", username, err) + return false + } + + if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { + log.Debug("Invalid password for user '%s'", username) + return false + } + + log.Debug("User '%s' authenticated successfully", username) + return true +} + +func (s *Service) UpdatePassword(username, newPassword string) error { + hashedPassword, err := hashPassword(newPassword) + if err != nil { + log.Error("Failed to hash new password: %v", err) + return err + } + + if err := s.repository.UpdatePassword(username, hashedPassword); err != nil { + log.Error("Failed to update password: %v", err) + return err + } + + log.Debug("Password updated successfully for user '%s'", username) + return nil +} + +func (s *Service) ValidateCredentials(user User) error { + user.Username = strings.TrimSpace(user.Username) + user.Password = strings.TrimSpace(user.Password) + + if user.Username == "" || user.Password == "" { + return errors.New("username and password cannot be empty") + } + + if len(user.Username) > 60 { + return errors.New("username too long") + } + if len(user.Password) > 120 { + return errors.New("password too long") + } + + for _, r := range user.Username { + if r < 32 || r == 127 { + return errors.New("username contains invalid characters") + } + } + + return nil +} + +func hashPassword(password string) (string, error) { + hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(hashed), err +} diff --git a/backend/whitelist/repository.go b/backend/whitelist/repository.go new file mode 100644 index 0000000..a28aa4f --- /dev/null +++ b/backend/whitelist/repository.go @@ -0,0 +1,61 @@ +package whitelist + +import ( + "fmt" + "goaway/backend/database" + + "gorm.io/gorm" +) + +type Repository interface { + AddDomain(domain string) error + GetDomains() (map[string]bool, error) + RemoveDomain(domain string) error +} + +type repository struct { + db *gorm.DB +} + +func NewRepository(db *gorm.DB) Repository { + return &repository{db: db} +} + +func (r *repository) AddDomain(domain string) error { + result := r.db.Create(&database.Whitelist{Domain: domain}) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("%s is already whitelisted", domain) + } + + return nil +} + +func (r *repository) GetDomains() (map[string]bool, error) { + var records []database.Whitelist + result := r.db.Find(&records) + if result.Error != nil { + return nil, result.Error + } + + domainMap := make(map[string]bool) + for _, record := range records { + domainMap[record.Domain] = true + } + + return domainMap, nil +} + +func (r *repository) RemoveDomain(domain string) error { + result := r.db.Delete(&database.Whitelist{}, "domain = ?", domain) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return fmt.Errorf("%s does not exist", domain) + } + + return nil +} diff --git a/backend/whitelist/service.go b/backend/whitelist/service.go new file mode 100644 index 0000000..b6bb249 --- /dev/null +++ b/backend/whitelist/service.go @@ -0,0 +1,59 @@ +package whitelist + +import "goaway/backend/logging" + +type Service struct { + repository Repository + Cache map[string]bool +} + +var log = logging.GetLogger() + +func NewService(repo Repository) *Service { + service := &Service{ + repository: repo, + Cache: map[string]bool{}, + } + + _, err := service.GetDomains() // Preload cache + if err != nil { + log.Warning("Could not preload domains cache, %v", err) + } + + return service +} + +func (s *Service) AddDomain(domain string) error { + err := s.repository.AddDomain(domain) + if err != nil { + return err + } + + s.Cache[domain] = true + return nil +} + +func (s *Service) GetDomains() (map[string]bool, error) { + domains, err := s.repository.GetDomains() + if err != nil { + return nil, err + } + + s.Cache = domains + return domains, nil +} + +func (s *Service) RemoveDomain(domain string) error { + err := s.repository.RemoveDomain(domain) + if err != nil { + return err + } + + delete(s.Cache, domain) + return nil +} + +func (s *Service) IsWhitelisted(domain string) bool { + _, exists := s.Cache[domain] + return exists +} diff --git a/client/components.json b/client/components.json index 1396e1c..9094f9a 100644 --- a/client/components.json +++ b/client/components.json @@ -17,5 +17,8 @@ "lib": "@/lib", "hooks": "@/hooks" }, - "iconLibrary": "phosphor-icons/react" + "iconLibrary": "phosphor-icons/react", + "registries": { + "@magicui": "https://magicui.design/r/{name}.json" + } } diff --git a/client/package.json b/client/package.json index e80c569..45375b6 100644 --- a/client/package.json +++ b/client/package.json @@ -12,22 +12,8 @@ }, "dependencies": { "@phosphor-icons/react": "^2.1.10", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slider": "^1.3.6", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", - "@radix-ui/react-tooltip": "^1.2.8", "@tanstack/react-table": "^8.21.3", "@tsparticles/engine": "^3.9.1", "@tsparticles/preset-stars": "^3.2.0", @@ -37,35 +23,41 @@ "clsx": "^2.1.1", "cmdk": "1.1.1", "compare-versions": "^6.1.1", - "motion": "^12.23.12", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", "next-themes": "^0.4.6", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-force-graph-2d": "^1.28.0", - "react-router-dom": "^7.8.2", + "radix-ui": "^1.4.3", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-force-graph-2d": "^1.29.0", + "react-router-dom": "^7.9.4", "react-timeago": "^8.3.0", - "recharts": "^3.1.2", + "recharts": "^3.3.0", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", - "tw-animate-css": "^1.3.8" + "tw-animate-css": "^1.4.0" }, "devDependencies": { - "@commitlint/cli": "^19.8.1", - "@commitlint/config-conventional": "^19.8.1", + "babel-plugin-react-compiler": "^1.0.0", + "@commitlint/cli": "^20.1.0", + "@commitlint/config-conventional": "^20.0.0", "@commitlint/types": "^20.0.0", - "@eslint/js": "^9.34.0", - "@tailwindcss/vite": "^4.1.12", - "@types/react": "^19.1.12", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.2", - "eslint": "^9.34.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "globals": "^16.3.0", - "tailwindcss": "^4.1.12", - "typescript": "~5.9.2", - "typescript-eslint": "^8.42.0", - "vite": "^7.1.4" + "@eslint/js": "^9.38.0", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-select": "^2.2.6", + "@tailwindcss/vite": "^4.1.15", + "@types/canvas-confetti": "^1.9.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.38.0", + "eslint-plugin-react-hooks": "^7.0.0", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.4.0", + "tailwindcss": "^4.1.15", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.2", + "vite": "^7.1.11" }, "pnpm": { "onlyBuiltDependencies": [ diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 859e499..c50566a 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -10,58 +10,16 @@ importers: dependencies: '@phosphor-icons/react': specifier: ^2.1.10 - version: 2.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-checkbox': - specifier: ^1.3.3 - version: 1.3.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-collapsible': - specifier: ^1.1.12 - version: 1.1.12(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-dialog': - specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-dropdown-menu': - specifier: ^2.1.16 - version: 2.1.16(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-label': - specifier: ^2.1.7 - version: 2.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-popover': - specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-scroll-area': - specifier: ^1.2.10 - version: 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-select': - specifier: ^2.2.6 - version: 2.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-separator': - specifier: ^1.1.7 - version: 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slider': - specifier: ^1.3.6 - version: 1.3.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': - specifier: ^1.2.3 - version: 1.2.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-switch': - specifier: ^1.2.6 - version: 1.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-tabs': - specifier: ^1.1.13 - version: 1.1.13(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 2.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-toggle': specifier: ^1.1.10 - version: 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-toggle-group': specifier: ^1.1.11 - version: 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-tooltip': - specifier: ^1.2.8 - version: 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/react-table': specifier: ^8.21.3 - version: 8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tsparticles/engine': specifier: ^3.9.1 version: 3.9.1 @@ -70,7 +28,7 @@ importers: version: 3.2.0 '@tsparticles/react': specifier: ^3.0.0 - version: 3.0.0(@tsparticles/engine@3.9.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 3.0.0(@tsparticles/engine@3.9.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) canvas-confetti: specifier: ^1.9.3 version: 1.9.3 @@ -82,92 +40,110 @@ importers: version: 2.1.1 cmdk: specifier: 1.1.1 - version: 1.1.1(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) compare-versions: specifier: ^6.1.1 version: 6.1.1 + lucide-react: + specifier: ^0.546.0 + version: 0.546.0(react@19.2.0) motion: - specifier: ^12.23.12 - version: 12.23.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: ^12.23.24 + version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0) next-themes: specifier: ^0.4.6 - version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + radix-ui: + specifier: ^1.4.3 + version: 1.4.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: - specifier: ^19.1.1 - version: 19.1.1 + specifier: ^19.2.0 + version: 19.2.0 react-dom: - specifier: ^19.1.1 - version: 19.1.1(react@19.1.1) + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) react-force-graph-2d: - specifier: ^1.28.0 - version: 1.29.0(react@19.1.1) + specifier: ^1.29.0 + version: 1.29.0(react@19.2.0) react-router-dom: - specifier: ^7.8.2 - version: 7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: ^7.9.4 + version: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-timeago: specifier: ^8.3.0 - version: 8.3.0(react@19.1.1) + version: 8.3.0(react@19.2.0) recharts: - specifier: ^3.1.2 - version: 3.2.1(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react-is@16.13.1)(react@19.1.1)(redux@5.0.1) + specifier: ^3.3.0 + version: 3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@16.13.1)(react@19.2.0)(redux@5.0.1) sonner: specifier: ^2.0.7 - version: 2.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) tailwind-merge: specifier: ^3.3.1 version: 3.3.1 tw-animate-css: - specifier: ^1.3.8 - version: 1.3.8 + specifier: ^1.4.0 + version: 1.4.0 devDependencies: '@commitlint/cli': - specifier: ^19.8.1 - version: 19.8.1(@types/node@24.5.2)(typescript@5.9.2) + specifier: ^20.1.0 + version: 20.1.0(@types/node@24.9.1)(typescript@5.9.3) '@commitlint/config-conventional': - specifier: ^19.8.1 - version: 19.8.1 + specifier: ^20.0.0 + version: 20.0.0 '@commitlint/types': specifier: ^20.0.0 version: 20.0.0 '@eslint/js': - specifier: ^9.34.0 - version: 9.35.0 + specifier: ^9.38.0 + version: 9.38.0 + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tailwindcss/vite': - specifier: ^4.1.12 - version: 4.1.13(vite@7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1)) + specifier: ^4.1.15 + version: 4.1.15(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)) + '@types/canvas-confetti': + specifier: ^1.9.0 + version: 1.9.0 '@types/react': - specifier: ^19.1.12 - version: 19.1.13 + specifier: ^19.2.2 + version: 19.2.2 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.13) + specifier: ^19.2.2 + version: 19.2.2(@types/react@19.2.2) '@vitejs/plugin-react': - specifier: ^5.0.2 - version: 5.0.3(vite@7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1)) + specifier: ^5.0.4 + version: 5.0.4(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2)) + babel-plugin-react-compiler: + specifier: ^1.0.0 + version: 1.0.0 eslint: - specifier: ^9.34.0 - version: 9.35.0(jiti@2.5.1) + specifier: ^9.38.0 + version: 9.38.0(jiti@2.6.1) eslint-plugin-react-hooks: - specifier: ^5.2.0 - version: 5.2.0(eslint@9.35.0(jiti@2.5.1)) + specifier: ^7.0.0 + version: 7.0.0(eslint@9.38.0(jiti@2.6.1)) eslint-plugin-react-refresh: - specifier: ^0.4.20 - version: 0.4.20(eslint@9.35.0(jiti@2.5.1)) + specifier: ^0.4.24 + version: 0.4.24(eslint@9.38.0(jiti@2.6.1)) globals: - specifier: ^16.3.0 + specifier: ^16.4.0 version: 16.4.0 tailwindcss: - specifier: ^4.1.12 - version: 4.1.13 + specifier: ^4.1.15 + version: 4.1.15 typescript: - specifier: ~5.9.2 - version: 5.9.2 + specifier: ~5.9.3 + version: 5.9.3 typescript-eslint: - specifier: ^8.42.0 - version: 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + specifier: ^8.46.2 + version: 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) vite: - specifier: ^7.1.4 - version: 7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1) + specifier: ^7.1.11 + version: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2) packages: @@ -254,231 +230,227 @@ packages: resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} - '@commitlint/cli@19.8.1': - resolution: {integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==} + '@commitlint/cli@20.1.0': + resolution: {integrity: sha512-pW5ujjrOovhq5RcYv5xCpb4GkZxkO2+GtOdBW2/qrr0Ll9tl3PX0aBBobGQl3mdZUbOBgwAexEQLeH6uxL0VYg==} engines: {node: '>=v18'} hasBin: true - '@commitlint/config-conventional@19.8.1': - resolution: {integrity: sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==} + '@commitlint/config-conventional@20.0.0': + resolution: {integrity: sha512-q7JroPIkDBtyOkVe9Bca0p7kAUYxZMxkrBArCfuD3yN4KjRAenP9PmYwnn7rsw8Q+hHq1QB2BRmBh0/Z19ZoJw==} engines: {node: '>=v18'} - '@commitlint/config-validator@19.8.1': - resolution: {integrity: sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==} + '@commitlint/config-validator@20.0.0': + resolution: {integrity: sha512-BeyLMaRIJDdroJuYM2EGhDMGwVBMZna9UiIqV9hxj+J551Ctc6yoGuGSmghOy/qPhBSuhA6oMtbEiTmxECafsg==} engines: {node: '>=v18'} - '@commitlint/ensure@19.8.1': - resolution: {integrity: sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==} + '@commitlint/ensure@20.0.0': + resolution: {integrity: sha512-WBV47Fffvabe68n+13HJNFBqiMH5U1Ryls4W3ieGwPC0C7kJqp3OVQQzG2GXqOALmzrgAB+7GXmyy8N9ct8/Fg==} engines: {node: '>=v18'} - '@commitlint/execute-rule@19.8.1': - resolution: {integrity: sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==} + '@commitlint/execute-rule@20.0.0': + resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} engines: {node: '>=v18'} - '@commitlint/format@19.8.1': - resolution: {integrity: sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==} + '@commitlint/format@20.0.0': + resolution: {integrity: sha512-zrZQXUcSDmQ4eGGrd+gFESiX0Rw+WFJk7nW4VFOmxub4mAATNKBQ4vNw5FgMCVehLUKG2OT2LjOqD0Hk8HvcRg==} engines: {node: '>=v18'} - '@commitlint/is-ignored@19.8.1': - resolution: {integrity: sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==} + '@commitlint/is-ignored@20.0.0': + resolution: {integrity: sha512-ayPLicsqqGAphYIQwh9LdAYOVAQ9Oe5QCgTNTj+BfxZb9b/JW222V5taPoIBzYnAP0z9EfUtljgBk+0BN4T4Cw==} engines: {node: '>=v18'} - '@commitlint/lint@19.8.1': - resolution: {integrity: sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==} + '@commitlint/lint@20.0.0': + resolution: {integrity: sha512-kWrX8SfWk4+4nCexfLaQT3f3EcNjJwJBsSZ5rMBw6JCd6OzXufFHgel2Curos4LKIxwec9WSvs2YUD87rXlxNQ==} engines: {node: '>=v18'} - '@commitlint/load@19.8.1': - resolution: {integrity: sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==} + '@commitlint/load@20.1.0': + resolution: {integrity: sha512-qo9ER0XiAimATQR5QhvvzePfeDfApi/AFlC1G+YN+ZAY8/Ua6IRrDrxRvQAr+YXUKAxUsTDSp9KXeXLBPsNRWg==} engines: {node: '>=v18'} - '@commitlint/message@19.8.1': - resolution: {integrity: sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==} + '@commitlint/message@20.0.0': + resolution: {integrity: sha512-gLX4YmKnZqSwkmSB9OckQUrI5VyXEYiv3J5JKZRxIp8jOQsWjZgHSG/OgEfMQBK9ibdclEdAyIPYggwXoFGXjQ==} engines: {node: '>=v18'} - '@commitlint/parse@19.8.1': - resolution: {integrity: sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==} + '@commitlint/parse@20.0.0': + resolution: {integrity: sha512-j/PHCDX2bGM5xGcWObOvpOc54cXjn9g6xScXzAeOLwTsScaL4Y+qd0pFC6HBwTtrH92NvJQc+2Lx9HFkVi48cg==} engines: {node: '>=v18'} - '@commitlint/read@19.8.1': - resolution: {integrity: sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==} + '@commitlint/read@20.0.0': + resolution: {integrity: sha512-Ti7Y7aEgxsM1nkwA4ZIJczkTFRX/+USMjNrL9NXwWQHqNqrBX2iMi+zfuzZXqfZ327WXBjdkRaytJ+z5vNqTOA==} engines: {node: '>=v18'} - '@commitlint/resolve-extends@19.8.1': - resolution: {integrity: sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==} + '@commitlint/resolve-extends@20.1.0': + resolution: {integrity: sha512-cxKXQrqHjZT3o+XPdqDCwOWVFQiae++uwd9dUBC7f2MdV58ons3uUvASdW7m55eat5sRiQ6xUHyMWMRm6atZWw==} engines: {node: '>=v18'} - '@commitlint/rules@19.8.1': - resolution: {integrity: sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==} + '@commitlint/rules@20.0.0': + resolution: {integrity: sha512-gvg2k10I/RfvHn5I5sxvVZKM1fl72Sqrv2YY/BnM7lMHcYqO0E2jnRWoYguvBfEcZ39t+rbATlciggVe77E4zA==} engines: {node: '>=v18'} - '@commitlint/to-lines@19.8.1': - resolution: {integrity: sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==} + '@commitlint/to-lines@20.0.0': + resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==} engines: {node: '>=v18'} - '@commitlint/top-level@19.8.1': - resolution: {integrity: sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==} - engines: {node: '>=v18'} - - '@commitlint/types@19.8.1': - resolution: {integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==} + '@commitlint/top-level@20.0.0': + resolution: {integrity: sha512-drXaPSP2EcopukrUXvUXmsQMu3Ey/FuJDc/5oiW4heoCfoE5BdLQyuc7veGeE3aoQaTVqZnh4D5WTWe2vefYKg==} engines: {node: '>=v18'} '@commitlint/types@20.0.0': resolution: {integrity: sha512-bVUNBqG6aznYcYjTjnc3+Cat/iBgbgpflxbIBTnsHTX0YVpnmINPEkSRWymT2Q8aSH3Y7aKnEbunilkYe8TybA==} engines: {node: '>=v18'} - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -489,36 +461,36 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.0': - resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.3.1': - resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + '@eslint/config-helpers@0.4.1': + resolution: {integrity: sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.15.2': - resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.35.0': - resolution: {integrity: sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==} + '@eslint/js@9.38.0': + resolution: {integrity: sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.5': - resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + '@eslint/plugin-kit@0.4.0': + resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@floating-ui/core@1.7.3': @@ -552,10 +524,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -597,6 +565,45 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: @@ -610,6 +617,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-checkbox@1.3.3': resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} peerDependencies: @@ -658,6 +691,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: @@ -737,6 +783,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -772,6 +844,58 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popover@1.1.15': resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} peerDependencies: @@ -837,6 +961,32 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-roving-focus@1.1.11': resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} peerDependencies: @@ -937,6 +1087,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toggle-group@1.1.11': resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} peerDependencies: @@ -963,6 +1126,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-tooltip@1.2.8': resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} peerDependencies: @@ -1012,6 +1188,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-layout-effect@1.1.1': resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: @@ -1064,8 +1249,8 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@reduxjs/toolkit@2.9.0': - resolution: {integrity: sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==} + '@reduxjs/toolkit@2.9.1': + resolution: {integrity: sha512-sETJ3qO72y7L7WiR5K54UFLT3jRzAtqeBPVO15xC3bGA6kDqCH8m/v7BKCPH4czydXzz/1lPEGLvew7GjOO3Qw==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 || ^19 react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 @@ -1075,111 +1260,116 @@ packages: react-redux: optional: true - '@rolldown/pluginutils@1.0.0-beta.35': - resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==} + '@rolldown/pluginutils@1.0.0-beta.38': + resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} - '@rollup/rollup-android-arm-eabi@4.50.2': - resolution: {integrity: sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==} + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.50.2': - resolution: {integrity: sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==} + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.50.2': - resolution: {integrity: sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==} + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.50.2': - resolution: {integrity: sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==} + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.50.2': - resolution: {integrity: sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==} + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.50.2': - resolution: {integrity: sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==} + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.50.2': - resolution: {integrity: sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==} + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.50.2': - resolution: {integrity: sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==} + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.50.2': - resolution: {integrity: sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==} + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.50.2': - resolution: {integrity: sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==} + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.50.2': - resolution: {integrity: sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==} + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.50.2': - resolution: {integrity: sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==} + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.50.2': - resolution: {integrity: sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==} + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.50.2': - resolution: {integrity: sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==} + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.50.2': - resolution: {integrity: sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==} + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.50.2': - resolution: {integrity: sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==} + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.50.2': - resolution: {integrity: sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==} + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.50.2': - resolution: {integrity: sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==} + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.50.2': - resolution: {integrity: sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==} + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.50.2': - resolution: {integrity: sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==} + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.50.2': - resolution: {integrity: sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==} + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} cpu: [x64] os: [win32] @@ -1189,65 +1379,65 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@tailwindcss/node@4.1.13': - resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} + '@tailwindcss/node@4.1.15': + resolution: {integrity: sha512-HF4+7QxATZWY3Jr8OlZrBSXmwT3Watj0OogeDvdUY/ByXJHQ+LBtqA2brDb3sBxYslIFx6UP94BJ4X6a4L9Bmw==} - '@tailwindcss/oxide-android-arm64@4.1.13': - resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} + '@tailwindcss/oxide-android-arm64@4.1.15': + resolution: {integrity: sha512-TkUkUgAw8At4cBjCeVCRMc/guVLKOU1D+sBPrHt5uVcGhlbVKxrCaCW9OKUIBv1oWkjh4GbunD/u/Mf0ql6kEA==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.13': - resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} + '@tailwindcss/oxide-darwin-arm64@4.1.15': + resolution: {integrity: sha512-xt5XEJpn2piMSfvd1UFN6jrWXyaKCwikP4Pidcf+yfHTSzSpYhG3dcMktjNkQO3JiLCp+0bG0HoWGvz97K162w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.13': - resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} + '@tailwindcss/oxide-darwin-x64@4.1.15': + resolution: {integrity: sha512-TnWaxP6Bx2CojZEXAV2M01Yl13nYPpp0EtGpUrY+LMciKfIXiLL2r/SiSRpagE5Fp2gX+rflp/Os1VJDAyqymg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.13': - resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} + '@tailwindcss/oxide-freebsd-x64@4.1.15': + resolution: {integrity: sha512-quISQDWqiB6Cqhjc3iWptXVZHNVENsWoI77L1qgGEHNIdLDLFnw3/AfY7DidAiiCIkGX/MjIdB3bbBZR/G2aJg==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': - resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.15': + resolution: {integrity: sha512-ObG76+vPlab65xzVUQbExmDU9FIeYLQ5k2LrQdR2Ud6hboR+ZobXpDoKEYXf/uOezOfIYmy2Ta3w0ejkTg9yxg==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': - resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} + '@tailwindcss/oxide-linux-arm64-gnu@4.1.15': + resolution: {integrity: sha512-4WbBacRmk43pkb8/xts3wnOZMDKsPFyEH/oisCm2q3aLZND25ufvJKcDUpAu0cS+CBOL05dYa8D4U5OWECuH/Q==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': - resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} + '@tailwindcss/oxide-linux-arm64-musl@4.1.15': + resolution: {integrity: sha512-AbvmEiteEj1nf42nE8skdHv73NoR+EwXVSgPY6l39X12Ex8pzOwwfi3Kc8GAmjsnsaDEbk+aj9NyL3UeyHcTLg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': - resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} + '@tailwindcss/oxide-linux-x64-gnu@4.1.15': + resolution: {integrity: sha512-+rzMVlvVgrXtFiS+ES78yWgKqpThgV19ISKD58Ck+YO5pO5KjyxLt7AWKsWMbY0R9yBDC82w6QVGz837AKQcHg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-linux-x64-musl@4.1.13': - resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} + '@tailwindcss/oxide-linux-x64-musl@4.1.15': + resolution: {integrity: sha512-fPdEy7a8eQN9qOIK3Em9D3TO1z41JScJn8yxl/76mp4sAXFDfV4YXxsiptJcOwy6bGR+70ZSwFIZhTXzQeqwQg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@tailwindcss/oxide-wasm32-wasi@4.1.13': - resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} + '@tailwindcss/oxide-wasm32-wasi@4.1.15': + resolution: {integrity: sha512-sJ4yd6iXXdlgIMfIBXuVGp/NvmviEoMVWMOAGxtxhzLPp9LOj5k0pMEMZdjeMCl4C6Up+RM8T3Zgk+BMQ0bGcQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1258,24 +1448,24 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': - resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} + '@tailwindcss/oxide-win32-arm64-msvc@4.1.15': + resolution: {integrity: sha512-sJGE5faXnNQ1iXeqmRin7Ds/ru2fgCiaQZQQz3ZGIDtvbkeV85rAZ0QJFMDg0FrqsffZG96H1U9AQlNBRLsHVg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': - resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} + '@tailwindcss/oxide-win32-x64-msvc@4.1.15': + resolution: {integrity: sha512-NLeHE7jUV6HcFKS504bpOohyi01zPXi2PXmjFfkzTph8xRxDdxkRsXm/xDO5uV5K3brrE1cCwbUYmFUSHR3u1w==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.13': - resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} + '@tailwindcss/oxide@4.1.15': + resolution: {integrity: sha512-krhX+UOOgnsUuks2SR7hFafXmLQrKxB4YyRTERuCE59JlYL+FawgaAlSkOYmDRJdf1Q+IFNDMl9iRnBW7QBDfQ==} engines: {node: '>= 10'} - '@tailwindcss/vite@4.1.13': - resolution: {integrity: sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==} + '@tailwindcss/vite@4.1.15': + resolution: {integrity: sha512-B6s60MZRTUil+xKoZoGe6i0Iar5VuW+pmcGlda2FX+guDuQ1G1sjiIy1W0frneVpeL/ZjZ4KEgWZHNrIm++2qA==} peerDependencies: vite: ^5.2.0 || ^6 || ^7 @@ -1348,6 +1538,9 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/canvas-confetti@1.9.0': + resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==} + '@types/conventional-commits-parser@5.0.1': resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} @@ -1384,81 +1577,81 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@24.5.2': - resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==} + '@types/node@24.9.1': + resolution: {integrity: sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==} - '@types/react-dom@19.1.9': - resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} + '@types/react-dom@19.2.2': + resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} peerDependencies: - '@types/react': ^19.0.0 + '@types/react': ^19.2.0 - '@types/react@19.1.13': - resolution: {integrity: sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@typescript-eslint/eslint-plugin@8.44.0': - resolution: {integrity: sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==} + '@typescript-eslint/eslint-plugin@8.46.2': + resolution: {integrity: sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.44.0 + '@typescript-eslint/parser': ^8.46.2 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.44.0': - resolution: {integrity: sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==} + '@typescript-eslint/parser@8.46.2': + resolution: {integrity: sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.44.0': - resolution: {integrity: sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==} + '@typescript-eslint/project-service@8.46.2': + resolution: {integrity: sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.44.0': - resolution: {integrity: sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==} + '@typescript-eslint/scope-manager@8.46.2': + resolution: {integrity: sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.44.0': - resolution: {integrity: sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==} + '@typescript-eslint/tsconfig-utils@8.46.2': + resolution: {integrity: sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.44.0': - resolution: {integrity: sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==} + '@typescript-eslint/type-utils@8.46.2': + resolution: {integrity: sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.44.0': - resolution: {integrity: sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==} + '@typescript-eslint/types@8.46.2': + resolution: {integrity: sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.44.0': - resolution: {integrity: sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==} + '@typescript-eslint/typescript-estree@8.46.2': + resolution: {integrity: sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.44.0': - resolution: {integrity: sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==} + '@typescript-eslint/utils@8.46.2': + resolution: {integrity: sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.44.0': - resolution: {integrity: sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==} + '@typescript-eslint/visitor-keys@8.46.2': + resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitejs/plugin-react@5.0.3': - resolution: {integrity: sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg==} + '@vitejs/plugin-react@5.0.4': + resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -1505,11 +1698,14 @@ packages: array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.5: - resolution: {integrity: sha512-TiU4qUT9jdCuh4aVOG7H1QozyeI2sZRqoRPdqBIaslfNt4WUSanRBueAwl2x5jt4rXBMim3lIN2x6yT8PDi24Q==} + baseline-browser-mapping@2.8.19: + resolution: {integrity: sha512-zoKGUdu6vb2jd3YOq0nnhEDQVbPcHhco3UImJrv5dSkvxTc2pl2WjOPsjZXDwPDSl5eghIMuY3R6J9NDKF3KcQ==} hasBin: true bezier-js@6.1.4: @@ -1525,8 +1721,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.2: - resolution: {integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==} + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1534,8 +1730,8 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001743: - resolution: {integrity: sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==} + caniuse-lite@1.0.30001751: + resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==} canvas-color-tracker@1.3.2: resolution: {integrity: sha512-ryQkDX26yJ3CXzb3hxUVNlg1NKE4REc5crLBq661Nxzr8TNd236SaEf2ffYLXyI5tSABSeguHLqcVq4vf9L3Zg==} @@ -1552,10 +1748,6 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1609,8 +1801,8 @@ packages: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} - cosmiconfig-typescript-loader@6.1.0: - resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} + cosmiconfig-typescript-loader@6.2.0: + resolution: {integrity: sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==} engines: {node: '>=v18'} peerDependencies: '@types/node': '*' @@ -1736,8 +1928,8 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - detect-libc@2.1.0: - resolution: {integrity: sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-node-es@1.1.0: @@ -1747,8 +1939,8 @@ packages: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} - electron-to-chromium@1.5.221: - resolution: {integrity: sha512-/1hFJ39wkW01ogqSyYoA4goOXOtMRy6B+yvA1u42nnsEGtHzIzmk93aPISumVQeblj47JUHLC9coCjUxb1EvtQ==} + electron-to-chromium@1.5.238: + resolution: {integrity: sha512-khBdc+w/Gv+cS8e/Pbnaw/FXcBUeKrRVik9IxfXtgREOWyJhR4tj43n3amkVogJ/yeQUqzkrZcFhtIxIdqmmcQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1764,11 +1956,11 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-toolkit@1.39.10: - resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==} + es-toolkit@1.40.0: + resolution: {integrity: sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q==} - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} engines: {node: '>=18'} hasBin: true @@ -1780,14 +1972,14 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} + eslint-plugin-react-hooks@7.0.0: + resolution: {integrity: sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==} + engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react-refresh@0.4.20: - resolution: {integrity: sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==} + eslint-plugin-react-refresh@0.4.24: + resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==} peerDependencies: eslint: '>=8.40' @@ -1803,8 +1995,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.35.0: - resolution: {integrity: sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==} + eslint@9.38.0: + resolution: {integrity: sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1895,8 +2087,8 @@ packages: resolution: {integrity: sha512-aTnihCmiMA0ItLJLCbrQYS9mzriopW24goFPgUnKAAmAlPogTSmFWqoBPMXzIfPb7bs04Hur5zEI4WYgLW3Sig==} engines: {node: '>=12'} - framer-motion@12.23.15: - resolution: {integrity: sha512-MBxUEHjWr/fRQ2aHg2CgdcjJpHMJ9ttHiPeClHDGiOJOYgmde3OXZUrbWDeeE8yvFrWA62hsPS4+rKQ9OJaoQA==} + framer-motion@12.23.24: + resolution: {integrity: sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -1961,6 +2153,12 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2029,8 +2227,8 @@ packages: resolution: {integrity: sha512-YKnxXEekXKzhpf7CLYA0A+oDP8V0OhICNCr5lv96FvSsDEmrb0GKM776JgQvHTMjr7DTTPEVv/1Ciaw0uEWzBA==} engines: {node: '>=12'} - jiti@2.5.1: - resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true js-tokens@4.0.0: @@ -2080,68 +2278,74 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} lines-and-columns@1.2.4: @@ -2192,6 +2396,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.546.0: + resolution: {integrity: sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -2217,27 +2426,14 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@3.0.2: - resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} - engines: {node: '>= 18'} - - mkdirp@3.0.1: - resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} - engines: {node: '>=10'} - hasBin: true - - motion-dom@12.23.12: - resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} + motion-dom@12.23.23: + resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==} motion-utils@12.23.6: resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} - motion@12.23.15: - resolution: {integrity: sha512-KOxnEDztmHM/cIdWap9PYs36cOlVe8pqBJjhmmY0NxDnnN4gokQFDrpxUkh56RyQ+gnT0daaDy5g0T6sHfVBJg==} + motion@12.23.24: + resolution: {integrity: sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2267,8 +2463,8 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - node-releases@2.0.21: - resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} + node-releases@2.0.26: + resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -2346,10 +2542,23 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@19.1.1: - resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} peerDependencies: - react: ^19.1.1 + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + peerDependencies: + react: ^19.2.0 react-force-graph-2d@1.29.0: resolution: {integrity: sha512-Xv5IIk+hsZmB3F2ibja/t6j/b0/1T9dtFOQacTUoLpgzRHrO6wPu1GtQ2LfRqI/imgtaapnXUgQaE8g8enPo5w==} @@ -2402,15 +2611,15 @@ packages: '@types/react': optional: true - react-router-dom@7.9.1: - resolution: {integrity: sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==} + react-router-dom@7.9.4: + resolution: {integrity: sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' - react-router@7.9.1: - resolution: {integrity: sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==} + react-router@7.9.4: + resolution: {integrity: sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==} engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' @@ -2434,12 +2643,12 @@ packages: peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react@19.1.1: - resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} - recharts@3.2.1: - resolution: {integrity: sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==} + recharts@3.3.0: + resolution: {integrity: sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2477,23 +2686,23 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.50.2: - resolution: {integrity: sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==} + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true @@ -2541,17 +2750,13 @@ packages: tailwind-merge@3.3.1: resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} - tailwindcss@4.1.13: - resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} + tailwindcss@4.1.15: + resolution: {integrity: sha512-k2WLnWkYFkdpRv+Oby3EBXIyQC8/s1HOFMBUViwtAh6Z5uAozeUSMQlIsn/c6Q2iJzqG6aJT3wdPaRNj70iYxQ==} - tapable@2.2.3: - resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - tar@7.4.3: - resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} - engines: {node: '>=18'} - text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -2585,34 +2790,34 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tw-animate-css@1.3.8: - resolution: {integrity: sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==} + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.44.0: - resolution: {integrity: sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==} + typescript-eslint@8.46.2: + resolution: {integrity: sha512-vbw8bOmiuYNdzzV3lsiWv6sRwjyuKJMQqWulBOU7M0RrxedXledX8G8kBbQeiOYDnTfiXz0Y4081E1QMNB6iQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true - undici-types@7.12.0: - resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2640,16 +2845,16 @@ packages: '@types/react': optional: true - use-sync-external-store@1.5.0: - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - vite@7.1.6: - resolution: {integrity: sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==} + vite@7.1.11: + resolution: {integrity: sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2708,10 +2913,6 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -2728,6 +2929,15 @@ packages: resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} engines: {node: '>=12.20'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + snapshots: '@babel/code-frame@7.27.1': @@ -2770,7 +2980,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.4 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.2 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -2842,66 +3052,66 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@commitlint/cli@19.8.1(@types/node@24.5.2)(typescript@5.9.2)': + '@commitlint/cli@20.1.0(@types/node@24.9.1)(typescript@5.9.3)': dependencies: - '@commitlint/format': 19.8.1 - '@commitlint/lint': 19.8.1 - '@commitlint/load': 19.8.1(@types/node@24.5.2)(typescript@5.9.2) - '@commitlint/read': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/format': 20.0.0 + '@commitlint/lint': 20.0.0 + '@commitlint/load': 20.1.0(@types/node@24.9.1)(typescript@5.9.3) + '@commitlint/read': 20.0.0 + '@commitlint/types': 20.0.0 tinyexec: 1.0.1 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' - typescript - '@commitlint/config-conventional@19.8.1': + '@commitlint/config-conventional@20.0.0': dependencies: - '@commitlint/types': 19.8.1 + '@commitlint/types': 20.0.0 conventional-changelog-conventionalcommits: 7.0.2 - '@commitlint/config-validator@19.8.1': + '@commitlint/config-validator@20.0.0': dependencies: - '@commitlint/types': 19.8.1 + '@commitlint/types': 20.0.0 ajv: 8.17.1 - '@commitlint/ensure@19.8.1': + '@commitlint/ensure@20.0.0': dependencies: - '@commitlint/types': 19.8.1 + '@commitlint/types': 20.0.0 lodash.camelcase: 4.3.0 lodash.kebabcase: 4.1.1 lodash.snakecase: 4.1.1 lodash.startcase: 4.4.0 lodash.upperfirst: 4.3.1 - '@commitlint/execute-rule@19.8.1': {} + '@commitlint/execute-rule@20.0.0': {} - '@commitlint/format@19.8.1': + '@commitlint/format@20.0.0': dependencies: - '@commitlint/types': 19.8.1 + '@commitlint/types': 20.0.0 chalk: 5.6.2 - '@commitlint/is-ignored@19.8.1': + '@commitlint/is-ignored@20.0.0': dependencies: - '@commitlint/types': 19.8.1 - semver: 7.7.2 + '@commitlint/types': 20.0.0 + semver: 7.7.3 - '@commitlint/lint@19.8.1': + '@commitlint/lint@20.0.0': dependencies: - '@commitlint/is-ignored': 19.8.1 - '@commitlint/parse': 19.8.1 - '@commitlint/rules': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/is-ignored': 20.0.0 + '@commitlint/parse': 20.0.0 + '@commitlint/rules': 20.0.0 + '@commitlint/types': 20.0.0 - '@commitlint/load@19.8.1(@types/node@24.5.2)(typescript@5.9.2)': + '@commitlint/load@20.1.0(@types/node@24.9.1)(typescript@5.9.3)': dependencies: - '@commitlint/config-validator': 19.8.1 - '@commitlint/execute-rule': 19.8.1 - '@commitlint/resolve-extends': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/config-validator': 20.0.0 + '@commitlint/execute-rule': 20.0.0 + '@commitlint/resolve-extends': 20.1.0 + '@commitlint/types': 20.0.0 chalk: 5.6.2 - cosmiconfig: 9.0.0(typescript@5.9.2) - cosmiconfig-typescript-loader: 6.1.0(@types/node@24.5.2)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2) + cosmiconfig: 9.0.0(typescript@5.9.3) + cosmiconfig-typescript-loader: 6.2.0(@types/node@24.9.1)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -2909,150 +3119,147 @@ snapshots: - '@types/node' - typescript - '@commitlint/message@19.8.1': {} + '@commitlint/message@20.0.0': {} - '@commitlint/parse@19.8.1': + '@commitlint/parse@20.0.0': dependencies: - '@commitlint/types': 19.8.1 + '@commitlint/types': 20.0.0 conventional-changelog-angular: 7.0.0 conventional-commits-parser: 5.0.0 - '@commitlint/read@19.8.1': + '@commitlint/read@20.0.0': dependencies: - '@commitlint/top-level': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/top-level': 20.0.0 + '@commitlint/types': 20.0.0 git-raw-commits: 4.0.0 minimist: 1.2.8 tinyexec: 1.0.1 - '@commitlint/resolve-extends@19.8.1': + '@commitlint/resolve-extends@20.1.0': dependencies: - '@commitlint/config-validator': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/config-validator': 20.0.0 + '@commitlint/types': 20.0.0 global-directory: 4.0.1 import-meta-resolve: 4.2.0 lodash.mergewith: 4.6.2 resolve-from: 5.0.0 - '@commitlint/rules@19.8.1': + '@commitlint/rules@20.0.0': dependencies: - '@commitlint/ensure': 19.8.1 - '@commitlint/message': 19.8.1 - '@commitlint/to-lines': 19.8.1 - '@commitlint/types': 19.8.1 + '@commitlint/ensure': 20.0.0 + '@commitlint/message': 20.0.0 + '@commitlint/to-lines': 20.0.0 + '@commitlint/types': 20.0.0 - '@commitlint/to-lines@19.8.1': {} + '@commitlint/to-lines@20.0.0': {} - '@commitlint/top-level@19.8.1': + '@commitlint/top-level@20.0.0': dependencies: find-up: 7.0.0 - '@commitlint/types@19.8.1': - dependencies: - '@types/conventional-commits-parser': 5.0.1 - chalk: 5.6.2 - '@commitlint/types@20.0.0': dependencies: '@types/conventional-commits-parser': 5.0.1 chalk: 5.6.2 - '@esbuild/aix-ppc64@0.25.10': + '@esbuild/aix-ppc64@0.25.11': optional: true - '@esbuild/android-arm64@0.25.10': + '@esbuild/android-arm64@0.25.11': optional: true - '@esbuild/android-arm@0.25.10': + '@esbuild/android-arm@0.25.11': optional: true - '@esbuild/android-x64@0.25.10': + '@esbuild/android-x64@0.25.11': optional: true - '@esbuild/darwin-arm64@0.25.10': + '@esbuild/darwin-arm64@0.25.11': optional: true - '@esbuild/darwin-x64@0.25.10': + '@esbuild/darwin-x64@0.25.11': optional: true - '@esbuild/freebsd-arm64@0.25.10': + '@esbuild/freebsd-arm64@0.25.11': optional: true - '@esbuild/freebsd-x64@0.25.10': + '@esbuild/freebsd-x64@0.25.11': optional: true - '@esbuild/linux-arm64@0.25.10': + '@esbuild/linux-arm64@0.25.11': optional: true - '@esbuild/linux-arm@0.25.10': + '@esbuild/linux-arm@0.25.11': optional: true - '@esbuild/linux-ia32@0.25.10': + '@esbuild/linux-ia32@0.25.11': optional: true - '@esbuild/linux-loong64@0.25.10': + '@esbuild/linux-loong64@0.25.11': optional: true - '@esbuild/linux-mips64el@0.25.10': + '@esbuild/linux-mips64el@0.25.11': optional: true - '@esbuild/linux-ppc64@0.25.10': + '@esbuild/linux-ppc64@0.25.11': optional: true - '@esbuild/linux-riscv64@0.25.10': + '@esbuild/linux-riscv64@0.25.11': optional: true - '@esbuild/linux-s390x@0.25.10': + '@esbuild/linux-s390x@0.25.11': optional: true - '@esbuild/linux-x64@0.25.10': + '@esbuild/linux-x64@0.25.11': optional: true - '@esbuild/netbsd-arm64@0.25.10': + '@esbuild/netbsd-arm64@0.25.11': optional: true - '@esbuild/netbsd-x64@0.25.10': + '@esbuild/netbsd-x64@0.25.11': optional: true - '@esbuild/openbsd-arm64@0.25.10': + '@esbuild/openbsd-arm64@0.25.11': optional: true - '@esbuild/openbsd-x64@0.25.10': + '@esbuild/openbsd-x64@0.25.11': optional: true - '@esbuild/openharmony-arm64@0.25.10': + '@esbuild/openharmony-arm64@0.25.11': optional: true - '@esbuild/sunos-x64@0.25.10': + '@esbuild/sunos-x64@0.25.11': optional: true - '@esbuild/win32-arm64@0.25.10': + '@esbuild/win32-arm64@0.25.11': optional: true - '@esbuild/win32-ia32@0.25.10': + '@esbuild/win32-ia32@0.25.11': optional: true - '@esbuild/win32-x64@0.25.10': + '@esbuild/win32-x64@0.25.11': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.35.0(jiti@2.5.1))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.38.0(jiti@2.6.1))': dependencies: - eslint: 9.35.0(jiti@2.5.1) + eslint: 9.38.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.0': + '@eslint/config-array@0.21.1': dependencies: - '@eslint/object-schema': 2.1.6 + '@eslint/object-schema': 2.1.7 debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.3.1': {} + '@eslint/config-helpers@0.4.1': + dependencies: + '@eslint/core': 0.16.0 - '@eslint/core@0.15.2': + '@eslint/core@0.16.0': dependencies: '@types/json-schema': 7.0.15 @@ -3070,13 +3277,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.35.0': {} + '@eslint/js@9.38.0': {} - '@eslint/object-schema@2.1.6': {} + '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.3.5': + '@eslint/plugin-kit@0.4.0': dependencies: - '@eslint/core': 0.15.2 + '@eslint/core': 0.16.0 levn: 0.4.1 '@floating-ui/core@1.7.3': @@ -3088,11 +3295,11 @@ snapshots: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@floating-ui/react-dom@2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@floating-ui/dom': 1.7.4 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) '@floating-ui/utils@0.2.10': {} @@ -3107,10 +3314,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3142,506 +3345,759 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@phosphor-icons/react@2.1.10(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@phosphor-icons/react@2.1.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - react: 19.1.1 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-context@1.1.2(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - react: 19.1.1 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.13)(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-direction@1.1.1(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-id@1.1.1(@types/react@19.1.13)(react@19.1.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.13 - - '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) - - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.13)(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.13)(react@19.1.1) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) '@radix-ui/rect': 1.1.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) aria-hidden: 1.2.6 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-remove-scroll: 2.7.1(@types/react@19.1.13)(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-slider@1.3.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-slot@1.2.3(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - react: 19.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.1.1 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: '@radix-ui/rect': 1.1.1 - react: 19.1.1 + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-use-size@1.1.1(@types/react@19.1.13)(react@19.1.1)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.13)(react@19.1.1) - react: 19.1.1 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 - '@types/react-dom': 19.1.9(@types/react@19.1.13) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) '@radix-ui/rect@1.1.1': {} - '@reduxjs/toolkit@2.9.0(react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1))(react@19.1.1)': + '@reduxjs/toolkit@2.9.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0)': dependencies: '@standard-schema/spec': 1.0.0 '@standard-schema/utils': 0.3.0 @@ -3650,154 +4106,154 @@ snapshots: redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.1 optionalDependencies: - react: 19.1.1 - react-redux: 9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1) + react: 19.2.0 + react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) - '@rolldown/pluginutils@1.0.0-beta.35': {} + '@rolldown/pluginutils@1.0.0-beta.38': {} - '@rollup/rollup-android-arm-eabi@4.50.2': + '@rollup/rollup-android-arm-eabi@4.52.5': optional: true - '@rollup/rollup-android-arm64@4.50.2': + '@rollup/rollup-android-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-arm64@4.50.2': + '@rollup/rollup-darwin-arm64@4.52.5': optional: true - '@rollup/rollup-darwin-x64@4.50.2': + '@rollup/rollup-darwin-x64@4.52.5': optional: true - '@rollup/rollup-freebsd-arm64@4.50.2': + '@rollup/rollup-freebsd-arm64@4.52.5': optional: true - '@rollup/rollup-freebsd-x64@4.50.2': + '@rollup/rollup-freebsd-x64@4.52.5': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.50.2': + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.50.2': + '@rollup/rollup-linux-arm-musleabihf@4.52.5': optional: true - '@rollup/rollup-linux-arm64-gnu@4.50.2': + '@rollup/rollup-linux-arm64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-arm64-musl@4.50.2': + '@rollup/rollup-linux-arm64-musl@4.52.5': optional: true - '@rollup/rollup-linux-loong64-gnu@4.50.2': + '@rollup/rollup-linux-loong64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.50.2': + '@rollup/rollup-linux-ppc64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.50.2': + '@rollup/rollup-linux-riscv64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-riscv64-musl@4.50.2': + '@rollup/rollup-linux-riscv64-musl@4.52.5': optional: true - '@rollup/rollup-linux-s390x-gnu@4.50.2': + '@rollup/rollup-linux-s390x-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-gnu@4.50.2': + '@rollup/rollup-linux-x64-gnu@4.52.5': optional: true - '@rollup/rollup-linux-x64-musl@4.50.2': + '@rollup/rollup-linux-x64-musl@4.52.5': optional: true - '@rollup/rollup-openharmony-arm64@4.50.2': + '@rollup/rollup-openharmony-arm64@4.52.5': optional: true - '@rollup/rollup-win32-arm64-msvc@4.50.2': + '@rollup/rollup-win32-arm64-msvc@4.52.5': optional: true - '@rollup/rollup-win32-ia32-msvc@4.50.2': + '@rollup/rollup-win32-ia32-msvc@4.52.5': optional: true - '@rollup/rollup-win32-x64-msvc@4.50.2': + '@rollup/rollup-win32-x64-gnu@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.5': optional: true '@standard-schema/spec@1.0.0': {} '@standard-schema/utils@0.3.0': {} - '@tailwindcss/node@4.1.13': + '@tailwindcss/node@4.1.15': dependencies: '@jridgewell/remapping': 2.3.5 enhanced-resolve: 5.18.3 - jiti: 2.5.1 - lightningcss: 1.30.1 + jiti: 2.6.1 + lightningcss: 1.30.2 magic-string: 0.30.19 source-map-js: 1.2.1 - tailwindcss: 4.1.13 + tailwindcss: 4.1.15 - '@tailwindcss/oxide-android-arm64@4.1.13': + '@tailwindcss/oxide-android-arm64@4.1.15': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.13': + '@tailwindcss/oxide-darwin-arm64@4.1.15': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.13': + '@tailwindcss/oxide-darwin-x64@4.1.15': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.13': + '@tailwindcss/oxide-freebsd-x64@4.1.15': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.15': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + '@tailwindcss/oxide-linux-arm64-gnu@4.1.15': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + '@tailwindcss/oxide-linux-arm64-musl@4.1.15': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + '@tailwindcss/oxide-linux-x64-gnu@4.1.15': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.13': + '@tailwindcss/oxide-linux-x64-musl@4.1.15': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.13': + '@tailwindcss/oxide-wasm32-wasi@4.1.15': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + '@tailwindcss/oxide-win32-arm64-msvc@4.1.15': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + '@tailwindcss/oxide-win32-x64-msvc@4.1.15': optional: true - '@tailwindcss/oxide@4.1.13': - dependencies: - detect-libc: 2.1.0 - tar: 7.4.3 + '@tailwindcss/oxide@4.1.15': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-arm64': 4.1.13 - '@tailwindcss/oxide-darwin-x64': 4.1.13 - '@tailwindcss/oxide-freebsd-x64': 4.1.13 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 - '@tailwindcss/oxide-linux-x64-musl': 4.1.13 - '@tailwindcss/oxide-wasm32-wasi': 4.1.13 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 + '@tailwindcss/oxide-android-arm64': 4.1.15 + '@tailwindcss/oxide-darwin-arm64': 4.1.15 + '@tailwindcss/oxide-darwin-x64': 4.1.15 + '@tailwindcss/oxide-freebsd-x64': 4.1.15 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.15 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.15 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.15 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.15 + '@tailwindcss/oxide-linux-x64-musl': 4.1.15 + '@tailwindcss/oxide-wasm32-wasi': 4.1.15 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.15 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.15 - '@tailwindcss/vite@4.1.13(vite@7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1))': + '@tailwindcss/vite@4.1.15(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: - '@tailwindcss/node': 4.1.13 - '@tailwindcss/oxide': 4.1.13 - tailwindcss: 4.1.13 - vite: 7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1) + '@tailwindcss/node': 4.1.15 + '@tailwindcss/oxide': 4.1.15 + tailwindcss: 4.1.15 + vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2) - '@tanstack/react-table@8.21.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@tanstack/react-table@8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@tanstack/table-core': 8.21.3 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) '@tanstack/table-core@8.21.3': {} @@ -3837,11 +4293,11 @@ snapshots: '@tsparticles/basic': 3.9.1 '@tsparticles/engine': 3.9.1 - '@tsparticles/react@3.0.0(@tsparticles/engine@3.9.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@tsparticles/react@3.0.0(@tsparticles/engine@3.9.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@tsparticles/engine': 3.9.1 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) '@tsparticles/shape-circle@3.9.1': dependencies: @@ -3886,9 +4342,11 @@ snapshots: dependencies: '@babel/types': 7.28.4 + '@types/canvas-confetti@1.9.0': {} + '@types/conventional-commits-parser@5.0.1': dependencies: - '@types/node': 24.5.2 + '@types/node': 24.9.1 '@types/d3-array@3.2.2': {} @@ -3918,122 +4376,122 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/node@24.5.2': + '@types/node@24.9.1': dependencies: - undici-types: 7.12.0 + undici-types: 7.16.0 - '@types/react-dom@19.1.9(@types/react@19.1.13)': + '@types/react-dom@19.2.2(@types/react@19.2.2)': dependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - '@types/react@19.1.13': + '@types/react@19.2.2': dependencies: csstype: 3.1.3 '@types/use-sync-external-store@0.0.6': {} - '@typescript-eslint/eslint-plugin@8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.44.0 - '@typescript-eslint/type-utils': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.44.0 - eslint: 9.35.0(jiti@2.5.1) + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.2 + '@typescript-eslint/type-utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.2 + eslint: 9.38.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.44.0 - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.44.0 + '@typescript-eslint/scope-manager': 8.46.2 + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.2 debug: 4.4.3 - eslint: 9.35.0(jiti@2.5.1) - typescript: 5.9.2 + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.44.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.46.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.44.0(typescript@5.9.2) - '@typescript-eslint/types': 8.44.0 + '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3) + '@typescript-eslint/types': 8.46.2 debug: 4.4.3 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.44.0': + '@typescript-eslint/scope-manager@8.46.2': dependencies: - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/visitor-keys': 8.44.0 + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/visitor-keys': 8.46.2 - '@typescript-eslint/tsconfig-utils@8.44.0(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.46.2(typescript@5.9.3)': dependencies: - typescript: 5.9.2 + typescript: 5.9.3 - '@typescript-eslint/type-utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.35.0(jiti@2.5.1) - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + eslint: 9.38.0(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.44.0': {} + '@typescript-eslint/types@8.46.2': {} - '@typescript-eslint/typescript-estree@8.44.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.46.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.44.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.44.0(typescript@5.9.2) - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/visitor-keys': 8.44.0 + '@typescript-eslint/project-service': 8.46.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3) + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/visitor-keys': 8.46.2 debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.44.0 - '@typescript-eslint/types': 8.44.0 - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) - eslint: 9.35.0(jiti@2.5.1) - typescript: 5.9.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.46.2 + '@typescript-eslint/types': 8.46.2 + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.44.0': + '@typescript-eslint/visitor-keys@8.46.2': dependencies: - '@typescript-eslint/types': 8.44.0 + '@typescript-eslint/types': 8.46.2 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-react@5.0.3(vite@7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1))': + '@vitejs/plugin-react@5.0.4(vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) - '@rolldown/pluginutils': 1.0.0-beta.35 + '@rolldown/pluginutils': 1.0.0-beta.38 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1) + vite: 7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color @@ -4078,9 +4536,13 @@ snapshots: array-ify@1.0.0: {} + babel-plugin-react-compiler@1.0.0: + dependencies: + '@babel/types': 7.28.4 + balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.5: {} + baseline-browser-mapping@2.8.19: {} bezier-js@6.1.4: {} @@ -4097,17 +4559,17 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.26.2: + browserslist@4.27.0: dependencies: - baseline-browser-mapping: 2.8.5 - caniuse-lite: 1.0.30001743 - electron-to-chromium: 1.5.221 - node-releases: 2.0.21 - update-browserslist-db: 1.1.3(browserslist@4.26.2) + baseline-browser-mapping: 2.8.19 + caniuse-lite: 1.0.30001751 + electron-to-chromium: 1.5.238 + node-releases: 2.0.26 + update-browserslist-db: 1.1.4(browserslist@4.27.0) callsites@3.1.0: {} - caniuse-lite@1.0.30001743: {} + caniuse-lite@1.0.30001751: {} canvas-color-tracker@1.3.2: dependencies: @@ -4122,8 +4584,6 @@ snapshots: chalk@5.6.2: {} - chownr@3.0.0: {} - class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -4136,14 +4596,14 @@ snapshots: clsx@2.1.1: {} - cmdk@1.1.1(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + cmdk@1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.13)(react@19.1.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -4182,21 +4642,21 @@ snapshots: cookie@1.0.2: {} - cosmiconfig-typescript-loader@6.1.0(@types/node@24.5.2)(cosmiconfig@9.0.0(typescript@5.9.2))(typescript@5.9.2): + cosmiconfig-typescript-loader@6.2.0(@types/node@24.9.1)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: - '@types/node': 24.5.2 - cosmiconfig: 9.0.0(typescript@5.9.2) - jiti: 2.5.1 - typescript: 5.9.2 + '@types/node': 24.9.1 + cosmiconfig: 9.0.0(typescript@5.9.3) + jiti: 2.6.1 + typescript: 5.9.3 - cosmiconfig@9.0.0(typescript@5.9.2): + cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 cross-spawn@7.0.6: dependencies: @@ -4299,7 +4759,7 @@ snapshots: deep-is@0.1.4: {} - detect-libc@2.1.0: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -4307,14 +4767,14 @@ snapshots: dependencies: is-obj: 2.0.0 - electron-to-chromium@1.5.221: {} + electron-to-chromium@1.5.238: {} emoji-regex@8.0.0: {} enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.3 + tapable: 2.3.0 env-paths@2.2.1: {} @@ -4322,48 +4782,55 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-toolkit@1.39.10: {} + es-toolkit@1.40.0: {} - esbuild@0.25.10: + esbuild@0.25.11: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.2.0(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-react-hooks@7.0.0(eslint@9.38.0(jiti@2.6.1)): dependencies: - eslint: 9.35.0(jiti@2.5.1) + '@babel/core': 7.28.4 + '@babel/parser': 7.28.4 + eslint: 9.38.0(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.1.12 + zod-validation-error: 4.0.2(zod@4.1.12) + transitivePeerDependencies: + - supports-color - eslint-plugin-react-refresh@0.4.20(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-react-refresh@0.4.24(eslint@9.38.0(jiti@2.6.1)): dependencies: - eslint: 9.35.0(jiti@2.5.1) + eslint: 9.38.0(jiti@2.6.1) eslint-scope@8.4.0: dependencies: @@ -4374,21 +4841,20 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.35.0(jiti@2.5.1): + eslint@9.38.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.3.1 - '@eslint/core': 0.15.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.38.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.1 + '@eslint/core': 0.16.0 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.35.0 - '@eslint/plugin-kit': 0.3.5 + '@eslint/js': 9.38.0 + '@eslint/plugin-kit': 0.4.0 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -4412,7 +4878,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.5.1 + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -4510,14 +4976,14 @@ snapshots: kapsule: 1.16.3 lodash-es: 4.17.21 - framer-motion@12.23.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - motion-dom: 12.23.12 + motion-dom: 12.23.23 motion-utils: 12.23.6 tslib: 2.8.1 optionalDependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) fsevents@2.3.3: optional: true @@ -4556,6 +5022,12 @@ snapshots: has-flag@4.0.0: {} + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4599,7 +5071,7 @@ snapshots: jerrypick@1.1.2: {} - jiti@2.5.1: {} + jiti@2.6.1: {} js-tokens@4.0.0: {} @@ -4636,50 +5108,54 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-darwin-arm64@1.30.1: + lightningcss-android-arm64@1.30.2: optional: true - lightningcss-darwin-x64@1.30.1: + lightningcss-darwin-arm64@1.30.2: optional: true - lightningcss-freebsd-x64@1.30.1: + lightningcss-darwin-x64@1.30.2: optional: true - lightningcss-linux-arm-gnueabihf@1.30.1: + lightningcss-freebsd-x64@1.30.2: optional: true - lightningcss-linux-arm64-gnu@1.30.1: + lightningcss-linux-arm-gnueabihf@1.30.2: optional: true - lightningcss-linux-arm64-musl@1.30.1: + lightningcss-linux-arm64-gnu@1.30.2: optional: true - lightningcss-linux-x64-gnu@1.30.1: + lightningcss-linux-arm64-musl@1.30.2: optional: true - lightningcss-linux-x64-musl@1.30.1: + lightningcss-linux-x64-gnu@1.30.2: optional: true - lightningcss-win32-arm64-msvc@1.30.1: + lightningcss-linux-x64-musl@1.30.2: optional: true - lightningcss-win32-x64-msvc@1.30.1: + lightningcss-win32-arm64-msvc@1.30.2: optional: true - lightningcss@1.30.1: + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: dependencies: - detect-libc: 2.1.0 + detect-libc: 2.1.2 optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 lines-and-columns@1.2.4: {} @@ -4719,6 +5195,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.546.0(react@19.2.0): + dependencies: + react: 19.2.0 + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4742,27 +5222,19 @@ snapshots: minimist@1.2.8: {} - minipass@7.1.2: {} - - minizlib@3.0.2: - dependencies: - minipass: 7.1.2 - - mkdirp@3.0.1: {} - - motion-dom@12.23.12: + motion-dom@12.23.23: dependencies: motion-utils: 12.23.6 motion-utils@12.23.6: {} - motion@12.23.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - framer-motion: 12.23.15(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + framer-motion: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0) tslib: 2.8.1 optionalDependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) ms@2.1.3: {} @@ -4770,12 +5242,12 @@ snapshots: natural-compare@1.4.0: {} - next-themes@0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - node-releases@2.0.21: {} + node-releases@2.0.26: {} object-assign@4.1.1: {} @@ -4847,98 +5319,161 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@19.1.1(react@19.1.1): + radix-ui@1.4.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.1.1 - scheduler: 0.26.0 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - react-force-graph-2d@1.29.0(react@19.1.1): + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react-force-graph-2d@1.29.0(react@19.2.0): dependencies: force-graph: 1.51.0 prop-types: 15.8.1 - react: 19.1.1 - react-kapsule: 2.5.7(react@19.1.1) + react: 19.2.0 + react-kapsule: 2.5.7(react@19.2.0) react-is@16.13.1: {} - react-kapsule@2.5.7(react@19.1.1): + react-kapsule@2.5.7(react@19.2.0): dependencies: jerrypick: 1.1.2 - react: 19.1.1 + react: 19.2.0 - react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.1.1 - use-sync-external-store: 1.5.0(react@19.1.1) + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 redux: 5.0.1 react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.8(@types/react@19.1.13)(react@19.1.1): + react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 - react-style-singleton: 2.2.3(@types/react@19.1.13)(react@19.1.1) + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - react-remove-scroll@2.7.1(@types/react@19.1.13)(react@19.1.1): + react-remove-scroll@2.7.1(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 - react-remove-scroll-bar: 2.3.8(@types/react@19.1.13)(react@19.1.1) - react-style-singleton: 2.2.3(@types/react@19.1.13)(react@19.1.1) + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.1.13)(react@19.1.1) - use-sidecar: 1.1.3(@types/react@19.1.13)(react@19.1.1) + use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - react-router-dom@7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-router-dom@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-router: 7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-router: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react-router@7.9.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-router@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: cookie: 1.0.2 - react: 19.1.1 + react: 19.2.0 set-cookie-parser: 2.7.1 optionalDependencies: - react-dom: 19.1.1(react@19.1.1) + react-dom: 19.2.0(react@19.2.0) - react-style-singleton@2.2.3(@types/react@19.1.13)(react@19.1.1): + react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.0): dependencies: get-nonce: 1.0.1 - react: 19.1.1 + react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - react-timeago@8.3.0(react@19.1.1): + react-timeago@8.3.0(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 - react@19.1.1: {} + react@19.2.0: {} - recharts@3.2.1(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react-is@16.13.1)(react@19.1.1)(redux@5.0.1): + recharts@3.3.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react-is@16.13.1)(react@19.2.0)(redux@5.0.1): dependencies: - '@reduxjs/toolkit': 2.9.0(react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1))(react@19.1.1) + '@reduxjs/toolkit': 2.9.1(react-redux@9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1))(react@19.2.0) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.39.10 + es-toolkit: 1.40.0 eventemitter3: 5.0.1 immer: 10.1.3 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) react-is: 16.13.1 - react-redux: 9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1) + react-redux: 9.2.0(@types/react@19.2.2)(react@19.2.0)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 - use-sync-external-store: 1.5.0(react@19.1.1) + use-sync-external-store: 1.6.0(react@19.2.0) victory-vendor: 37.3.6 transitivePeerDependencies: - '@types/react' @@ -4962,42 +5497,43 @@ snapshots: reusify@1.1.0: {} - rollup@4.50.2: + rollup@4.52.5: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.50.2 - '@rollup/rollup-android-arm64': 4.50.2 - '@rollup/rollup-darwin-arm64': 4.50.2 - '@rollup/rollup-darwin-x64': 4.50.2 - '@rollup/rollup-freebsd-arm64': 4.50.2 - '@rollup/rollup-freebsd-x64': 4.50.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.50.2 - '@rollup/rollup-linux-arm-musleabihf': 4.50.2 - '@rollup/rollup-linux-arm64-gnu': 4.50.2 - '@rollup/rollup-linux-arm64-musl': 4.50.2 - '@rollup/rollup-linux-loong64-gnu': 4.50.2 - '@rollup/rollup-linux-ppc64-gnu': 4.50.2 - '@rollup/rollup-linux-riscv64-gnu': 4.50.2 - '@rollup/rollup-linux-riscv64-musl': 4.50.2 - '@rollup/rollup-linux-s390x-gnu': 4.50.2 - '@rollup/rollup-linux-x64-gnu': 4.50.2 - '@rollup/rollup-linux-x64-musl': 4.50.2 - '@rollup/rollup-openharmony-arm64': 4.50.2 - '@rollup/rollup-win32-arm64-msvc': 4.50.2 - '@rollup/rollup-win32-ia32-msvc': 4.50.2 - '@rollup/rollup-win32-x64-msvc': 4.50.2 + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 fsevents: 2.3.3 run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - scheduler@0.26.0: {} + scheduler@0.27.0: {} semver@6.3.1: {} - semver@7.7.2: {} + semver@7.7.3: {} set-cookie-parser@2.7.1: {} @@ -5007,10 +5543,10 @@ snapshots: shebang-regex@3.0.0: {} - sonner@2.0.7(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + sonner@2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) source-map-js@1.2.1: {} @@ -5034,18 +5570,9 @@ snapshots: tailwind-merge@3.3.1: {} - tailwindcss@4.1.13: {} + tailwindcss@4.1.15: {} - tapable@2.2.3: {} - - tar@7.4.3: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.0.2 - mkdirp: 3.0.1 - yallist: 5.0.0 + tapable@2.3.0: {} text-extensions@2.4.0: {} @@ -5066,38 +5593,38 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@2.1.0(typescript@5.9.2): + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 tslib@2.8.1: {} - tw-animate-css@1.3.8: {} + tw-animate-css@1.4.0: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2): + typescript-eslint@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/typescript-estree': 8.44.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.44.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - eslint: 9.35.0(jiti@2.5.1) - typescript: 5.9.2 + '@typescript-eslint/eslint-plugin': 8.46.2(@typescript-eslint/parser@8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.2(eslint@9.38.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.38.0(jiti@2.6.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - typescript@5.9.2: {} + typescript@5.9.3: {} - undici-types@7.12.0: {} + undici-types@7.16.0: {} unicorn-magic@0.1.0: {} - update-browserslist-db@1.1.3(browserslist@4.26.2): + update-browserslist-db@1.1.4(browserslist@4.27.0): dependencies: - browserslist: 4.26.2 + browserslist: 4.27.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -5105,24 +5632,24 @@ snapshots: dependencies: punycode: 2.3.1 - use-callback-ref@1.3.3(@types/react@19.1.13)(react@19.1.1): + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - use-sidecar@1.1.3(@types/react@19.1.13)(react@19.1.1): + use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.0): dependencies: detect-node-es: 1.1.0 - react: 19.1.1 + react: 19.2.0 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.2 - use-sync-external-store@1.5.0(react@19.1.1): + use-sync-external-store@1.6.0(react@19.2.0): dependencies: - react: 19.1.1 + react: 19.2.0 victory-vendor@37.3.6: dependencies: @@ -5141,19 +5668,19 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite@7.1.6(@types/node@24.5.2)(jiti@2.5.1)(lightningcss@1.30.1): + vite@7.1.11(@types/node@24.9.1)(jiti@2.6.1)(lightningcss@1.30.2): dependencies: - esbuild: 0.25.10 + esbuild: 0.25.11 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.50.2 + rollup: 4.52.5 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.5.2 + '@types/node': 24.9.1 fsevents: 2.3.3 - jiti: 2.5.1 - lightningcss: 1.30.1 + jiti: 2.6.1 + lightningcss: 1.30.2 which@2.0.2: dependencies: @@ -5171,8 +5698,6 @@ snapshots: yallist@3.1.1: {} - yallist@5.0.0: {} - yargs-parser@21.1.1: {} yargs@17.7.2: @@ -5188,3 +5713,9 @@ snapshots: yocto-queue@0.1.0: {} yocto-queue@1.2.1: {} + + zod-validation-error@4.0.2(zod@4.1.12): + dependencies: + zod: 4.1.12 + + zod@4.1.12: {} diff --git a/client/public/favicon.ico b/client/public/favicon.ico index 34c3a7ae38edafaa1a01ec4e403eec4680781ccb..a7d8d0188aa60ef1637562feee2d7b82842c9889 100644 GIT binary patch literal 7662 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 G>wf?n>oy4h literal 15406 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 S_I>`FjsNrhKRfVqJMf>7@a%K| diff --git a/client/public/gray-icon.png b/client/public/gray-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b337d8b433124f924e3e16d8475e35c66bac072c GIT binary patch literal 3448168 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ⅇMmJmRow1B(UYuxW8`%GO6m##@?{e8*b{nhKn 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%0AkP`lg)RHL;5;If;P8RN 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⩔MQGy{;8+5YFY8lJfTf49p&hXK5X5I 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�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 w{9t8YyLlC~Zii7zO>4-xeXxezopr0P@ilumAu6 literal 27347 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 - -

- -
-
-

- {clientEntry.name || "Unnamed Device"} -

-
- - {clientEntry.ip} + + +
+
+

+ {clientEntry.name || "unknown"} +

+
+ + ip: {clientEntry.ip} + + {clientEntry.mac && ( + + mac: {clientEntry.mac} - {clientEntry.mac && ( - - - {clientEntry.mac} - - )} - {clientEntry.vendor && ( - - {clientEntry.vendor} - - )} -
-
-
- Last Activity -
- {formatTimeAgo(clientEntry.lastSeen)} -
+ )} + {clientEntry.vendor && ( + + vendor: {clientEntry.vendor} + + )}
-
-
+
+ Last Activity +
+ {formatTimeAgo(clientEntry.lastSeen)} +
+
+
+ -
- - -
+ + + setActiveTab("overview")} + > + + Overview + + setActiveTab("domains")} + > + + Domains + + setActiveTab("history")} + > + + History + + + {isLoading ? (
) : clientDetails ? ( -
+
{activeTab === "overview" && ( <> -
+
} label="Total Requests" @@ -181,17 +182,14 @@ export function CardDetails({ } label="Most Queried" - value={clientDetails.mostQueriedDomain.split(".")[0]} + value={clientDetails.mostQueriedDomain} color="bg-indigo-600" />
-

- - Top Queried Domains -

-
+

Top Queried Domains

+
{Object.entries(clientDetails.allDomains) .sort((a, b) => b[1] - a[1]) .slice(0, 5) @@ -208,10 +206,10 @@ export function CardDetails({
{count}
-
+
{domain}
-
+
-
- -
- - - + + +
)} {activeTab === "domains" && (
-

- +

All Queried Domains {Object.keys(clientDetails.allDomains).length} domains -

+

Count
-
+
Domain
@@ -294,7 +280,7 @@ export function CardDetails({
{count}
-
+
{domain}
@@ -309,7 +295,7 @@ export function CardDetails({ )}
) : ( -
+
No data available for this client
@@ -326,23 +312,12 @@ function StatCard({ icon, label, value, color }) { return (
-
-
- {icon} - {label} +
+
+ {icon} {label}
-
{value}
+
{value}
); } - -function ActionButton({ label, bgClass }) { - return ( - - ); -} diff --git a/client/src/app/clients/map.tsx b/client/src/app/clients/map.tsx index 39d1137..012e020 100644 --- a/client/src/app/clients/map.tsx +++ b/client/src/app/clients/map.tsx @@ -7,6 +7,7 @@ import { XIcon } from "@phosphor-icons/react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import ForceGraph2D, { ForceGraphMethods } from "react-force-graph-2d"; import { CardDetails } from "./details"; +import { toast } from "sonner"; interface ClientEntry { ip: string; @@ -129,8 +130,8 @@ export default function DNSServerVisualizer() { const [viewSettings, setViewSettings] = useState({ clusterBySubnet: false, hideInactiveClients: false, - minNodeSize: 3, - maxNodeSize: 12, + minNodeSize: 2, + maxNodeSize: 10, showLabels: true, activityThresholdMinutes: 60 }); @@ -235,7 +236,7 @@ export default function DNSServerVisualizer() { setError( err instanceof Error ? err.message : "Failed to fetch clients" ); - console.error("Error fetching clients:", err); + toast.warning("Error fetching clients", { description: `${err}` }); } }; @@ -289,7 +290,9 @@ export default function DNSServerVisualizer() { }); } } catch (error) { - console.error("Error handling WebSocket message:", error); + toast.warning("Error handling WebSocket message", { + description: `${error}` + }); } }; @@ -319,14 +322,14 @@ export default function DNSServerVisualizer() { id: "dns-server", name: "DNS Server", type: "server", - color: "#3b82f6", + color: "cornflowerblue", size: viewSettings.maxNodeSize }, { id: "upstream", name: "Upstream", type: "server", - color: "#008000", + color: "teal", size: viewSettings.maxNodeSize } ]; @@ -557,7 +560,7 @@ export default function DNSServerVisualizer() {
{[ @@ -583,7 +586,7 @@ export default function DNSServerVisualizer() { ).length } ].map(({ label, plural, value }) => ( -
+

{value}

{value === 1 ? label : plural} diff --git a/client/src/app/home/Audit.tsx b/client/src/app/home/Audit.tsx index d637952..fcc758f 100644 --- a/client/src/app/home/Audit.tsx +++ b/client/src/app/home/Audit.tsx @@ -1,8 +1,9 @@ "use client"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { NoContent } from "@/shared"; import { GetRequest } from "@/util"; -import { ArticleIcon, WarningIcon } from "@phosphor-icons/react"; +import { ArticleIcon } from "@phosphor-icons/react"; import { useEffect, useState } from "react"; import { toast } from "sonner"; @@ -87,16 +88,9 @@ export default function Audit() { ))}

) : ( - + )} ); } - -const EmptyState = () => ( -
- -

No audit entries found

-
-); diff --git a/client/src/app/home/FrequencyChartBlockedDomains.tsx b/client/src/app/home/FrequencyChartBlockedDomains.tsx index ec1ebf5..4825696 100644 --- a/client/src/app/home/FrequencyChartBlockedDomains.tsx +++ b/client/src/app/home/FrequencyChartBlockedDomains.tsx @@ -2,7 +2,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { GetRequest } from "@/util"; -import { NetworkSlashIcon, WarningIcon } from "@phosphor-icons/react"; +import { NetworkSlashIcon } from "@phosphor-icons/react"; import { useEffect, useState, useRef } from "react"; import { Bar, @@ -20,6 +20,7 @@ import { NameType, ValueType } from "recharts/types/component/DefaultTooltipContent"; +import { NoContent } from "@/shared"; type TopBlockedDomains = { frequency: number; @@ -58,17 +59,6 @@ const CustomTooltip = ({ return null; }; -const EmptyState = () => ( -
-
- -
-

- Blocked domains will appear here when detected -

-
-); - const isNewData = (a: TopBlockedDomains[], b: TopBlockedDomains[]): boolean => { if (a.length !== b.length) return false; @@ -211,7 +201,7 @@ export default function FrequencyChartBlockedDomains() { ) : ( - + )} diff --git a/client/src/app/home/FrequencyChartTopBlockedClients.tsx b/client/src/app/home/FrequencyChartTopBlockedClients.tsx index ccbb67c..531f3a8 100644 --- a/client/src/app/home/FrequencyChartTopBlockedClients.tsx +++ b/client/src/app/home/FrequencyChartTopBlockedClients.tsx @@ -2,8 +2,9 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { NoContent } from "@/shared"; import { GetRequest } from "@/util"; -import { UsersIcon, WarningIcon } from "@phosphor-icons/react"; +import { UsersIcon } from "@phosphor-icons/react"; import { useEffect, useState, useRef } from "react"; import { Bar, @@ -58,17 +59,6 @@ const CustomTooltip = ({ return null; }; -const EmptyState = () => ( -
-
- -
-

- Client data will appear here when requests are detected -

-
-); - const isNewData = (a: TopBlockedClients[], b: TopBlockedClients[]): boolean => { if (a.length !== b.length) return false; @@ -214,7 +204,7 @@ export default function FrequencyChartTopBlockedClients() { ) : ( - + )} diff --git a/client/src/app/home/ResponseSizeTimeline.tsx b/client/src/app/home/ResponseSizeTimeline.tsx index 58472a6..c26dd5d 100644 --- a/client/src/app/home/ResponseSizeTimeline.tsx +++ b/client/src/app/home/ResponseSizeTimeline.tsx @@ -6,6 +6,7 @@ import { ChartLegendContent, ChartTooltip } from "@/components/ui/chart"; +import { NoContent } from "@/shared"; import { GetRequest } from "@/util"; import { ArrowsClockwiseIcon, @@ -93,9 +94,15 @@ export default function ResponseSizeTimeline() { }, [timelineInterval]); useEffect(() => { - fetchData(); + const timer = setTimeout(() => { + fetchData(); + }, 0); + const interval = setInterval(fetchData, 10000); - return () => clearInterval(interval); + return () => { + clearTimeout(timer); + clearInterval(interval); + }; }, [fetchData]); const getFilteredData = () => { @@ -207,7 +214,7 @@ export default function ResponseSizeTimeline() {
{isZoomed && (

blocked:

-

{listEntry.blockedCount.toLocaleString()}

+

{listEntry.blockedCount}

updated:

- +
diff --git a/client/src/app/lists/updateCustom.tsx b/client/src/app/lists/updateCustom.tsx index c34b00a..eaab8b9 100644 --- a/client/src/app/lists/updateCustom.tsx +++ b/client/src/app/lists/updateCustom.tsx @@ -92,10 +92,7 @@ export function UpdateCustom() {
- +

Add domain names (one per line) to your custom blocklist. These domains will be automatically blocked across your diff --git a/client/src/app/logs/columns.tsx b/client/src/app/logs/columns.tsx index d183abd..9d1db1a 100644 --- a/client/src/app/logs/columns.tsx +++ b/client/src/app/logs/columns.tsx @@ -40,7 +40,7 @@ export function SortableHeader({ column, title }: Props) { + } + /> +); diff --git a/client/src/app/settings/DatabaseSection.tsx b/client/src/app/settings/DatabaseSection.tsx new file mode 100644 index 0000000..e5a8180 --- /dev/null +++ b/client/src/app/settings/DatabaseSection.tsx @@ -0,0 +1,154 @@ +import { toast } from "sonner"; +import { SpinnerIcon, UploadIcon, DownloadIcon } from "@phosphor-icons/react"; +import { Button } from "@/components/ui/button"; +import { formatBytes } from "./helpers"; +import { getApiBaseUrl } from "@/util"; +import { SettingRow } from "./SettingsRow"; +import { SetModalsType } from "./types"; + +export const DatabaseSection = ({ + loading, + setLoading, + fileInput, + setFile, + setModals +}: { + loading: { main: boolean; import: boolean; export: boolean }; + setLoading: React.Dispatch< + React.SetStateAction<{ main: boolean; import: boolean; export: boolean }> + >; + fileInput: React.RefObject; + setFile: (file: File | null) => void; + setModals: React.Dispatch>; +}) => { + const handleFileUpload = (e: React.ChangeEvent) => { + const uploadedFile = e.target.files?.[0]; + if (uploadedFile?.name.endsWith(".db")) { + setFile(uploadedFile); + setModals((prev: SetModalsType) => ({ ...prev, importConfirm: true })); + } else { + toast.error("Please select a .db file"); + } + }; + + const exportDb = async () => { + setLoading((prev) => ({ ...prev, export: true })); + const toastId = toast.loading("Starting export...", { + description: "Preparing database for export", + duration: Infinity + }); + + try { + const response = await fetch(`${getApiBaseUrl()}/api/exportDatabase`); + if (!response.ok) + throw new Error(`Export failed: ${response.statusText}`); + if (!response.body) throw new Error("ReadableStream not supported"); + + const total = parseInt(response.headers.get("Content-Length") || "0", 10); + const reader = response.body.getReader(); + const chunks = []; + let received = 0; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + chunks.push(value); + received += value.length; + + const progressText = + total > 0 + ? `${Math.round((received / total) * 100)}% (${formatBytes( + received + )} / ${formatBytes(total)})` + : `Downloaded ${formatBytes(received)}`; + + toast.loading("Downloading database...", { + id: toastId, + description: progressText, + duration: Infinity + }); + } + + const blob = new Blob(chunks, { type: "application/octet-stream" }); + const url = URL.createObjectURL(blob); + const a = Object.assign(document.createElement("a"), { + href: url, + download: "database.db" + }); + document.body.appendChild(a).click(); + a.remove(); + URL.revokeObjectURL(url); + + toast.success("Database exported successfully!", { + id: toastId, + description: `Downloaded ${formatBytes(received)}`, + duration: 4000 + }); + } catch (error) { + toast.error("Export failed", { + id: toastId, + description: error.message || "An error occurred during export", + duration: 5000 + }); + } finally { + setLoading((prev) => ({ ...prev, export: false })); + } + }; + + return ( + <> + + {loading.export ? ( + <> + Exporting... + + ) : ( + <> + Export + + )} + + } + /> + + + + + } + /> + + ); +}; diff --git a/client/src/app/settings/DynamicSettingsSection.tsx b/client/src/app/settings/DynamicSettingsSection.tsx new file mode 100644 index 0000000..117b839 --- /dev/null +++ b/client/src/app/settings/DynamicSettingsSection.tsx @@ -0,0 +1,62 @@ +import { Combobox } from "@/components/combobox"; +import { Switch } from "@/components/ui/switch"; +import { SettingRow } from "./SettingsRow"; +import { JSX } from "react"; + +interface SettingsSectionProps { + label: string; + key: string; + explanation: string; + default: number | string | boolean; + widgetType: JSX.Element; +} + +export const DynamicSettingsSection = ({ + settings, + getSettingValue, + handleSettingChange +}: { + settings: SettingsSectionProps[]; + getSettingValue: (key: string) => number | string | boolean; + handleSettingChange: (key: string, value: number | string | boolean) => void; +}) => ( + <> + {settings.map( + ({ label, key, explanation, options, widgetType: Widget }) => { + const value = getSettingValue(key); + + return ( + handleSettingChange(key, v), + options, + className: "w-40" + } + : Widget === Switch + ? { + checked: Boolean(value), + onCheckedChange: (v: boolean) => + handleSettingChange(key, v) + } + : { + value: String(value), + onChange: (e: React.ChangeEvent) => + handleSettingChange(key, e.target.value), + placeholder: label, + className: "w-40" + })} + /> + } + /> + ); + } + )} + +); diff --git a/client/src/app/settings/ImportModal.tsx b/client/src/app/settings/ImportModal.tsx new file mode 100644 index 0000000..c8848c4 --- /dev/null +++ b/client/src/app/settings/ImportModal.tsx @@ -0,0 +1,96 @@ +import { useState, useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { formatBytes, formatDate } from "./helpers"; + +export const ImportModal = ({ + open, + onClose, + onConfirm, + filename +}: { + open: boolean; + onClose: () => void; + onConfirm: () => void; + filename?: string; +}) => { + const [fileDetails, setFileDetails] = useState<{ + name: string; + size: number; + lastModified: number; + } | null>(null); + + useEffect(() => { + if (filename) { + const input = document.querySelector( + "input[type='file']" + ) as HTMLInputElement; + const file = input?.files?.[0]; + if (file) { + setTimeout(() => { + setFileDetails({ + name: file.name, + size: file.size, + lastModified: file.lastModified + }); + }, 0); + } + } + }, [filename]); + + return ( +

+ + + Confirm Import + +

+ Replace the current database with {filename}? +

+

+ A backup of your current database will be created. +

+ {fileDetails && ( +
+

+ File Details: +

+
    +
  • + Name: {fileDetails.name} +
  • +
  • + Size: {formatBytes(fileDetails.size)} +
  • +
  • + Last Modified:{" "} + {formatDate(fileDetails.lastModified)} +
  • +
+
+ )} +
+
+ + + + +
+
+ ); +}; diff --git a/client/src/app/settings/LoggingSection.tsx b/client/src/app/settings/LoggingSection.tsx new file mode 100644 index 0000000..61edce7 --- /dev/null +++ b/client/src/app/settings/LoggingSection.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; +import { SettingRow } from "./SettingsRow"; +import { Switch } from "@/components/ui/switch"; + +export function LoggingSection({ + loggingEnabled, + logLevel, + statisticsRetention, + onLoggingEnabledChange, + onLogLevelChange, + onStatisticsRetentionChange +}: { + loggingEnabled?: boolean; + logLevel?: number; + statisticsRetention?: number; + onLoggingEnabledChange?: (enabled: boolean) => void; + onLogLevelChange?: (level: string) => void; + onStatisticsRetentionChange?: (days: string) => void; +}) { + const logLevelMap = ["debug", "info", "warning", "error"]; + const currentLogLevel = + typeof logLevel === "number" ? logLevelMap[logLevel] : logLevel; + + console.log(logLevel); + + return ( + <> + { + if (value && onLogLevelChange) { + onLogLevelChange(value); + } + }} + > + Debug + Info + Warning + Error + + } + /> + + { + if (value && onStatisticsRetentionChange) { + onStatisticsRetentionChange(value); + } + }} + > + 1 day + 7 days + 30 days + 90 days + + } + /> + + + } + /> + + ); +} diff --git a/client/src/app/settings/PasswordModal.tsx b/client/src/app/settings/PasswordModal.tsx new file mode 100644 index 0000000..8a5b638 --- /dev/null +++ b/client/src/app/settings/PasswordModal.tsx @@ -0,0 +1,77 @@ +import { WarningIcon } from "@phosphor-icons/react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; + +export const PasswordModal = ({ + open, + onClose, + onSubmit, + passwords, + setPasswords, + error, + setError +}: { + open: boolean; + onClose: () => void; + onSubmit: () => void; + passwords: { current: string; new: string; confirm: string }; + setPasswords: (p: { current: string; new: string; confirm: string }) => void; + error: string; + setError: (e: string) => void; +}) => ( + + + + Change Password + + Update your password. You'll be logged out after changing it. + + + + {error && ( +
+ + {error} +
+ )} + +
+ {["current", "new", "confirm"].map((type) => ( +
+ + { + setPasswords({ ...passwords, [type]: e.target.value }); + setError(""); + }} + placeholder={`Enter ${type} password`} + /> +
+ ))} +
+ + + + + +
+
+); diff --git a/client/src/app/settings/SecuritySection.tsx b/client/src/app/settings/SecuritySection.tsx new file mode 100644 index 0000000..ed4287a --- /dev/null +++ b/client/src/app/settings/SecuritySection.tsx @@ -0,0 +1,31 @@ +import { Button } from "@/components/ui/button"; +import { SettingRow } from "./SettingsRow"; + +export const SecuritySection = ({ + onPasswordClick, + onApiKeyClick +}: { + onPasswordClick: () => void; + onApiKeyClick: () => void; +}) => ( + <> + + Change Password + + } + /> + + Manage Keys + + } + /> + +); diff --git a/client/src/app/settings/SettingsRow.tsx b/client/src/app/settings/SettingsRow.tsx new file mode 100644 index 0000000..7bc7389 --- /dev/null +++ b/client/src/app/settings/SettingsRow.tsx @@ -0,0 +1,17 @@ +export const SettingRow = ({ + title, + description, + action +}: { + title: string; + description: string; + action: React.ReactNode; +}) => ( +
+
+

{title}

+

{description}

+
+
{action}
+
+); diff --git a/client/src/app/settings/SettingsSection.tsx b/client/src/app/settings/SettingsSection.tsx new file mode 100644 index 0000000..7c74d51 --- /dev/null +++ b/client/src/app/settings/SettingsSection.tsx @@ -0,0 +1,161 @@ +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { + CertificateIcon, + CircuitryIcon, + DatabaseIcon, + KeyIcon, + LockIcon, + NotificationIcon, + ShuffleIcon, + TextAlignCenterIcon +} from "@phosphor-icons/react"; + +export const SETTINGS_SECTIONS = [ + { + title: "Security", + description: + "Manage user authentication and access control for the dashboard and API.", + icon: , + settings: [] + }, + { + title: "API", + description: + "Configure how the API behaves, including its port and authentication requirements.", + icon: , + settings: [ + { + label: "Port *", + key: "apiPort", + explanation: "Port the API server listens on.", + default: 8080, + widgetType: Input + }, + { + label: "Authentication *", + key: "authentication", + explanation: "Require login credentials to access the dashboard.", + options: [true, false], + default: true, + widgetType: Switch + } + ] + }, + { + title: "Logging", + description: + "Set logging preferences, including verbosity level and data retention.", + icon: , + settings: [] + }, + { + title: "Alerts", + description: + "Configure how the system notifies you about important events.", + icon: , + settings: [] + }, + { + title: "DNS Server", + description: + "Manage core DNS server settings, including ports, caching, and buffer size.", + icon: , + settings: [ + { + label: "Address *", + key: "dnsAddress", + explanation: "The network address to bind the DNS server to.", + default: "0.0.0.0", + widgetType: Input + }, + { + label: "Port *", + key: "dnsPort", + explanation: "Port the DNS server listens on.", + default: 53, + widgetType: Input + }, + { + label: "DoT Port *", + key: "dotPort", + explanation: "Port for DNS-over-TLS traffic.", + default: 853, + widgetType: Input + }, + { + label: "DoH Port *", + key: "dohPort", + explanation: "Port for DNS-over-HTTPS traffic.", + default: 443, + widgetType: Input + }, + { + label: "Cache TTL *", + key: "cacheTTL", + explanation: "How long (in seconds) to cache DNS results.", + default: 60, + widgetType: Input + }, + { + label: "UDP Size *", + key: "udpSize", + explanation: "Maximum UDP packet size in bytes.", + default: 512, + widgetType: Input + } + ] + }, + { + title: "Certificate", + description: + "Specify TLS certificates used for DoH (dns-over-https) and DoT (dns-over-tls).", + icon: , + settings: [ + { + label: "TLS Certificate *", + key: "tlsCertFile", + explanation: "Path to the TLS certificate file.", + default: "", + widgetType: Input + }, + { + label: "TLS Key *", + key: "tlsKeyFile", + explanation: "Path to the TLS private key file.", + default: "", + widgetType: Input + } + ] + }, + { + title: "Database", + description: + "Import, export, and manage the internal database used by the application.", + icon: , + settings: [] + }, + { + title: "Miscellaneous", + description: + "Other configurable options that don't fit into a specific category.", + icon: , + settings: [ + { + label: "Scheduled Blacklist Updates *", + key: "scheduledBlacklistUpdates", + explanation: "Automatically update blacklists on a regular schedule.", + default: true, + widgetType: Switch + }, + { + label: "In App Updates *", + key: "inAppUpdate", + explanation: + "Enable in-app update checks and automatic version management.", + default: false, + widgetType: Switch + } + ] + } +]; diff --git a/client/src/app/settings/helpers.ts b/client/src/app/settings/helpers.ts new file mode 100644 index 0000000..f44387b --- /dev/null +++ b/client/src/app/settings/helpers.ts @@ -0,0 +1,21 @@ +export const parseLogLevel = (level: number | string): number | string => { + const levels = ["Debug", "Info", "Warning", "Error"]; + return typeof level === "number" ? levels[level] : levels.indexOf(level); +}; + +export const formatBytes = (bytes: number): string => { + if (!bytes) return "0 B"; + const units = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + return `${(bytes / 1024 ** i).toFixed(1)} ${units[i]}`; +}; + +export const formatDate = (timestamp: number): string => + new Date(timestamp).toLocaleString("en-US", { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false + }); diff --git a/client/src/app/settings/types.ts b/client/src/app/settings/types.ts new file mode 100644 index 0000000..1bd25f7 --- /dev/null +++ b/client/src/app/settings/types.ts @@ -0,0 +1,71 @@ +export interface Root { + dns: Dns; + api: Api; + logging: Logging; + misc: Misc; +} + +export interface Dns { + status: Status; + address: string; + gateway: string; + cacheTTL: number; + udpSize: number; + tls: Tls; + upstream: Upstream; + ports: Ports; +} + +export interface Status { + pausedAt: string; + pauseTime: string; + paused: boolean; +} + +export interface Tls { + enabled: boolean; + cert: string; + key: string; +} + +export interface Upstream { + preferred: string; + fallback: string[]; +} + +export interface Ports { + udptcp: number; + dot: number; + doh: number; +} + +export interface Api { + port: number; + authentication: boolean; + rateLimit: RateLimit; +} + +export interface RateLimit { + enabled: boolean; + maxTries: number; + window: number; +} + +export interface Logging { + enabled: boolean; + level: number; +} + +export interface Misc { + inAppUpdate: boolean; + statisticsRetention: number; + dashboard: boolean; + scheduledBlacklistUpdates: boolean; +} + +export interface SetModalsType { + password: false; + apiKey: false; + importConfirm: false; + notifications: false; +} diff --git a/client/src/components/app-sidebar.tsx b/client/src/components/app-sidebar.tsx index 11427a8..c64efea 100644 --- a/client/src/components/app-sidebar.tsx +++ b/client/src/components/app-sidebar.tsx @@ -10,6 +10,7 @@ import { } from "@/components/ui/sidebar"; import { GenerateQuote } from "@/quotes"; import { + BrowserIcon, CloudArrowUpIcon, GearIcon, GithubLogoIcon, @@ -23,7 +24,6 @@ import { UsersIcon } from "@phosphor-icons/react"; import * as React from "react"; -import { Separator } from "./ui/separator"; import { TextAnimate } from "./ui/text-animate"; import { ServerStatistics } from "./server-statistics"; @@ -86,6 +86,12 @@ const data = { } ], navSecondary: [ + { + title: "Website", + url: "https://pommee.github.io/goaway", + icon: BrowserIcon, + blank: "_blank" + }, { title: "GitHub", url: "https://github.com/pommee/goaway", @@ -103,16 +109,14 @@ const data = { export function AppSidebar({ ...props }: React.ComponentProps) { return ( -
+
-
- {""} -
+ {"project-mascot"}
GoAway ) { - diff --git a/client/src/components/nav-actions.tsx b/client/src/components/nav-actions.tsx index ea32d38..353a04c 100644 --- a/client/src/components/nav-actions.tsx +++ b/client/src/components/nav-actions.tsx @@ -153,7 +153,7 @@ export default function PauseBlockingDialog({ timeLeft: number; }; - const [time, setTime] = useState("10"); + const [pauseTime, setPauseTime] = useState(10); const [isLoading, setIsLoading] = useState(false); const [pauseStatus, setPauseStatus] = useState(); const [remainingTime, setRemainingTime] = useState(0); @@ -193,11 +193,11 @@ export default function PauseBlockingDialog({ setIsLoading(true); try { const [status] = await PostRequest("pause", { - time: parseInt(time) + time: pauseTime }); if (status === 200) { - toast.info(`Paused blocking for ${time} seconds`); + toast.info(`Paused blocking for ${pauseTime} seconds`); const [getStatus, getResponse] = await GetRequest("pause"); if (getStatus === 200) { setPauseStatus(getResponse); @@ -265,8 +265,8 @@ export default function PauseBlockingDialog({ id="pause-time" type="number" min="1" - value={time} - onChange={(e) => setTime(e.target.value)} + value={pauseTime} + onChange={(e) => setPauseTime(e.target.value)} className="w-full" />
diff --git a/client/src/components/notifications.tsx b/client/src/components/notifications.tsx index f3107e6..05afd90 100644 --- a/client/src/components/notifications.tsx +++ b/client/src/components/notifications.tsx @@ -17,7 +17,7 @@ type NotificationsResponse = { category: string; text: string; read: boolean; - createdAt: string; + created_at: string; }; export default function Notifications() { @@ -31,9 +31,10 @@ export default function Notifications() { (notification) => !notification.read ).length; - const shouldAnimate = unreadCount > prevUnreadCountRef.current; + const [shouldAnimate, setShouldAnimate] = useState(false); useEffect(() => { + setShouldAnimate(unreadCount > prevUnreadCountRef.current); prevUnreadCountRef.current = unreadCount; }, [unreadCount]); @@ -42,7 +43,9 @@ export default function Notifications() { try { const [code, response] = await GetRequest("notifications"); if (code !== 200) { - toast.warning("Unable to fetch notifications"); + toast.warning("Unable to fetch notifications", { + id: "fetch-notifications-error" + }); return; } @@ -100,7 +103,7 @@ export default function Notifications() { return `${diffDays} days ago`; }; - const handleMarkAllAsRead = async (e) => { + const handleMarkAllAsRead = async (e: { stopPropagation: () => void }) => { e.stopPropagation(); const updatedNotifications = notifications.map((notification) => ({ ...notification, @@ -144,7 +147,7 @@ export default function Notifications() { Notifications @@ -186,7 +189,9 @@ export default function Notifications() {

- {getTimeAgo(notification.createdAt)} + {getTimeAgo( + new Date(notification.created_at).toString() + )}

{notification.category} diff --git a/client/src/components/server-statistics.tsx b/client/src/components/server-statistics.tsx index f67060e..f604cd1 100644 --- a/client/src/components/server-statistics.tsx +++ b/client/src/components/server-statistics.tsx @@ -130,6 +130,59 @@ function calculateProgress(logs: string[]): { return { progress, currentStep }; } +const formatNumber = (num: number | undefined) => { + if (num === undefined || num === null || isNaN(num)) { + return "0.0"; + } + return num.toFixed(1); +}; + +const MetricBar = ({ + label, + value, + max, + unit, + icon: Icon, + showIcon = true +}: { + label: string; + value: number | undefined; + max: number; + unit: string; + icon: React.ElementType; + showIcon?: boolean; +}) => { + const safeValue = value ?? 0; + const percentage = Math.min((safeValue / max) * 100, 100); + const color = getColor(safeValue, max); + + return ( +
+ {showIcon && ( + + )} +
+
+ {label} + + {formatNumber(value)} + {unit} + +
+
+
+
+
+
+ ); +}; + export function ServerStatistics() { const [metrics, setMetrics] = useState(null); const [updateNotified, setUpdateNotified] = useState(false); @@ -197,6 +250,7 @@ export function ServerStatistics() { useEffect(() => { const { progress, currentStep } = calculateProgress(updateLogs); + // eslint-disable-next-line react-hooks/set-state-in-effect setUpdateProgress(progress); setCurrentStep(currentStep); }, [updateLogs]); @@ -230,59 +284,6 @@ export function ServerStatistics() { }; } - const formatNumber = (num: number | undefined) => { - if (num === undefined || num === null || isNaN(num)) { - return "0.0"; - } - return num.toFixed(1); - }; - - const MetricBar = ({ - label, - value, - max, - unit, - icon: Icon, - showIcon = true - }: { - label: string; - value: number | undefined; - max: number; - unit: string; - icon: React.ElementType; - showIcon?: boolean; - }) => { - const safeValue = value ?? 0; - const percentage = Math.min((safeValue / max) * 100, 100); - const color = getColor(safeValue, max); - - return ( -
- {showIcon && ( - - )} -
-
- {label} - - {formatNumber(value)} - {unit} - -
-
-
-
-
-
- ); - }; - const dockerCommands = `# Stop and remove the current container docker stop container-name docker rm container-name @@ -337,7 +338,7 @@ docker pull pommee/goaway:version`;
@@ -447,118 +448,115 @@ docker pull pommee/goaway:version`; ) : ( -
-
-
-

- - In-app Updates Disabled -

-
-

- If you are running inside a Docker container, use the - following commands to update: -

-
-
-
-
# Stop and remove the current container
-
- docker stop container-name -
-
- docker rm container-name -
-
-
- # Pull and run the new version -
-
- docker pull pommee/goaway:version -
+
+
+

+ + In-app updates are disabled +

+
+

+ If you are running inside a Docker container, use the + following commands to update: +

+
+
+
+
# Stop and remove the current container
+
+ docker stop container-name +
+
+ docker rm container-name +
+
+
+ # Pull and run the new version +
+
+ docker pull pommee/goaway:version
-
-

- Replace{" "} - - container-name - {" "} - and{" "} - - version - {" "} - with your actual values. -

+
+

+ Replace{" "} + + container-name + {" "} + and{" "} + + version + {" "} + with your actual values. +

+
- + -
-

- If you are running the standalone binary, use the - following commands to update: -

-
-
-
- # Stop the binary from running and run one of the - following: -
-
-
- # Will install the latest version of goaway into - ~/$USER/.local/bin -
-
- curl - https://raw.githubusercontent.com/pommee/goaway/main/installer.sh - | sh -
-
- # It's also possible to specify what version to - install -
-
- curl - https://raw.githubusercontent.com/pommee/goaway/main/installer.sh - | sh /dev/stdin 0.40.4 -
-
-
- # Will replace your goaway binary in the current - working directory with the latest version -
-
- https://raw.githubusercontent.com/pommee/goaway/refs/heads/main/updater.sh - | bash -
+
+

+ If you are running the standalone binary, use the following + commands to update: +

+
+
+
+ # Stop the binary from running and run one of the + following: +
+
+
+ # Will install the latest version of goaway into + ~/$USER/.local/bin +
+
+ curl + https://raw.githubusercontent.com/pommee/goaway/main/installer.sh + | sh +
+
+ # It's also possible to specify what version to install +
+
+ curl + https://raw.githubusercontent.com/pommee/goaway/main/installer.sh + | sh /dev/stdin 0.40.4 +
+
+
+ # Will replace your goaway binary in the current working + directory with the latest version +
+
+ https://raw.githubusercontent.com/pommee/goaway/refs/heads/main/updater.sh + | bash
+

+ This information might be outdated, in that case refer to the{" "} + + repository README + +

-

- This information might be outdated, in that case refer to the{" "} - - repository README - -

)} diff --git a/client/src/components/site-header.tsx b/client/src/components/site-header.tsx index 94570ac..679ea09 100644 --- a/client/src/components/site-header.tsx +++ b/client/src/components/site-header.tsx @@ -71,7 +71,7 @@ export function SiteHeader() { const description = currentPage?.description || ""; return ( -
+
diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx index 7eb839e..f090991 100644 --- a/client/src/components/ui/button.tsx +++ b/client/src/components/ui/button.tsx @@ -1,30 +1,32 @@ import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; +import { Slot as SlotPrimitive } from "radix-ui"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", { variants: { variant: { - default: - "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: - "bg-destructive/50 text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: - "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", secondary: - "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline" }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9" + icon: "size-9", + "icon-sm": "size-8", + "icon-lg": "size-10" } }, defaultVariants: { @@ -44,7 +46,7 @@ function Button({ VariantProps & { asChild?: boolean; }) { - const Comp = asChild ? Slot : "button"; + const Comp = asChild ? SlotPrimitive.Slot : "button"; const cursorPointer = props.disabled === undefined || props.disabled === false ? "cursor-pointer" diff --git a/client/src/components/ui/checkbox.tsx b/client/src/components/ui/checkbox.tsx index 8718e75..e85e69b 100644 --- a/client/src/components/ui/checkbox.tsx +++ b/client/src/components/ui/checkbox.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { Checkbox as CheckboxPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; import { CheckIcon } from "@phosphor-icons/react"; diff --git a/client/src/components/ui/collapsible.tsx b/client/src/components/ui/collapsible.tsx index 849e7b6..260f4e2 100644 --- a/client/src/components/ui/collapsible.tsx +++ b/client/src/components/ui/collapsible.tsx @@ -1,4 +1,4 @@ -import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"; +import { Collapsible as CollapsiblePrimitive } from "radix-ui"; function Collapsible({ ...props diff --git a/client/src/components/ui/dialog.tsx b/client/src/components/ui/dialog.tsx index a1c6d6f..84060f0 100644 --- a/client/src/components/ui/dialog.tsx +++ b/client/src/components/ui/dialog.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; -import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { Dialog as DialogPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; import { XCircleIcon } from "@phosphor-icons/react"; diff --git a/client/src/components/ui/dropdown-menu.tsx b/client/src/components/ui/dropdown-menu.tsx index 70e41c9..287a50e 100644 --- a/client/src/components/ui/dropdown-menu.tsx +++ b/client/src/components/ui/dropdown-menu.tsx @@ -1,7 +1,7 @@ "use client"; import * as React from "react"; -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; import { CaretRightIcon, CheckIcon, CircleIcon } from "@phosphor-icons/react"; diff --git a/client/src/components/ui/input.tsx b/client/src/components/ui/input.tsx index b6491e1..868dec6 100644 --- a/client/src/components/ui/input.tsx +++ b/client/src/components/ui/input.tsx @@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) { type={type} data-slot="input" className={cn( - "border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", + "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className diff --git a/client/src/components/ui/label.tsx b/client/src/components/ui/label.tsx index 2db054e..a94bfae 100644 --- a/client/src/components/ui/label.tsx +++ b/client/src/components/ui/label.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as LabelPrimitive from "@radix-ui/react-label"; +import { Label as LabelPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; diff --git a/client/src/components/ui/popover.tsx b/client/src/components/ui/popover.tsx index 94ee7ea..5c62e80 100644 --- a/client/src/components/ui/popover.tsx +++ b/client/src/components/ui/popover.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as PopoverPrimitive from "@radix-ui/react-popover"; +import { Popover as PopoverPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; diff --git a/client/src/components/ui/scroll-area.tsx b/client/src/components/ui/scroll-area.tsx index 92b825e..38d184d 100644 --- a/client/src/components/ui/scroll-area.tsx +++ b/client/src/components/ui/scroll-area.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; +import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; diff --git a/client/src/components/ui/select.tsx b/client/src/components/ui/select.tsx index 50f0a78..47840c2 100644 --- a/client/src/components/ui/select.tsx +++ b/client/src/components/ui/select.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import * as SelectPrimitive from "@radix-ui/react-select"; +import { Select as SelectPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; import { CaretDownIcon, CaretUpIcon, CheckIcon } from "@phosphor-icons/react"; diff --git a/client/src/components/ui/separator.tsx b/client/src/components/ui/separator.tsx index 7f81877..719dea9 100644 --- a/client/src/components/ui/separator.tsx +++ b/client/src/components/ui/separator.tsx @@ -1,7 +1,5 @@ -"use client"; - import * as React from "react"; -import * as SeparatorPrimitive from "@radix-ui/react-separator"; +import { Separator as SeparatorPrimitive } from "radix-ui"; import { cn } from "@/lib/utils"; @@ -13,7 +11,7 @@ function Separator({ }: React.ComponentProps) { return ( ) { return ; @@ -33,7 +36,7 @@ function SheetOverlay({ {children} - + Close diff --git a/client/src/components/ui/sidebar.tsx b/client/src/components/ui/sidebar.tsx index 3a51713..1f9d0b3 100644 --- a/client/src/components/ui/sidebar.tsx +++ b/client/src/components/ui/sidebar.tsx @@ -1,6 +1,11 @@ +/* eslint-disable react-hooks/purity */ +"use client"; + import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; -import { VariantProps, cva } from "class-variance-authority"; +import { Slot as SlotPrimitive } from "radix-ui"; +import { cva, type VariantProps } from "class-variance-authority"; +import { PanelLeftIcon } from "lucide-react"; + import { useIsMobile } from "@/hooks/use-mobile"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; @@ -20,7 +25,6 @@ import { TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; -import { SidebarSimpleIcon } from "@phosphor-icons/react"; const SIDEBAR_COOKIE_NAME = "sidebar_state"; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; @@ -29,7 +33,7 @@ const SIDEBAR_WIDTH_MOBILE = "18rem"; const SIDEBAR_WIDTH_ICON = "3rem"; const SIDEBAR_KEYBOARD_SHORTCUT = "b"; -type SidebarContext = { +type SidebarContextProps = { state: "expanded" | "collapsed"; open: boolean; setOpen: (open: boolean) => void; @@ -39,7 +43,7 @@ type SidebarContext = { toggleSidebar: () => void; }; -const SidebarContext = React.createContext(null); +const SidebarContext = React.createContext(null); function useSidebar() { const context = React.useContext(SidebarContext); @@ -110,7 +114,7 @@ function SidebarProvider({ // This makes it easier to style the sidebar with Tailwind classes. const state = open ? "expanded" : "collapsed"; - const contextValue = React.useMemo( + const contextValue = React.useMemo( () => ({ state, open, @@ -213,6 +217,7 @@ function Sidebar({ > {/* This is what handles the sidebar gap on desktop */}

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