From d05799fe65dc845b4b195338d5149c581891e6ab Mon Sep 17 00:00:00 2001 From: el Date: Tue, 1 Apr 2025 16:33:54 +0200 Subject: [PATCH] init --- README.md | 38 + agent/cmd/main.go | 53 + agent/go.mod | 11 + agent/internal/agent/agent.go | 157 + agent/internal/agent/containers.go | 121 + agent/internal/agent/logs.go | 120 + agent/internal/config/config.go | 40 + agent/internal/config/errors.go | 8 + frontend/.eslintrc.cjs | 14 + frontend/.gitignore | 24 + frontend/env.d.ts | 16 + frontend/index.html | 13 + frontend/install.sh | 13 + frontend/package-lock.json | 4013 +++++++++++++++++ frontend/package.json | 28 + frontend/postcss.config.js | 6 + frontend/src/App.vue | 86 + frontend/src/assets/main.css | 27 + frontend/src/components/AgentList.vue | 264 ++ frontend/src/components/ContainerDetails.vue | 205 + frontend/src/components/ContainerList.vue | 144 + frontend/src/components/LogsView.vue | 139 + frontend/src/env.d.ts | 7 + frontend/src/main.ts | 42 + frontend/src/router/index.ts | 27 + frontend/tailwind.config.js | 11 + frontend/tsconfig.json | 25 + frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 26 + server/app/__pycache__/auth.cpython-313.pyc | Bin 0 -> 2016 bytes server/app/__pycache__/main.cpython-313.pyc | Bin 0 -> 4056 bytes server/app/__pycache__/models.cpython-313.pyc | Bin 0 -> 3095 bytes server/app/api/api.py | 7 + server/app/api/endpoints/logs.py | 31 + server/app/auth.py | 37 + server/app/main.py | 77 + server/app/models.py | 56 + server/app/models/__init__.py | 7 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 398 bytes .../models/__pycache__/agent.cpython-313.pyc | Bin 0 -> 2895 bytes .../__pycache__/container.cpython-313.pyc | Bin 0 -> 3103 bytes server/app/models/agent.py | 54 + server/app/models/container.py | 54 + server/app/routes/__init__.py | 4 + .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 260 bytes .../routes/__pycache__/agents.cpython-313.pyc | Bin 0 -> 4542 bytes .../__pycache__/containers.cpython-313.pyc | Bin 0 -> 15823 bytes server/app/routes/agents.py | 75 + server/app/routes/containers.py | 270 ++ .../__pycache__/agent.cpython-313.pyc | Bin 0 -> 4352 bytes .../__pycache__/agent_service.cpython-313.pyc | Bin 0 -> 4127 bytes .../__pycache__/docker.cpython-313.pyc | Bin 0 -> 20586 bytes .../__pycache__/redis.cpython-313.pyc | Bin 0 -> 2999 bytes server/app/services/agent.py | 74 + server/app/services/agent_service.py | 75 + server/app/services/docker.py | 387 ++ server/app/services/docker_service.py | 77 + server/app/services/logs_service.py | 58 + server/app/services/redis.py | 33 + server/requirements.txt | 14 + 60 files changed, 7078 insertions(+) create mode 100644 README.md create mode 100644 agent/cmd/main.go create mode 100644 agent/go.mod create mode 100644 agent/internal/agent/agent.go create mode 100644 agent/internal/agent/containers.go create mode 100644 agent/internal/agent/logs.go create mode 100644 agent/internal/config/config.go create mode 100644 agent/internal/config/errors.go create mode 100644 frontend/.eslintrc.cjs create mode 100644 frontend/.gitignore create mode 100644 frontend/env.d.ts create mode 100644 frontend/index.html create mode 100755 frontend/install.sh create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/assets/main.css create mode 100644 frontend/src/components/AgentList.vue create mode 100644 frontend/src/components/ContainerDetails.vue create mode 100644 frontend/src/components/ContainerList.vue create mode 100644 frontend/src/components/LogsView.vue create mode 100644 frontend/src/env.d.ts create mode 100644 frontend/src/main.ts create mode 100644 frontend/src/router/index.ts create mode 100644 frontend/tailwind.config.js create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts create mode 100644 server/app/__pycache__/auth.cpython-313.pyc create mode 100644 server/app/__pycache__/main.cpython-313.pyc create mode 100644 server/app/__pycache__/models.cpython-313.pyc create mode 100644 server/app/api/api.py create mode 100644 server/app/api/endpoints/logs.py create mode 100644 server/app/auth.py create mode 100644 server/app/main.py create mode 100644 server/app/models.py create mode 100644 server/app/models/__init__.py create mode 100644 server/app/models/__pycache__/__init__.cpython-313.pyc create mode 100644 server/app/models/__pycache__/agent.cpython-313.pyc create mode 100644 server/app/models/__pycache__/container.cpython-313.pyc create mode 100644 server/app/models/agent.py create mode 100644 server/app/models/container.py create mode 100644 server/app/routes/__init__.py create mode 100644 server/app/routes/__pycache__/__init__.cpython-313.pyc create mode 100644 server/app/routes/__pycache__/agents.cpython-313.pyc create mode 100644 server/app/routes/__pycache__/containers.cpython-313.pyc create mode 100644 server/app/routes/agents.py create mode 100644 server/app/routes/containers.py create mode 100644 server/app/services/__pycache__/agent.cpython-313.pyc create mode 100644 server/app/services/__pycache__/agent_service.cpython-313.pyc create mode 100644 server/app/services/__pycache__/docker.cpython-313.pyc create mode 100644 server/app/services/__pycache__/redis.cpython-313.pyc create mode 100644 server/app/services/agent.py create mode 100644 server/app/services/agent_service.py create mode 100644 server/app/services/docker.py create mode 100644 server/app/services/docker_service.py create mode 100644 server/app/services/logs_service.py create mode 100644 server/app/services/redis.py create mode 100644 server/requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..bbd13b7 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +# Étoile Polaire + +Une application de gestion de conteneurs Docker avec support multi-machines. + +## Description + +Étoile Polaire est une application web permettant de : +- Visualiser les logs des conteneurs Docker en temps réel +- Gérer les conteneurs (démarrer, arrêter, redémarrer) +- Mettre à jour les conteneurs en un clic +- Gérer des conteneurs sur des machines distantes via des agents + +## Architecture + +Le projet est composé de trois parties principales : + +- `server/` : Backend FastAPI (Python) +- `frontend/` : Interface utilisateur Vue.js +- `agent/` : Agent Go pour la gestion des conteneurs + +## Prérequis + +- Python 3.8+ +- Node.js 16+ +- Go 1.16+ +- Docker + +## Installation + +Instructions d'installation à venir... + +## Développement + +Instructions de développement à venir... + +## Licence + +MIT \ No newline at end of file diff --git a/agent/cmd/main.go b/agent/cmd/main.go new file mode 100644 index 0000000..d9815dc --- /dev/null +++ b/agent/cmd/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "flag" + "log" + "os" + "os/signal" + "syscall" + + "github.com/etoilepolaire/agent/internal/agent" + "github.com/etoilepolaire/agent/internal/config" +) + +func main() { + // Configuration des flags + serverURL := flag.String("server", "http://localhost:8000", "URL du serveur central") + agentName := flag.String("name", "", "Nom de l'agent") + flag.Parse() + + if *agentName == "" { + log.Fatal("Le nom de l'agent est requis") + } + + // Chargement de la configuration + cfg := config.Config{ + ServerURL: *serverURL, + Name: *agentName, + } + + // Création de l'agent + a, err := agent.New(cfg) + if err != nil { + log.Fatalf("Erreur lors de la création de l'agent: %v", err) + } + + // Gestion des signaux pour un arrêt propre + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // Démarrage de l'agent + go func() { + if err := a.Start(); err != nil { + log.Printf("Erreur lors du démarrage de l'agent: %v", err) + } + }() + + // Attente du signal d'arrêt + <-sigChan + log.Println("Arrêt de l'agent...") + if err := a.Stop(); err != nil { + log.Printf("Erreur lors de l'arrêt de l'agent: %v", err) + } +} \ No newline at end of file diff --git a/agent/go.mod b/agent/go.mod new file mode 100644 index 0000000..1f90387 --- /dev/null +++ b/agent/go.mod @@ -0,0 +1,11 @@ +module github.com/etoilepolaire/agent + +go 1.21 + +require ( + github.com/docker/docker v24.0.7+incompatible + github.com/gorilla/websocket v1.5.1 + github.com/spf13/cobra v1.7.0 + github.com/spf13/viper v1.16.0 + golang.org/x/crypto v0.14.0 +) \ No newline at end of file diff --git a/agent/internal/agent/agent.go b/agent/internal/agent/agent.go new file mode 100644 index 0000000..f882977 --- /dev/null +++ b/agent/internal/agent/agent.go @@ -0,0 +1,157 @@ +package agent + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/docker/docker/client" + "github.com/etoilepolaire/agent/internal/config" +) + +// Agent représente l'agent Docker +type Agent struct { + config config.Config + client *client.Client + httpClient *http.Client + agentID string + ctx context.Context + cancel context.CancelFunc +} + +// New crée une nouvelle instance de l'agent +func New(cfg config.Config) (*Agent, error) { + if err := cfg.Validate(); err != nil { + return nil, err + } + + // Création du client Docker + dockerClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return nil, fmt.Errorf("erreur lors de la création du client Docker: %v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + return &Agent{ + config: cfg, + client: dockerClient, + httpClient: &http.Client{Timeout: 10 * time.Second}, + ctx: ctx, + cancel: cancel, + }, nil +} + +// Start démarre l'agent +func (a *Agent) Start() error { + // Enregistrement de l'agent + if err := a.register(); err != nil { + return fmt.Errorf("erreur lors de l'enregistrement: %v", err) + } + + // Démarrage des routines + go a.heartbeat() + go a.watchContainers() + go a.watchLogs() + + return nil +} + +// Stop arrête l'agent +func (a *Agent) Stop() error { + a.cancel() + return nil +} + +// register enregistre l'agent auprès du serveur +func (a *Agent) register() error { + registration := struct { + Name string `json:"name"` + Hostname string `json:"hostname"` + IPAddress string `json:"ip_address"` + DockerVersion string `json:"docker_version"` + }{ + Name: a.config.Name, + Hostname: a.config.Hostname, + IPAddress: a.config.IPAddress, + DockerVersion: a.config.DockerVersion, + } + + data, err := json.Marshal(registration) + if err != nil { + return err + } + + resp, err := a.httpClient.Post( + fmt.Sprintf("%s/api/agents/register", a.config.ServerURL), + "application/json", + bytes.NewBuffer(data), + ) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("erreur lors de l'enregistrement: %s", resp.Status) + } + + var result struct { + ID string `json:"id"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return err + } + + a.agentID = result.ID + return nil +} + +// heartbeat envoie régulièrement un heartbeat au serveur +func (a *Agent) heartbeat() { + ticker := time.NewTicker(a.config.HeartbeatInterval) + defer ticker.Stop() + + for { + select { + case <-a.ctx.Done(): + return + case <-ticker.C: + if err := a.sendHeartbeat(); err != nil { + fmt.Printf("Erreur lors de l'envoi du heartbeat: %v\n", err) + } + } + } +} + +// sendHeartbeat envoie un heartbeat au serveur +func (a *Agent) sendHeartbeat() error { + resp, err := a.httpClient.Post( + fmt.Sprintf("%s/api/agents/%s/heartbeat", a.config.ServerURL, a.agentID), + "application/json", + nil, + ) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("erreur lors de l'envoi du heartbeat: %s", resp.Status) + } + + return nil +} + +// watchContainers surveille les changements dans les conteneurs +func (a *Agent) watchContainers() { + // TODO: Implémenter la surveillance des conteneurs +} + +// watchLogs surveille les logs des conteneurs +func (a *Agent) watchLogs() { + // TODO: Implémenter la surveillance des logs +} \ No newline at end of file diff --git a/agent/internal/agent/containers.go b/agent/internal/agent/containers.go new file mode 100644 index 0000000..0dfdc29 --- /dev/null +++ b/agent/internal/agent/containers.go @@ -0,0 +1,121 @@ +package agent + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" +) + +// Container représente un conteneur Docker +type Container struct { + ID string `json:"id"` + Name string `json:"name"` + Image string `json:"image"` + Status string `json:"status"` + Created time.Time `json:"created"` + Ports map[string][]Port `json:"ports"` + Labels map[string]string `json:"labels"` +} + +// Port représente un port exposé +type Port struct { + HostIP string `json:"host_ip"` + HostPort string `json:"host_port"` +} + +// watchContainers surveille les changements dans les conteneurs +func (a *Agent) watchContainers() { + events, errs := a.client.Events(a.ctx, types.EventsOptions{}) + + for { + select { + case <-a.ctx.Done(): + return + case err := <-errs: + fmt.Printf("Erreur lors de la surveillance des conteneurs: %v\n", err) + case event := <-events: + if event.Type == events.ContainerEventType { + if err := a.handleContainerEvent(event); err != nil { + fmt.Printf("Erreur lors du traitement de l'événement: %v\n", err) + } + } + } + } +} + +// handleContainerEvent traite un événement de conteneur +func (a *Agent) handleContainerEvent(event events.Message) error { + // Mise à jour de la liste des conteneurs + containers, err := a.listContainers() + if err != nil { + return err + } + + // Envoi de la mise à jour au serveur + data, err := json.Marshal(containers) + if err != nil { + return err + } + + resp, err := a.httpClient.Post( + fmt.Sprintf("%s/api/agents/%s/containers/update", a.config.ServerURL, a.agentID), + "application/json", + bytes.NewBuffer(data), + ) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("erreur lors de la mise à jour des conteneurs: %s", resp.Status) + } + + return nil +} + +// listContainers liste tous les conteneurs +func (a *Agent) listContainers() ([]Container, error) { + containers, err := a.client.ContainerList(a.ctx, types.ContainerListOptions{All: true}) + if err != nil { + return nil, err + } + + var result []Container + for _, c := range containers { + container, err := a.client.ContainerInspect(a.ctx, c.ID) + if err != nil { + continue + } + + ports := make(map[string][]Port) + for containerPort, bindings := range container.NetworkSettings.Ports { + var portBindings []Port + for _, binding := range bindings { + portBindings = append(portBindings, Port{ + HostIP: binding.HostIP, + HostPort: binding.HostPort, + }) + } + ports[string(containerPort)] = portBindings + } + + result = append(result, Container{ + ID: container.ID, + Name: container.Name, + Image: container.Config.Image, + Status: container.State.Status, + Created: time.Unix(container.Created, 0), + Ports: ports, + Labels: container.Config.Labels, + }) + } + + return result, nil +} \ No newline at end of file diff --git a/agent/internal/agent/logs.go b/agent/internal/agent/logs.go new file mode 100644 index 0000000..6ad18ab --- /dev/null +++ b/agent/internal/agent/logs.go @@ -0,0 +1,120 @@ +package agent + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/docker/docker/api/types" +) + +// LogEntry représente une entrée de log +type LogEntry struct { + ContainerID string `json:"container_id"` + Timestamp time.Time `json:"timestamp"` + Message string `json:"message"` + Stream string `json:"stream"` +} + +// watchLogs surveille les logs des conteneurs +func (a *Agent) watchLogs() { + containers, err := a.client.ContainerList(a.ctx, types.ContainerListOptions{}) + if err != nil { + fmt.Printf("Erreur lors de la récupération des conteneurs: %v\n", err) + return + } + + for _, container := range containers { + go a.watchContainerLogs(container.ID) + } +} + +// watchContainerLogs surveille les logs d'un conteneur spécifique +func (a *Agent) watchContainerLogs(containerID string) { + options := types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Follow: true, + Timestamps: true, + } + + reader, err := a.client.ContainerLogs(a.ctx, containerID, options) + if err != nil { + fmt.Printf("Erreur lors de la récupération des logs: %v\n", err) + return + } + defer reader.Close() + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + select { + case <-a.ctx.Done(): + return + default: + line := scanner.Text() + if err := a.handleLogLine(containerID, line); err != nil { + fmt.Printf("Erreur lors du traitement du log: %v\n", err) + } + } + } +} + +// handleLogLine traite une ligne de log +func (a *Agent) handleLogLine(containerID, line string) error { + // Le format des logs Docker est : [timestamp] stream message + // Exemple : [2024-03-31T22:30:00.000000000Z] stdout Hello World + + var stream string + var message string + var timestamp time.Time + + // Parse le timestamp + if len(line) >= 32 { + timestampStr := line[1:31] + var err error + timestamp, err = time.Parse(time.RFC3339Nano, timestampStr) + if err != nil { + return err + } + + // Parse le stream et le message + if len(line) >= 35 { + stream = line[33:35] + message = line[36:] + } + } + + logEntry := LogEntry{ + ContainerID: containerID, + Timestamp: timestamp, + Message: message, + Stream: stream, + } + + // Envoi du log au serveur + data, err := json.Marshal(logEntry) + if err != nil { + return err + } + + resp, err := a.httpClient.Post( + fmt.Sprintf("%s/api/containers/%s/logs", a.config.ServerURL, containerID), + "application/json", + bytes.NewBuffer(data), + ) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("erreur lors de l'envoi du log: %s", resp.Status) + } + + return nil +} \ No newline at end of file diff --git a/agent/internal/config/config.go b/agent/internal/config/config.go new file mode 100644 index 0000000..25811d0 --- /dev/null +++ b/agent/internal/config/config.go @@ -0,0 +1,40 @@ +package config + +import ( + "os" + "time" +) + +// Config représente la configuration de l'agent +type Config struct { + ServerURL string + Name string + Hostname string + IPAddress string + DockerVersion string + HeartbeatInterval time.Duration +} + +// DefaultConfig retourne une configuration par défaut +func DefaultConfig() Config { + hostname, _ := os.Hostname() + return Config{ + ServerURL: "http://localhost:8000", + Name: "", + Hostname: hostname, + IPAddress: "127.0.0.1", // À remplacer par l'adresse IP réelle + DockerVersion: "unknown", + HeartbeatInterval: 30 * time.Second, + } +} + +// Validate vérifie que la configuration est valide +func (c *Config) Validate() error { + if c.Name == "" { + return ErrNameRequired + } + if c.ServerURL == "" { + return ErrServerURLRequired + } + return nil +} \ No newline at end of file diff --git a/agent/internal/config/errors.go b/agent/internal/config/errors.go new file mode 100644 index 0000000..e3a8578 --- /dev/null +++ b/agent/internal/config/errors.go @@ -0,0 +1,8 @@ +package config + +import "errors" + +var ( + ErrNameRequired = errors.New("le nom de l'agent est requis") + ErrServerURLRequired = errors.New("l'URL du serveur est requise") +) \ No newline at end of file diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 0000000..b81df3a --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,14 @@ +/* eslint-env node */ +require('@rushstack/eslint-patch/modern-module-resolution') + +module.exports = { + root: true, + extends: [ + 'plugin:vue/vue3-essential', + 'eslint:recommended', + '@vue/eslint-config-typescript', + ], + parserOptions: { + ecmaVersion: 'latest', + }, +} \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..d79574a --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? \ No newline at end of file diff --git a/frontend/env.d.ts b/frontend/env.d.ts new file mode 100644 index 0000000..f7506b0 --- /dev/null +++ b/frontend/env.d.ts @@ -0,0 +1,16 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} + +interface ImportMetaEnv { + readonly VITE_API_URL: string + readonly VITE_WS_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..045b179 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Étoile Polaire + + +
+ + + \ No newline at end of file diff --git a/frontend/install.sh b/frontend/install.sh new file mode 100755 index 0000000..3d06551 --- /dev/null +++ b/frontend/install.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Installation des dépendances +npm install + +# Création du fichier .env +cat > .env << EOL +VITE_API_URL=http://localhost:8000 +VITE_WS_URL=ws://localhost:8000 +EOL + +echo "Installation terminée !" +echo "Pour lancer le serveur de développement : npm run dev" \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ad8ab95 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4013 @@ +{ + "name": "etoilepolaire-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "etoilepolaire-frontend", + "version": "0.0.0", + "dependencies": { + "@vueuse/motion": "^3.0.3", + "axios": "^1.6.2", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vue-toastification": "^2.0.0-rc.5" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@vitejs/plugin-vue": "^4.5.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.5", + "typescript": "^5.2.2", + "vite": "^5.0.0", + "vue-tsc": "^1.8.22" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nuxt/kit": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.16.1.tgz", + "integrity": "sha512-Perby8hJGUeCWad5oTVXb/Ibvp18ZCUC5PxHHu+acMDmVfnxSo48yqk7qNd09VkTF3LEzoEjNZpmW2ZWN0ry7A==", + "license": "MIT", + "optional": true, + "dependencies": { + "c12": "^3.0.2", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.3", + "errx": "^0.1.0", + "exsolve": "^1.0.4", + "globby": "^14.1.0", + "ignore": "^7.0.3", + "jiti": "^2.4.2", + "klona": "^2.0.6", + "knitwork": "^1.2.0", + "mlly": "^1.7.4", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.1.0", + "scule": "^1.3.0", + "semver": "^7.7.1", + "std-env": "^3.8.1", + "ufo": "^1.5.4", + "unctx": "^2.4.1", + "unimport": "^4.1.2", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxt/kit/node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "optional": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.38.0.tgz", + "integrity": "sha512-ldomqc4/jDZu/xpYU+aRxo3V4mGCV9HeTgUBANI3oIQMOL+SsxB+S2lxMpkFp5UamSS3XuTMQVbsS24R4J4Qjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.38.0.tgz", + "integrity": "sha512-VUsgcy4GhhT7rokwzYQP+aV9XnSLkkhlEJ0St8pbasuWO/vwphhZQxYEKUP3ayeCYLhk6gEtacRpYP/cj3GjyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.38.0.tgz", + "integrity": "sha512-buA17AYXlW9Rn091sWMq1xGUvWQFOH4N1rqUxGJtEQzhChxWjldGCCup7r/wUnaI6Au8sKXpoh0xg58a7cgcpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.38.0.tgz", + "integrity": "sha512-Mgcmc78AjunP1SKXl624vVBOF2bzwNWFPMP4fpOu05vS0amnLcX8gHIge7q/lDAHy3T2HeR0TqrriZDQS2Woeg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.38.0.tgz", + "integrity": "sha512-zzJACgjLbQTsscxWqvrEQAEh28hqhebpRz5q/uUd1T7VTwUNZ4VIXQt5hE7ncs0GrF+s7d3S4on4TiXUY8KoQA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.38.0.tgz", + "integrity": "sha512-hCY/KAeYMCyDpEE4pTETam0XZS4/5GXzlLgpi5f0IaPExw9kuB+PDTOTLuPtM10TlRG0U9OSmXJ+Wq9J39LvAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.38.0.tgz", + "integrity": "sha512-mimPH43mHl4JdOTD7bUMFhBdrg6f9HzMTOEnzRmXbOZqjijCw8LA5z8uL6LCjxSa67H2xiLFvvO67PT05PRKGg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.38.0.tgz", + "integrity": "sha512-tPiJtiOoNuIH8XGG8sWoMMkAMm98PUwlriOFCCbZGc9WCax+GLeVRhmaxjJtz6WxrPKACgrwoZ5ia/uapq3ZVg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.38.0.tgz", + "integrity": "sha512-wZco59rIVuB0tjQS0CSHTTUcEde+pXQWugZVxWaQFdQQ1VYub/sTrNdY76D1MKdN2NB48JDuGABP6o6fqos8mA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.38.0.tgz", + "integrity": "sha512-fQgqwKmW0REM4LomQ+87PP8w8xvU9LZfeLBKybeli+0yHT7VKILINzFEuggvnV9M3x1Ed4gUBmGUzCo/ikmFbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.38.0.tgz", + "integrity": "sha512-hz5oqQLXTB3SbXpfkKHKXLdIp02/w3M+ajp8p4yWOWwQRtHWiEOCKtc9U+YXahrwdk+3qHdFMDWR5k+4dIlddg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.38.0.tgz", + "integrity": "sha512-NXqygK/dTSibQ+0pzxsL3r4Xl8oPqVoWbZV9niqOnIHV/J92fe65pOir0xjkUZDRSPyFRvu+4YOpJF9BZHQImw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.38.0.tgz", + "integrity": "sha512-GEAIabR1uFyvf/jW/5jfu8gjM06/4kZ1W+j1nWTSSB3w6moZEBm7iBtzwQ3a1Pxos2F7Gz+58aVEnZHU295QTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.38.0.tgz", + "integrity": "sha512-9EYTX+Gus2EGPbfs+fh7l95wVADtSQyYw4DfSBcYdUEAmP2lqSZY0Y17yX/3m5VKGGJ4UmIH5LHLkMJft3bYoA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.38.0.tgz", + "integrity": "sha512-Mpp6+Z5VhB9VDk7RwZXoG2qMdERm3Jw07RNlXHE0bOnEeX+l7Fy4bg+NxfyN15ruuY3/7Vrbpm75J9QHFqj5+Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.38.0.tgz", + "integrity": "sha512-vPvNgFlZRAgO7rwncMeE0+8c4Hmc+qixnp00/Uv3ht2x7KYrJ6ERVd3/R0nUtlE6/hu7/HiiNHJ/rP6knRFt1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.38.0.tgz", + "integrity": "sha512-q5Zv+goWvQUGCaL7fU8NuTw8aydIL/C9abAVGCzRReuj5h30TPx4LumBtAidrVOtXnlB+RZkBtExMsfqkMfb8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.38.0.tgz", + "integrity": "sha512-u/Jbm1BU89Vftqyqbmxdq14nBaQjQX1HhmsdBWqSdGClNaKwhjsg5TpW+5Ibs1mb8Es9wJiMdl86BcmtUVXNZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.38.0.tgz", + "integrity": "sha512-mqu4PzTrlpNHHbu5qleGvXJoGgHpChBlrBx/mEhTPpnAL1ZAYFlvHD7rLK839LLKQzqEQMFJfGrrOHItN4ZQqA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.38.0.tgz", + "integrity": "sha512-jjqy3uWlecfB98Psxb5cD6Fny9Fupv9LrDSPTQZUROqjvZmcCqNu4UMl7qqhlUUGpwiAkotj6GYu4SZdcr/nLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.28.tgz", + "integrity": "sha512-DHlH/fNL6Mho38jTy7/JT7sn2wnXI+wULR6PV4gy4VHLVvnrV/d3pHAMQHhc4gjdLmK2ZiPoMxzp6B3yRajLSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.11.1.tgz", + "integrity": "sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "1.11.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.11.1.tgz", + "integrity": "sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.11.1.tgz", + "integrity": "sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "1.11.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.27.tgz", + "integrity": "sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~1.11.1", + "@volar/source-map": "~1.11.1", + "@vue/compiler-dom": "^3.3.0", + "@vue/shared": "^3.3.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.3.1", + "path-browserify": "^1.0.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.0.0.tgz", + "integrity": "sha512-rkgb4a8/0b234lMGCT29WkCjPfsX0oxrIRR7FDndRoW3FsaC9NBzefXg/9TLhAgwM11f49XnutshM4LzJBrQ5g==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.0.0", + "@vueuse/shared": "13.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.0.0.tgz", + "integrity": "sha512-TRNksqmvtvqsuHf7bbgH9OSXEV2b6+M3BSN4LR5oxWKykOFT9gV78+C2/0++Pq9KCp9KQ1OQDPvGlWNQpOb2Mw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/motion": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@vueuse/motion/-/motion-3.0.3.tgz", + "integrity": "sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "^13.0.0", + "@vueuse/shared": "^13.0.0", + "defu": "^6.1.4", + "framesync": "^6.1.2", + "popmotion": "^11.0.5", + "style-value-types": "^5.1.2" + }, + "optionalDependencies": { + "@nuxt/kit": "^3.13.0" + }, + "peerDependencies": { + "vue": ">=3.0.0" + } + }, + "node_modules/@vueuse/shared": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.0.0.tgz", + "integrity": "sha512-9MiHhAPw+sqCF/RLo8V6HsjRqEdNEWVpDLm2WBRW2G/kSQjb8X901sozXpSCaeLG0f7TEfMrT4XNaA5m1ez7Dg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "license": "MIT", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/c12": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.0.2.tgz", + "integrity": "sha512-6Tzk1/TNeI3WBPpK0j/Ss4+gPj3PUJYbWl/MWDJBThFvwNGNkXtd7Cz8BJtD4aRwoGHtzQD0SnxamgUiBH0/Nw==", + "license": "MIT", + "optional": true, + "dependencies": { + "chokidar": "^4.0.3", + "confbox": "^0.1.8", + "defu": "^6.1.4", + "dotenv": "^16.4.7", + "exsolve": "^1.0.0", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.5", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.0.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "optional": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/c12/node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "optional": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/c12/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001707", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz", + "integrity": "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT", + "optional": true + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "license": "MIT", + "optional": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.128", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.128.tgz", + "integrity": "sha512-bo1A4HH/NS522Ws0QNFIzyPcyUUNV/yyy70Ho1xqfGYzPUme2F/xr4tlEOuM6/A538U1vDA7a4XfCd1CKRegKQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errx": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/errx/-/errx-0.1.0.tgz", + "integrity": "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==", + "license": "MIT", + "optional": true + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.4.tgz", + "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framesync": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/framesync/-/framesync-6.1.2.tgz", + "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==", + "license": "MIT", + "dependencies": { + "tslib": "2.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "license": "MIT", + "optional": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hey-listen": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", + "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "license": "MIT", + "optional": true + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/knitwork": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/knitwork/-/knitwork-1.2.0.tgz", + "integrity": "sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg==", + "license": "MIT", + "optional": true + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "license": "MIT", + "optional": true, + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz", + "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nypm": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.0.tgz", + "integrity": "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==", + "license": "MIT", + "optional": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.4.0", + "pathe": "^2.0.3", + "pkg-types": "^2.0.0", + "tinyexec": "^0.3.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT", + "optional": true + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT", + "optional": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT", + "optional": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", + "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "license": "MIT", + "optional": true, + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.1.tgz", + "integrity": "sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg==", + "license": "MIT", + "optional": true + }, + "node_modules/popmotion": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-11.0.5.tgz", + "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==", + "license": "MIT", + "dependencies": { + "framesync": "6.1.2", + "hey-listen": "^1.0.8", + "style-value-types": "5.1.2", + "tslib": "2.4.0" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/quansync": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "license": "MIT", + "optional": true, + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "devOptional": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.38.0.tgz", + "integrity": "sha512-5SsIRtJy9bf1ErAOiFMFzl64Ex9X5V7bnJ+WlFMb+zmP459OSWCEG7b0ERZ+PEU7xPt4OG3RHbrp1LJlXxYTrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.38.0", + "@rollup/rollup-android-arm64": "4.38.0", + "@rollup/rollup-darwin-arm64": "4.38.0", + "@rollup/rollup-darwin-x64": "4.38.0", + "@rollup/rollup-freebsd-arm64": "4.38.0", + "@rollup/rollup-freebsd-x64": "4.38.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.38.0", + "@rollup/rollup-linux-arm-musleabihf": "4.38.0", + "@rollup/rollup-linux-arm64-gnu": "4.38.0", + "@rollup/rollup-linux-arm64-musl": "4.38.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.38.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.38.0", + "@rollup/rollup-linux-riscv64-gnu": "4.38.0", + "@rollup/rollup-linux-riscv64-musl": "4.38.0", + "@rollup/rollup-linux-s390x-gnu": "4.38.0", + "@rollup/rollup-linux-x64-gnu": "4.38.0", + "@rollup/rollup-linux-x64-musl": "4.38.0", + "@rollup/rollup-win32-arm64-msvc": "4.38.0", + "@rollup/rollup-win32-ia32-msvc": "4.38.0", + "@rollup/rollup-win32-x64-msvc": "4.38.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/std-env": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.1.tgz", + "integrity": "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==", + "license": "MIT", + "optional": true + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "license": "MIT", + "optional": true, + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/style-value-types": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-5.1.2.tgz", + "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==", + "license": "MIT", + "dependencies": { + "hey-listen": "^1.0.8", + "tslib": "2.4.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "license": "MIT", + "optional": true + }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "license": "MIT", + "optional": true, + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "license": "MIT", + "optional": true, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", + "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "license": "MIT", + "optional": true + }, + "node_modules/unctx": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.4.1.tgz", + "integrity": "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==", + "license": "MIT", + "optional": true, + "dependencies": { + "acorn": "^8.14.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17", + "unplugin": "^2.1.0" + } + }, + "node_modules/unctx/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unimport": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-4.1.3.tgz", + "integrity": "sha512-H+IVJ7rAkE3b+oC8rSJ2FsPaVsweeMC8eKZc+C6Mz7+hxDF45AnrY/tVCNRBvzMwWNcJEV67WdAVcal27iMjOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "acorn": "^8.14.1", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.1.1", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "pkg-types": "^2.1.0", + "scule": "^1.3.0", + "strip-literal": "^3.0.0", + "tinyglobby": "^0.2.12", + "unplugin": "^2.2.2", + "unplugin-utils": "^0.2.4" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/unplugin": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.2.tgz", + "integrity": "sha512-Qp+iiD+qCRnUek+nDoYvtWX7tfnYyXsrOnJ452FRTgOyKmTM7TUJ3l+PLPJOOWPTUyKISKp4isC5JJPSXUjGgw==", + "license": "MIT", + "optional": true, + "dependencies": { + "acorn": "^8.14.1", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.4.tgz", + "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==", + "license": "MIT", + "optional": true, + "dependencies": { + "pathe": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/unplugin-utils/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/untyped": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/untyped/-/untyped-2.0.0.tgz", + "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "citty": "^0.1.6", + "defu": "^6.1.4", + "jiti": "^2.4.2", + "knitwork": "^1.2.0", + "scule": "^1.3.0" + }, + "bin": { + "untyped": "dist/cli.mjs" + } + }, + "node_modules/untyped/node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "optional": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.16.tgz", + "integrity": "sha512-Y5gnfp4NemVfgOTDQAunSD4346fal44L9mszGGY/e+qxsRT5y1sMlS/8tiQ8AFAp+MFgYNSINdfEchJiPm41vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz", + "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-toastification": { + "version": "2.0.0-rc.5", + "resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz", + "integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.0.2" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.27", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.27.tgz", + "integrity": "sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~1.11.1", + "@vue/language-core": "1.8.27", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT", + "optional": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..6f5fd79 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,28 @@ +{ + "name": "etoilepolaire-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@vueuse/motion": "^3.0.3", + "axios": "^1.6.2", + "vue": "^3.3.8", + "vue-router": "^4.2.5", + "vue-toastification": "^2.0.0-rc.5" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "@vitejs/plugin-vue": "^4.5.0", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.5", + "typescript": "^5.2.2", + "vite": "^5.0.0", + "vue-tsc": "^1.8.22" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..387612e --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..c4274b6 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,86 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css new file mode 100644 index 0000000..5dc1106 --- /dev/null +++ b/frontend/src/assets/main.css @@ -0,0 +1,27 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply bg-gray-50 text-gray-900; + } +} + +@layer components { + .btn { + @apply px-4 py-2 rounded-md font-medium transition-colors duration-200; + } + + .btn-primary { + @apply bg-blue-600 text-white hover:bg-blue-700; + } + + .btn-secondary { + @apply bg-gray-200 text-gray-800 hover:bg-gray-300; + } + + .card { + @apply bg-white rounded-lg shadow-sm p-6; + } +} \ No newline at end of file diff --git a/frontend/src/components/AgentList.vue b/frontend/src/components/AgentList.vue new file mode 100644 index 0000000..30b3a92 --- /dev/null +++ b/frontend/src/components/AgentList.vue @@ -0,0 +1,264 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/ContainerDetails.vue b/frontend/src/components/ContainerDetails.vue new file mode 100644 index 0000000..ac7c209 --- /dev/null +++ b/frontend/src/components/ContainerDetails.vue @@ -0,0 +1,205 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/ContainerList.vue b/frontend/src/components/ContainerList.vue new file mode 100644 index 0000000..1916ee0 --- /dev/null +++ b/frontend/src/components/ContainerList.vue @@ -0,0 +1,144 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/LogsView.vue b/frontend/src/components/LogsView.vue new file mode 100644 index 0000000..533e719 --- /dev/null +++ b/frontend/src/components/LogsView.vue @@ -0,0 +1,139 @@ + + + \ No newline at end of file diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000..9608656 --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..bac9e8b --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,42 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import Toast from 'vue-toastification' +import { MotionPlugin } from '@vueuse/motion' +import 'vue-toastification/dist/index.css' +import './assets/main.css' + +interface Toast { + type: string +} + +const app = createApp(App) + +app.use(router) +app.use(Toast, { + position: 'top-right', + timeout: 3000, + closeOnClick: true, + pauseOnFocusLoss: true, + pauseOnHover: true, + draggable: true, + draggablePercent: 0.6, + showCloseButtonOnHover: false, + hideProgressBar: false, + closeButton: 'button', + icon: true, + rtl: false, + transition: 'Vue-Toastification__bounce', + maxToasts: 5, + newestOnTop: true, + filterBeforeCreate: (toast: Toast, toasts: Toast[]) => { + if (toasts.filter((t: Toast) => t.type === toast.type).length !== 0) { + return false + } + return toast + } +}) + +app.use(MotionPlugin) + +app.mount('#app') \ No newline at end of file diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..7cb9e15 --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,27 @@ +import { createRouter, createWebHistory } from 'vue-router' +import ContainerList from '../components/ContainerList.vue' +import ContainerDetails from '../components/ContainerDetails.vue' +import LogsView from '../components/LogsView.vue' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { + path: '/', + name: 'containers', + component: ContainerList + }, + { + path: '/containers/:id', + name: 'container-details', + component: ContainerDetails + }, + { + path: '/logs', + name: 'logs', + component: LogsView + } + ] +}) + +export default router \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..850b922 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..c4725c5 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..862dfb2 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..f4c67ab --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true + }, + '/ws': { + target: 'ws://localhost:8000', + ws: true + } + } + } +}) \ No newline at end of file diff --git a/server/app/__pycache__/auth.cpython-313.pyc b/server/app/__pycache__/auth.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38046cd3a148257655c5619d07b506a2e751e972 GIT binary patch literal 2016 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 fORn0&saP5in literal 0 HcmV?d00001 diff --git a/server/app/__pycache__/models.cpython-313.pyc b/server/app/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9b4405411fd36b1d72ace872849bdf7b50178a0 GIT binary patch literal 3095 zcmbtWO>7%Q6yCMR-d+FZr)knurNM35F3^Uy2?`YyLX)O-8n?=(NJti|t-WzqwRg?z zIy5INMS|qEJs_D|k8taeBPY&Sma5H2hyw={aY2f?^WLm$oCr~VEcx3v-UkXKmikd(pnn<8 z^JH~>!=kQNh40%AxTUrGg~IK^GLy9RyZ7!bEwcE%h5H&j6KhM_`oh{ecdjk1-u)0r zvbeOc$fO%TI4*qxfShOgj;d&#<(Sc73Y?m6N-u^ zy)Dv2F$r&_IFi{2WRrmZ(1QN*k^bqyp5Ryd{tO>43u6%+0ep!gzM%nMvhNG$F??j) z6#BI^9Vw3X$IJA%W5A8|zLD*5$AKH~aYH?B0ytdV_@C>7Sq*dV2t83rtD{Wm>v%s4 zUt|#rWQj6kF{@@&OcqCJG+a6g-fu*fEt?xgqw47!M#*#N7K_y#>bWdlH6Fm$Gubx1 zPaXQmWzwcoZPZMlOnXz`G^kr(nO+v?pdG8U#gtOLp*LLoPO1h;LAQFXbZ?lj$)%`H z(lM9=J&rJrFaf}#u16UpgkjqbirBGHyIg)UcEYL!i3T-+zJYBC0Ru%Za!(bi2fc*t z83Zf`^fCf!j$Q#^v5l%@c=RgTQOow|EV`x;Fh@eo=FmF(0x*_t0Puwn$jR;HAd%j_ z9V9c``5<+&LrwJZJpGKcC+C{CcFO+MU8_Up_D;O=wbwo|+g#pR z_s{Hp)FHEbr_VlHZ=b#v_G(4_`CYq1t_5c=GzYP|@`|0IG_FwvudT0{;3KkH%{ zfIy94MuqVR(nH&KAU>b3gkiw0|0xW(WF7{*3lyb(CFU~4dqIV$(!gEdejP6z`@E9e zgpaZ~R7ucOfyq$gY!BkX*P<#fS$Y9p4lB?UT1SMkf*b;V$UO+E_hrYX8 zdrop;2(b763jse|!y|Q2330B3VV-YmW(BJ$ufqGhNMhPi)Ea#c;e7;LOhJ`i5`n1~ zUBmhF@_G+B1RUGNGXU1jE9+oxJSXGfJJ_?J4l}vFRW@w6-%yl)cp}8TtvcMr!i?ml zo*xXDw2YW=Tcis1<>|~16P~p^L%I6+7V!BT-Ww*r6JFY`)B7+0UNhZ~0e*{$qWF_A z{gZIMEu8;RIQ642)+LfS;g?#oT>*^lNJhNsD_sE|-6>hTzH_@Pz@vMNh(kMDT>&25 YR8&0UTU`Mj-2@RQTFURhPw@5r0ez*uKmY&$ literal 0 HcmV?d00001 diff --git a/server/app/api/api.py b/server/app/api/api.py new file mode 100644 index 0000000..19681e7 --- /dev/null +++ b/server/app/api/api.py @@ -0,0 +1,7 @@ +from fastapi import APIRouter +from app.api.endpoints import containers, logs + +api_router = APIRouter() + +api_router.include_router(containers.router, prefix="/containers", tags=["containers"]) +api_router.include_router(logs.router, prefix="/logs", tags=["logs"]) \ No newline at end of file diff --git a/server/app/api/endpoints/logs.py b/server/app/api/endpoints/logs.py new file mode 100644 index 0000000..2a4c4bc --- /dev/null +++ b/server/app/api/endpoints/logs.py @@ -0,0 +1,31 @@ +from fastapi import APIRouter, WebSocket, WebSocketDisconnect +from app.services.logs_service import logs_service +import asyncio + +router = APIRouter() + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + """Endpoint WebSocket pour recevoir les logs en temps réel.""" + await logs_service.connect(websocket) + try: + while True: + data = await websocket.receive_text() + # Ici, nous pourrions ajouter la logique pour filtrer les logs + # en fonction des préférences du client + await logs_service.broadcast_log(data) + except WebSocketDisconnect: + logs_service.disconnect(websocket) + +@router.websocket("/ws/{container_id}") +async def container_logs_websocket(websocket: WebSocket, container_id: str): + """Endpoint WebSocket pour recevoir les logs d'un conteneur spécifique.""" + await logs_service.connect(websocket, container_id) + try: + async for log in logs_service.get_container_logs(container_id): + await websocket.send_text(log) + except WebSocketDisconnect: + logs_service.disconnect(websocket, container_id) + except Exception as e: + await websocket.send_text(f"Error: {str(e)}") + logs_service.disconnect(websocket, container_id) \ No newline at end of file diff --git a/server/app/auth.py b/server/app/auth.py new file mode 100644 index 0000000..8130771 --- /dev/null +++ b/server/app/auth.py @@ -0,0 +1,37 @@ +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from jose import JWTError, jwt +from datetime import datetime, timedelta +from typing import Optional + +# TODO: Déplacer dans un fichier de configuration +SECRET_KEY = "votre_clé_secrète_ici" +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + +async def get_current_user(token: str = Depends(oauth2_scheme)): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Impossible de valider les identifiants", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + username: str = payload.get("sub") + if username is None: + raise credentials_exception + except JWTError: + raise credentials_exception + return username \ No newline at end of file diff --git a/server/app/main.py b/server/app/main.py new file mode 100644 index 0000000..6468f1e --- /dev/null +++ b/server/app/main.py @@ -0,0 +1,77 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from fastapi.websockets import WebSocket +import uvicorn +import logging +import asyncio +from app.routes import containers, agents +from app.services.agent import AgentService + +# Configuration du logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +app = FastAPI( + title="Étoile Polaire", + description="API de gestion des conteneurs Docker et des agents", + version="1.0.0" +) + +# Configuration CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], # Frontend URL + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Inclusion des routes +app.include_router(containers.router) +app.include_router(agents.router) + +# Service d'agents +agent_service = AgentService() + +@app.get("/") +async def root(): + return {"message": "Bienvenue sur l'API Étoile Polaire"} + +@app.get("/health") +async def health_check(): + return {"status": "healthy"} + +@app.on_event("startup") +async def startup_event(): + """Tâches à exécuter au démarrage de l'application.""" + # Démarrer la tâche de nettoyage des agents inactifs + asyncio.create_task(cleanup_inactive_agents()) + +async def cleanup_inactive_agents(): + """Nettoie périodiquement les agents inactifs.""" + while True: + try: + inactive_agents = await agent_service.cleanup_inactive_agents() + if inactive_agents: + logger.info(f"Agents inactifs supprimés : {inactive_agents}") + except Exception as e: + logger.error(f"Erreur lors du nettoyage des agents : {e}") + await asyncio.sleep(300) # Vérifier toutes les 5 minutes + +# WebSocket pour les logs en temps réel +@app.websocket("/ws/logs") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + try: + while True: + # TODO: Implémenter la logique de streaming des logs + data = await websocket.receive_text() + await websocket.send_text(f"Message reçu: {data}") + except Exception as e: + logger.error(f"Erreur WebSocket: {e}") + finally: + await websocket.close() + +if __name__ == "__main__": + uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file diff --git a/server/app/models.py b/server/app/models.py new file mode 100644 index 0000000..5d0b4b9 --- /dev/null +++ b/server/app/models.py @@ -0,0 +1,56 @@ +from pydantic import BaseModel, Field +from typing import List, Optional, Dict +from datetime import datetime +from enum import Enum + +class ContainerStatus(str, Enum): + RUNNING = "running" + STOPPED = "stopped" + PAUSED = "paused" + RESTARTING = "restarting" + REMOVED = "removed" + DEAD = "dead" + CREATED = "created" + +class Container(BaseModel): + id: str + name: str + image: str + status: ContainerStatus + created: datetime + ports: Dict[str, List[Dict[str, str]]] = Field(default_factory=dict) + labels: Dict[str, str] = Field(default_factory=dict) + networks: List[str] = Field(default_factory=list) + volumes: List[str] = Field(default_factory=list) + env_vars: Dict[str, str] = Field(default_factory=dict) + restart_policy: Optional[str] = None + cpu_usage: Optional[float] = None + memory_usage: Optional[int] = None + network_usage: Optional[Dict[str, int]] = None + +class Agent(BaseModel): + id: str + name: str + hostname: str + ip_address: str + docker_version: str + status: str + last_seen: datetime + containers: List[Container] = Field(default_factory=list) + +class LogEntry(BaseModel): + container_id: str + timestamp: datetime + message: str + stream: str # stdout ou stderr + +class ContainerUpdate(BaseModel): + container_id: str + action: str # start, stop, restart, update + agent_id: Optional[str] = None + +class AgentRegistration(BaseModel): + name: str + hostname: str + ip_address: str + docker_version: str \ No newline at end of file diff --git a/server/app/models/__init__.py b/server/app/models/__init__.py new file mode 100644 index 0000000..6248261 --- /dev/null +++ b/server/app/models/__init__.py @@ -0,0 +1,7 @@ +from .container import Container, ContainerLog, ContainerUpdate, ContainerStats +from .agent import Agent, AgentRegistration + +__all__ = [ + 'Container', 'ContainerLog', 'ContainerUpdate', 'ContainerStats', + 'Agent', 'AgentRegistration' +] \ No newline at end of file diff --git a/server/app/models/__pycache__/__init__.cpython-313.pyc b/server/app/models/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..324ed92da420f873fc8eee8b6a4df8349ced5cd1 GIT binary patch literal 398 zcmZvYy-EZz6or$S%%3_g7WP{SgJ2pf5kc%`#YG3}5Zo98*~xGx3YI>E58+ez5O!&{ zuo4^DeE}13$HrTH_uP}*6UeG8H;}K_y-&IH{XGopihjlF(2Hl(paxG+jRDsYM0E^t zO@NGpA5D@vg|yBf8zYLE_!2`dNtJwHzgiJjoSD{g)e3N1gFH9VjfUyEn{g}L_Fy!Y z+rEliawoXRxK*aDQiy##kbop04afp=kBh2s#m)NR|L(ekhwQ%*(CfgzhP0s#TV;gYMDg$KJfx i;si!MD*Qyh>+uz1j2CG7hNcU&za;Ty^prvAPrd=KXlShf literal 0 HcmV?d00001 diff --git a/server/app/models/__pycache__/agent.cpython-313.pyc b/server/app/models/__pycache__/agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93aa6a2f99751002d9c2b8b48121f584b0a4938c GIT binary patch literal 2895 zcmb7G-HQ`f6u*=C$jszxS64SG?xqz#hN5X}#eND_OEIe}IPF6rFiex#*}?2gdhe{T zZ^D9I=}UYN{1^J%zoaZkxFCJ#Q|L<}DZcicGf6g)Q0+i|_uO;8&OPUM&dsn?vKf58 zUHB`!p)vL+b|#-b7KSfDc+OnrDjTe!aHWQFZX?%Fx!TaU*3h|5I(5Tn7~B9^bM=jU z!{la-RoVN@HBK`(-&C%t*`C}YtqEGIX-%~@X>HIJX66=1TLf)sW^R$RWzbe;=9Wmi z1lr{pZJD&kXon*kis5l*Wq<}binPA=MQ|@^2OUS1=KXFOC9&U;+U=;7O6_hWQkZ16 z{WM6UonWAhSKUhD)Q{qTx3Y_6IKCwt-hlpd)?{398PB;2SI@Ji*3{kHMHnGTCCLDt zHr44UxdzXNdB>F2%`k{lv~OU5UTU$w6G&}45nx$2;i=Tq3KNrdD8a0 zous|nLAmI8KkfRR@kqtG=0-=)ZX&U$~7@4=}JfsYV z&^Ha-q?vaq@SrnibVLErS)jAW^D;8zcwuIqK?KS3Vwi)FXSe*mi|ia!VE0X40_0@` z?DHiAyqQy4cypQ#`Oi3pfv+MQt*&C1wpiq$8jd31N#b(=2;=Fs$F~nI^{zg>e5kGc zXYb7V<8KZ=?cI9%)k|$Xb1QB(=T@`~zu_Z*H{H50pD@R=e8S8k7dBBM$B~q$Y|A%I za9W%ZE0X$>9w{fO(I;wFc*>oH2o2+9$a&MrBfyn!dhz6`L+w<5`9!bv z;%aaE+1fPQaN%Olz4PmJlx-+cw!!OV(Cf8KjC~M<@Pu-DS_bGGs!-CzM~t2`JL-CV zyUhb3WVxNR9tPYS=hfIz`L{S~5y_BN9089MSk@*_OnG_#UVrKM{{8;S>Pu}kBN5JA z?1~PIyc3BQxPl5)R(NnUD?C_?sN^({N|1%racype1*b^4F}MO6ejb5J^k*nxiRE7) z5Z}m~A7J-?5b7vF7rE^m?uLNJi;n;%xk=CStnV;EiE86qrTm<5YtyMnv%A;!WBC8T zRoF)w>0URALr#wkF`7)lsv)guzGSt7{vgl;T!UdUDC0VX6>pn`4IgT0ewqE*xxsHj z7wf6m2KZZ56y+6L`-5Hlon3sz&c0%`k)|n?o;707jEah4^&F@qiZwDcrG_#zqoS>> iAKV!+Xhu~%(?c^VYfAM6t*wrVrcyh&^ACfDEcq8EUN^=7 literal 0 HcmV?d00001 diff --git a/server/app/models/__pycache__/container.cpython-313.pyc b/server/app/models/__pycache__/container.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7ceccf84f233e1e1e1a065893bea8d507c3715e GIT binary patch literal 3103 zcma)8&5zSo5VsS@cD|D(%f~Jh7M8MUfuPc=P(CVH%13rLREjDUHI-!&Z`Q@J-Dd|# z)x%1W+T|7wRNGq*z4Slmf6=ZYt)eHP9(r2!g0$(qGvg$LB}F*Ocs%po`1$vnnfG>+ z$(R7w9}EA~zlsRL-}o@R;F+%7-{JO5C<|qANhpd;JcfE`DO3!za8Y7XF~TB6naN}i zFDXTZDaV9^!f~N2%?Ra4Mf@lngkmaLWw0t^)+kw3utvwMF|x+M8XvR9$(jIba?F|_ zYYMFCF>A7($!B-))p?Oeuj`(%;?xW)FLCLHS@pTJY!G^bHumvf$f=zrZgQ)|`e>Fs#C=!|I^; zj%gXLW9cR{3ZB6p8myqZZUL5MSYBcDHD}!x!%QTBFnDtyeiVB94zJ(ZD0ij9z4TPO z)XN>{N(XwmL+iIUs@u{bHZz92S^s%yC_# z##Mhb7_`w~(1(LT9SkPSOL>J&AprYO)Q$`VuF!iA1i+li zwpV(YeeJse%%r_$`{8a71|v+yy8@m!;4P2vMr~LiY9;z%2ss}ikKO=z)P@BUqk>5- z4l5wQ8NZmvt8PFIT2^kkV0)8~pEaKvNxEvNcGV}@Si@j!pX zOy3v)8=nL$kE~jb?g!8k$P&Dq?MFeVPJ(*@y(n;@UI7Hao!#G+_V=cyyVCSPCbOLr zo$vpgI}c>7i_mTceYCDQGsUOd$<_L72vE#or9b6o8B*rn~#4! zyOqs%rE|Uc1?aGA9e;E2X=^Jr*OeA<@=E6d7_Q(v7*Oim-Yh>|LEn)%3S52i^|mw@ zBrGm;a>D)!p1lb>+&?LSl%Oetjdsj|joJm+qOgm`Dsn|0OF1QI+2D7(e80>F3Wkdr%M}VcDJ8DqI25=p?;8-160=8%`Nbzd)X_0&y3*YL z(fyXm@{;szX(6B)U;YZsVKCsedjZ5=n#cCu(OyuN7#x>|y&sQBP~R#~jG$k3>O1El z7?0wa3~I6Aas?D>?@;VDr|DzSLzy=Q?K?UVeHDTP&707_gO8I?y@3uC_*M@|0#ws< z@}Sb1npwZOvA8YG1Q6iJQa%O8uvSgC;dI0h11|e57Y=nnjT50nyaGN(E1~la?HkzK zsj`Vndi9=No2!?DliXFd41-1Zd-p)R2#cclTzKa%;dECx{aiTGmn3nflkW>~>nBt( x*SXji;MUK{;&kU;Uw~UblMs)0qJ05w{ll`D@0e)M_cLK}wsY}c0dDkZ{{dw5xFi4o literal 0 HcmV?d00001 diff --git a/server/app/models/agent.py b/server/app/models/agent.py new file mode 100644 index 0000000..3c096b5 --- /dev/null +++ b/server/app/models/agent.py @@ -0,0 +1,54 @@ +from pydantic import BaseModel +from typing import Optional, Dict, List +from datetime import datetime +from .container import Container + +class AgentBase(BaseModel): + name: str + host: str + port: int + token: str + +class AgentCreate(AgentBase): + pass + +class AgentUpdate(BaseModel): + name: Optional[str] = None + host: Optional[str] = None + port: Optional[int] = None + token: Optional[str] = None + +class Agent(AgentBase): + id: str + status: str + version: str + last_seen: datetime + created_at: datetime + updated_at: datetime + containers: List[Container] + + class Config: + from_attributes = True + +class AgentStatus(BaseModel): + status: str + version: str + last_seen: datetime + containers_count: int + system_info: Dict[str, str] + +class AgentRegistration(BaseModel): + name: str + hostname: str + ip_address: str + docker_version: str + +class Agent(BaseModel): + id: str + name: str + hostname: str + ip_address: str + docker_version: str + status: str + last_seen: datetime + containers: List[Container] \ No newline at end of file diff --git a/server/app/models/container.py b/server/app/models/container.py new file mode 100644 index 0000000..28505f7 --- /dev/null +++ b/server/app/models/container.py @@ -0,0 +1,54 @@ +from pydantic import BaseModel +from typing import Dict, List, Optional, Any +from datetime import datetime + +class PortBinding(BaseModel): + host_ip: str = "0.0.0.0" + host_port: int + container_port: int + protocol: str = "tcp" + +class NetworkStats(BaseModel): + rx_bytes: int + rx_packets: int + rx_errors: int + rx_dropped: int + tx_bytes: int + tx_packets: int + tx_errors: int + tx_dropped: int + +class ContainerStats(BaseModel): + cpu_percent: float + memory_usage: int + memory_limit: int + network: Dict[str, NetworkStats] + block_read: int + block_write: int + +class Container(BaseModel): + id: str + name: str + status: str + image: str + created: str + ports: Optional[List[PortBinding]] = None + volumes: Optional[List[str]] = None + environment: Optional[Dict[str, str]] = None + networks: Optional[List[str]] = None + health_status: Optional[str] = None + restart_policy: Optional[str] = None + command: Optional[str] = None + +class ContainerUpdate(BaseModel): + image: Optional[str] = None + command: Optional[str] = None + environment: Optional[Dict[str, str]] = None + ports: Optional[List[PortBinding]] = None + volumes: Optional[List[str]] = None + restart_policy: Optional[str] = None + +class ContainerLog(BaseModel): + timestamp: datetime + message: str + stream: str = "stdout" \ No newline at end of file diff --git a/server/app/routes/__init__.py b/server/app/routes/__init__.py new file mode 100644 index 0000000..2366fd7 --- /dev/null +++ b/server/app/routes/__init__.py @@ -0,0 +1,4 @@ +from . import containers +from . import agents + +__all__ = ['containers', 'agents'] \ No newline at end of file diff --git a/server/app/routes/__pycache__/__init__.cpython-313.pyc b/server/app/routes/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc9d1d0a7f9d9301ddcd7e14014188bd62e31e09 GIT binary patch literal 260 zcmey&%ge<81goT8rw0M)#~=<2FhLogRe+4C48aV+jNS}hj75wJAT|?_%~Zq$WHSe| z=ra{Dr?YCZy<`L`)nvTIm7Jefl9-v7T2u_=u_dOb=9Lr|F#{!wSb&6|Cd(}b1oswu ze0*X~PJH}IhR;A*hFezp8Tq-X`l&hkY57IzKt@S^W=?8BeokU$QL27%YEfBgk$z%9 zfqqebX-R6aetdjpUS>&ryk0@&Ee@O9{FKt1RJ$UO&p_@e76lR?m>C%v?=nc-We|D5 REj~ly0+&)Fdl3gv1^^OkKy&~A literal 0 HcmV?d00001 diff --git a/server/app/routes/__pycache__/agents.cpython-313.pyc b/server/app/routes/__pycache__/agents.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad8f2c23e99a619903c42227d7df8f995740be3f GIT binary patch literal 4542 zcmcgv-A`M|6`%3_vayZ924hTcZbH6*5)(Fvk2p{--Lx@THsY>#SHco<%>|r-?auY3 z*;Fef?MmxEpese%7dEL>)4m{eD`neNC4^K}eX`{ThpSDtRr?ZEsz9XDO6^0>jBO0Y z1p1+l%!lx2>qFUREoK*>|QV-^f6M9f+tZ36C5%T zQ-~v+OxsM8d`KVy=$yh&iXn+eAu} zjF?Dcq;dLmW!h@3G(?1mBtU~=9Wh0?vnXO8#HIeLSg}Q#u9_6P7v16S;K8yPX$D{C zt3E0k_>$^+X^fbeCth*Xcyb-Fs-`16HAkAto}4wFTEJ6pWhK>Gd6lQeh=q9*6<3Wn zH+b_`y;L-%sd6@lPO7@HmKtB4Blg&O0$=VLU+oKR-JTb8Io+IL3EsXroz&ARG3?)Y z`|aCr+>NQJbUc|bq;IJyH8H1kV}rjH*U~QlUDQGdE~tsLVP(|>QKM&voW0zFr3vjmE5!Xqp7$*N9_A`>QMeJ4iWQvT zg4{fwN7uR2XdVxN-p${0({okjbW+#kB~_D4=Wu!I#Y1bNlW@E5(L!497 z(fCrgK!5;{VO;{-+42cm_QFQ!3Jp$6lWv|kscvUuX5rLnG>DzHVO68!WGKe4e=)hN z`qd@>e3C4Hl1|2#)KqdQ8YilsnyCb$mGTn`6RjMy%c=JX8~|d1?pwPB;sv^ownae* zeCav&Nn^(TwC!B6tv}z^UuYXBHVG0$EcBu*4mRO6+>3tE5VUi@as|!YhFJi0!_Gku5XKKnIF8SKV3;dv{W$*) z7X81D^Cj>}%N)GJGxW$=S2!7vqVIN0Sk-$6vM;u0isJ{l@ zrZ?AOdTKLER7Ml=1l-i+C}n9)p1TMLVdo)hDHs=@kAGiRfkS=*9P;;Ountm|1rR0V zG+g18%{2xd2BQjsER>*`lM-?mhRX0c3ofegAq-9RfOCP4qk_Uni-ITtN2;=cyQ}E# z%e(usiv{;kv1uqHJa@Ew={%jC$PG{CowstfTTdM=8QYG8nuouBX-2lzS5n086_^Dm z;tW7308kp`AG-tSbK3B096(<*1by6w=nA%T8|?zseH?Vf_LKh}IUEBTxa#%D;XOQ9 zrkX?Kz=!!`LBlips0SM12+#<7^ictg5EN?YL)0ldys4(;%~knMQYVx|Sag9s&OsNU ziPvEi*mU-Q*9U?~7!Yd6#9lI)+fOJI|BO(|jM7afqk{gl7>Gl_@bwmb!+GEE6Hmc+ zv*@{*u|Id5ss+ZWocRGDgr!R-0>bbVZHHnjVX7X0-#nV1tZHO{?`0A(RkN zeOV;x0;VDL=%EkhB~;KnOIrkS2nC+=MbALqGq65f@C1s^Kt_7*aMz%qsXf;*l5>pa z%%gw>*Yy)XK`XOF7qG+_utb1=>={G9!ND&41s+4cZ3xQn*0_Ql+(w5$b(w>%d(M3? zj=z+M)yLB@%<&(k6~{71De@p(*BIzHM-QvCuYNY#z^u&mFBbs9{;Bgq+_zF?CFJdktwm z3QJ4H34Jh1+n_JMA*SC?#S;sJ5}jdVe@nE>Y|F$SGB{%STp=V+TNjR`N@sd`%95VH7n?T!kB9r-}jt3nX zWwrHwM-E@y8o!bI_Pd4g_n5)M?`>0qC$2raOZ)8TWN&8A(~0`GhQ@N^Zxx1a?;r~f zJdADAY8~vLqVrwZHi5S7rt=-Jn+oUNnJ-K&FuPS3v&+t~>i1k-^UepInGXuMb89r1 zdwsevs_Zvihb@+;^c|csXOj!~%+}Z&xf|~k#@;O%DQu!A)rrpT&~=jQaQOcM`@XOf literal 0 HcmV?d00001 diff --git a/server/app/routes/__pycache__/containers.cpython-313.pyc b/server/app/routes/__pycache__/containers.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38f753f36bc5e23136c5c0b14069d2fa8421d28b GIT binary patch literal 15823 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 None: + """Valide l'ID d'un conteneur.""" + if not container_id or container_id == "undefined": + logger.error("ID de conteneur invalide ou undefined") + raise HTTPException(status_code=400, detail="ID de conteneur invalide") + +@router.get("/{container_id}", response_model=Container) +async def get_container(container_id: str): + """Récupère les informations d'un conteneur spécifique.""" + try: + validate_container_id(container_id) + container = await docker_service.get_container(container_id) + return container + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur lors de la récupération du conteneur {container_id} : {e}") + raise HTTPException(status_code=404, detail="Conteneur non trouvé") + +@router.get("/{container_id}/logs") +async def get_container_logs(container_id: str, tail: int = 100): + """Récupère les logs d'un conteneur.""" + try: + validate_container_id(container_id) + logger.info(f"Requête de logs pour le conteneur {container_id}") + + # Récupérer les logs + logs = await docker_service.get_container_logs(container_id, tail=tail) + + if not logs: + logger.warning(f"Aucun log disponible pour le conteneur {container_id}") + return {"message": "Aucun log disponible pour ce conteneur"} + + logger.info(f"Logs récupérés avec succès pour le conteneur {container_id}") + return {"logs": logs} + + except HTTPException: + raise + except docker.errors.NotFound: + logger.error(f"Conteneur {container_id} non trouvé") + raise HTTPException(status_code=404, detail="Conteneur non trouvé") + except docker.errors.APIError as e: + logger.error(f"Erreur API Docker pour le conteneur {container_id}: {e}") + raise HTTPException(status_code=500, detail=f"Erreur Docker: {str(e)}") + except Exception as e: + logger.error(f"Erreur lors de la récupération des logs du conteneur {container_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.get("/{container_id}/stats") +async def get_container_stats(container_id: str): + """Récupère les statistiques d'un conteneur.""" + try: + validate_container_id(container_id) + logger.info(f"Récupération des statistiques pour le conteneur {container_id}") + stats = await docker_service.get_container_stats(container_id) + return stats + except HTTPException: + raise + except docker.errors.NotFound: + logger.error(f"Conteneur {container_id} non trouvé") + raise HTTPException(status_code=404, detail=f"Conteneur {container_id} non trouvé") + except Exception as e: + logger.error(f"Erreur lors de la récupération des stats du conteneur {container_id}: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.post("/{container_id}/start") +async def start_container(container_id: str): + """Démarre un conteneur.""" + try: + validate_container_id(container_id) + await docker_service.start_container(container_id) + return {"message": "Conteneur démarré avec succès"} + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur lors du démarrage du conteneur {container_id} : {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.post("/{container_id}/stop") +async def stop_container(container_id: str): + """Arrête un conteneur.""" + try: + validate_container_id(container_id) + await docker_service.stop_container(container_id) + return {"message": "Conteneur arrêté avec succès"} + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur lors de l'arrêt du conteneur {container_id} : {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.post("/{container_id}/restart") +async def restart_container(container_id: str): + """Redémarre un conteneur.""" + try: + validate_container_id(container_id) + await docker_service.restart_container(container_id) + return {"message": "Conteneur redémarré avec succès"} + except HTTPException: + raise + except Exception as e: + logger.error(f"Erreur lors du redémarrage du conteneur {container_id} : {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.websocket("/{container_id}/logs/ws") +async def websocket_logs(websocket: WebSocket, container_id: str): + """WebSocket pour les logs en temps réel d'un conteneur.""" + if not container_id or container_id == "undefined": + logger.error("ID de conteneur invalide ou undefined") + try: + await websocket.send_json({"error": True, "message": "ID de conteneur invalide"}) + except: + pass + return + + async with manage_websocket_connection(websocket, container_id): + try: + # Créer une tâche pour suivre les logs + task = asyncio.create_task(handle_container_logs(websocket, container_id)) + active_connections[container_id][websocket] = task + + # Attendre que la tâche se termine ou que le client se déconnecte + try: + await task + except asyncio.CancelledError: + logger.info(f"Tâche de suivi des logs annulée pour {container_id}") + except WebSocketDisconnect: + logger.info(f"Client WebSocket déconnecté pour le conteneur {container_id}") + + except Exception as e: + logger.error(f"Erreur WebSocket pour le conteneur {container_id}: {e}") + try: + await websocket.send_json({"error": True, "message": str(e)}) + except: + pass + +async def handle_container_logs(websocket: WebSocket, container_id: str): + """Gère l'envoi des logs pour un conteneur.""" + try: + # Buffer pour accumuler les logs + log_buffer = [] + last_send_time = asyncio.get_event_loop().time() + BUFFER_SIZE = 100 # Nombre maximum de logs dans le buffer + FLUSH_INTERVAL = 0.1 # Intervalle minimum entre les envois (100ms) + + logger.info(f"Démarrage du suivi des logs pour le conteneur {container_id}") + + async for log in docker_service.follow_container_logs(container_id): + # Vérifier si la connexion est toujours active + if container_id not in active_connections or websocket not in active_connections[container_id]: + logger.info(f"Connexion WebSocket fermée pour le conteneur {container_id}") + break + + # Ajouter le log au buffer + log_buffer.append(log) + current_time = asyncio.get_event_loop().time() + + # Envoyer les logs si le buffer est plein ou si assez de temps s'est écoulé + if len(log_buffer) >= BUFFER_SIZE or (current_time - last_send_time) >= FLUSH_INTERVAL: + if log_buffer: + try: + await websocket.send_json({"logs": log_buffer}) + logger.debug(f"Envoi de {len(log_buffer)} logs pour le conteneur {container_id}") + except WebSocketDisconnect: + logger.info(f"Client WebSocket déconnecté pendant l'envoi des logs pour {container_id}") + break + except Exception as e: + logger.error(f"Erreur lors de l'envoi des logs pour {container_id}: {e}") + break + log_buffer = [] + last_send_time = current_time + + # Envoyer les logs restants + if log_buffer: + try: + await websocket.send_json({"logs": log_buffer}) + logger.debug(f"Envoi des {len(log_buffer)} logs restants pour le conteneur {container_id}") + except WebSocketDisconnect: + logger.info(f"Client WebSocket déconnecté pendant l'envoi des logs restants pour {container_id}") + except Exception as e: + logger.error(f"Erreur lors de l'envoi des logs restants pour {container_id}: {e}") + + except asyncio.CancelledError: + logger.info(f"Suivi des logs annulé pour le conteneur {container_id}") + raise + except Exception as e: + logger.error(f"Erreur lors du suivi des logs pour {container_id}: {e}") + raise \ No newline at end of file diff --git a/server/app/services/__pycache__/agent.cpython-313.pyc b/server/app/services/__pycache__/agent.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64fc5526046e9f27a7d0bfb332a26b98657efd20 GIT binary patch literal 4352 zcmb7HU2NOd6}}`znfkG1Tb6$loBp>PN6HYVL;jjM^D@_7yuzG<05+hhGOZ|+N$ryK z)?`436e!%GSemYZ)9#_!TMP_X_OKy+T7tZEZ&BSr0+%5HwijbBa-AUvu!o&UhK;m&_%IN<({50&9@ne86}bCW1U;bsWMnV;dNdCE@< zRG9WqkBn==j5sY)aoS70@a$2QJLiyvv`+CZNU-8t@QSbp*6Y~1|10ZoPtXs5ZqU``6(3uxcXb#E_1Z$D<{{pJ+4n8Z5SFa4TlK{W% zBS3DFBP2m`6&aaF2^+`U8Kkar2+4r`&@bK;+Ss5$(6Jj9nLU$+W6*WZ_84kz$-sBY z_VM^7eOZmGx%iS!Gr*X7HmBzGTq;Y|IPOttJeAKg7W`qv+1auDo3@lpX0@!DOxls% zH;Pr3C3t}m2J&-K8W~;nycc-fedyQvs^>3K*ETE@J4ko=-}X|~w5XP@UI$bH@OvA6 z+y4gAPh>F2eAQU9wai7UL+Wxw%0P# zn*I~o1P@UU#jDW{6iDn_lXwo}4D^}|gGNx?;M`87NhY2YJE6lyCq9V|BW6`J}=k=|ltv=A9Bwe=Uw`&t8 zCQflQ3TzEb5r_BYu5;P;M`0L2viGF*b}Cr@ygP5`L?BeWBIu9^`P9-gX! zkl-u8vCuW=Ra1U=TfVGYRL-d~^SLQ64g-*wwPUmN0H{Sp<~HU?Isyx{A4wmOb-{5Z zUY?EuG4Rb*?}e>AW_`zZNf%RcjSKhDz;_xafS@0r2Ew15{OIIHV5k&sd)nBt7Pt|( zHC1RFTooU8_I(onIKI(&XjObKRBG#iDOlqeVM63;e1c#1HvG1~1;p{m{x+XO%{?Q* zRg#8lMb1K~&pljETlt4~D2qU5I60r9vY~78Ym>1k&#Y$)8P#0pcg$s=H!CprfzU;G zYmIrT>qDSzv;$#A35Bm;efR41x88m0)=xJ=Baft!7c6kD9q8t23J1ELUHEli_U1~s zdKFS2cE=o|ZO_=7HLC{b2SqISs%+|(0VpyY=oqqENlXvdjTrU?Ijrg!|F(A}m9tcX zVjXZwjuY$wRB_B;*zGlT*d<{wI|y4@n1{mePp-|}m?^dl6K8V%~?9jX+$>pqO znW|9(e$3G9^4*YZTr9wnGxkHmPF2!)Fhn(tRQ3S&tF?1yt%8*>xgcEd7(yJbA0|_f zE-WyAe*EIVel_+mRM>f}>}(`Bg*(oj_>hoA*mGXSn)fVa9+ zguX}Jjp?k06?F;l%-MCWiD#pAyD}rgx0p56Wy8smXdrqDNYv}Z{;R;-0@lK6(zHX) z!4*&#tgb*7h2ca-TRNwvG%F9yl~dCzQ8G{ipOg`*4wh?}Hd zR;x?BgT>zQLhpF7ce2nsxzT&B*mZ7I{6cEqQ^m2b+7iiu?{Y5;Y?-f}o*3r-#7zu( z9*`km-EvCt%Ck{!6N^=}1^;(oQw#q){K@39zG&r;4P zNwaTY_Dv?&3B{Vtf|x=%A&g*7Fg^zI4*8Pj#aLNt5F?MiakflgzHdFlsVqxknaf+v9h{-<-tf(Xu3pkvm7r1g5*= z&v4pm2#7uRlrn+ovzE`&fA07fvgJ;EPuh9UrTf;W`g44b^V#^t(Rzx#hwU}r%xATX zV+>{>G`*Ah@Itm1%&z7P$ChYS+w1(xWPdrG+hcpXV-Z_A<+$oqdI1CwwZ!NLQWiLl h`zr}NA;SeS{Dh1=A&37)dY^l~&2f=02~u{*e*o$7r)K~F literal 0 HcmV?d00001 diff --git a/server/app/services/__pycache__/agent_service.cpython-313.pyc b/server/app/services/__pycache__/agent_service.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25fcb820b51ce18a869058d1eefd0f3d70d50e9a GIT binary patch literal 4127 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 BK??u? literal 0 HcmV?d00001 diff --git a/server/app/services/__pycache__/redis.cpython-313.pyc b/server/app/services/__pycache__/redis.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..916ed97b6d061d82f09e0adc3a8245bdb750decc GIT binary patch literal 2999 zcmb_eUrZcD7@z%jEXTotfCYkdJxcX#%Tf8$P>phy{t;?pZi|{g+-&cb>(XWSI8|0L)5aQo1p=W%j_vBEZQ$chI_ZDLw{oGeaSoizXadpIVP561pz>`9 zi)yVZOh~FY5m2RxstFmkiuwX{MN{GwhpSmH#8pX^Azn@6ILM+jBC0_asbOzR>}_7H zVX=@J!nJ8RTIYolM#?0rfzO#qW339NNOFaR(JBCs0MEuTFqhFdSid9+kQCdr2Dmtt zR|QoZ6vmKM>QW_WmBy!0G;qSJww$DHopy+8-fuy$Zx~G>r4dieIN0_0)T!BUvIw7M zIcahbhDeyM*pt}8y)X@HXN(ywgN;e^Er47khBZm(E);JtcG{UXVulr)bZ{CR*D)<4 z<5;?hjTnJ#8#tzCGRy@8NQ`~GJ2T_Snr7Ojt7%?yIY!;ZFy)VQfO!Y~)ZBJu{KN5u zGgrlH4f*CnbK?8K+dF&a#9MOv28=*nXmcNC%9AW}+H}1DHry<>lN8`$p~`+2o{gJe zI*|gPkD)}dx2V_Y5T7eY0#cEa1zF0j5gi4F*p zTvW{Vz=SWXITXO;6`w8 z0~jx0Vckv}m@d&1r-)+%ypEnCFsg};YNe))_6i^qPoz_M0<~1!3M;so_RDF*Wis2s zcrhG;LF7#^Yry%vhUV3Vu6#q+a{caww-@Z?y8b!gcDV70yWp|ARu4t*5L*T+6}Kb!q{HrM&d`J45zFZ=T0p=EjK(f8q@N2DI6 zxX>Sen)@Oe?-Z{qGPoEZMJFnxLJ4M4xeCtzEtNN(O_U@6pD&dDVqG}R3KeI;FOH#g zqPo20`&4`?UzDAK(t5Nv8f{V@Rs8}Wi3M2-RBO-#K^!w~r>8TWD-)pKk?uF=M^o3C{f6u)f zx)i(^T$Nk$a?5<>5qk=p z|3obRH#BUQ1Nh=u1qwJu0YAH2&1NzHl%cpz7Ea^#C$bwZy)w)L0JY&75ZgRw3|G4T z60E_bW$4&HRy!(?-Rz)8lI(OHY<) z>w`)B<4{3c15JA6mlp9lD%kr=KfMacaWITTKM`I!fARdnuGPpuJ~FWU(d6@*FjB#Jdm}hAJVk5S=}o1gf(r_#Kg7Whs<^~ z4L=U=dIE8=Cz!U2DFo~$!ClnbLrsjD0We;(rV&?n&7`KgkXBAX(jxE;x^WEuFo@+5 zOm9eLs1%6_<~{U)=cWC>$)XhJa=q(_)=R{%ubupEZ~pM>f3VBS25`Xvo3HPcL#;;pqigiB#$C@ U(Gz#ko`+%^ca(d8sAcQ=6DpjPfB*mh literal 0 HcmV?d00001 diff --git a/server/app/services/agent.py b/server/app/services/agent.py new file mode 100644 index 0000000..32e4e96 --- /dev/null +++ b/server/app/services/agent.py @@ -0,0 +1,74 @@ +import uuid +from typing import List, Optional, Dict +from datetime import datetime, timedelta +from ..models import Agent, AgentRegistration +from .docker import DockerService + +class AgentService: + def __init__(self): + self.agents: Dict[str, Agent] = {} + self.docker_service = DockerService() + + async def register_agent(self, registration: AgentRegistration) -> Agent: + """Enregistre un nouvel agent.""" + agent_id = str(uuid.uuid4()) + agent = Agent( + id=agent_id, + name=registration.name, + hostname=registration.hostname, + ip_address=registration.ip_address, + docker_version=registration.docker_version, + status="active", + last_seen=datetime.utcnow(), + containers=[] + ) + self.agents[agent_id] = agent + return agent + + async def update_agent_status(self, agent_id: str) -> Optional[Agent]: + """Met à jour le statut d'un agent.""" + if agent_id not in self.agents: + return None + + agent = self.agents[agent_id] + agent.last_seen = datetime.utcnow() + agent.status = "active" + return agent + + async def get_agent(self, agent_id: str) -> Optional[Agent]: + """Récupère un agent par son ID.""" + return self.agents.get(agent_id) + + async def list_agents(self) -> List[Agent]: + """Liste tous les agents enregistrés.""" + return list(self.agents.values()) + + async def remove_agent(self, agent_id: str) -> bool: + """Supprime un agent.""" + if agent_id in self.agents: + del self.agents[agent_id] + return True + return False + + async def cleanup_inactive_agents(self, timeout_minutes: int = 5) -> List[str]: + """Nettoie les agents inactifs.""" + now = datetime.utcnow() + inactive_agents = [ + agent_id for agent_id, agent in self.agents.items() + if (now - agent.last_seen) > timedelta(minutes=timeout_minutes) + ] + + for agent_id in inactive_agents: + await self.remove_agent(agent_id) + + return inactive_agents + + async def update_agent_containers(self, agent_id: str) -> Optional[Agent]: + """Met à jour la liste des conteneurs d'un agent.""" + if agent_id not in self.agents: + return None + + agent = self.agents[agent_id] + agent.containers = await self.docker_service.list_containers() + agent.last_seen = datetime.utcnow() + return agent \ No newline at end of file diff --git a/server/app/services/agent_service.py b/server/app/services/agent_service.py new file mode 100644 index 0000000..2a1ed02 --- /dev/null +++ b/server/app/services/agent_service.py @@ -0,0 +1,75 @@ +import uuid +from typing import List, Optional +from datetime import datetime +from ..models.agent import Agent, AgentCreate, AgentUpdate, AgentStatus + +class AgentService: + def __init__(self): + # TODO: Remplacer par une vraie base de données + self.agents: List[Agent] = [] + + async def list_agents(self) -> List[Agent]: + """Liste tous les agents.""" + return self.agents + + async def create_agent(self, agent: AgentCreate) -> Agent: + """Crée un nouvel agent.""" + new_agent = Agent( + id=str(uuid.uuid4()), + name=agent.name, + host=agent.host, + port=agent.port, + token=agent.token, + status="offline", + version="1.0.0", + last_seen=datetime.now(), + created_at=datetime.now(), + updated_at=datetime.now() + ) + self.agents.append(new_agent) + return new_agent + + async def get_agent(self, agent_id: str) -> Optional[Agent]: + """Récupère un agent par son ID.""" + return next((agent for agent in self.agents if agent.id == agent_id), None) + + async def update_agent(self, agent_id: str, agent_update: AgentUpdate) -> Optional[Agent]: + """Met à jour un agent.""" + agent = await self.get_agent(agent_id) + if not agent: + return None + + update_data = agent_update.dict(exclude_unset=True) + for key, value in update_data.items(): + setattr(agent, key, value) + + agent.updated_at = datetime.now() + return agent + + async def delete_agent(self, agent_id: str) -> bool: + """Supprime un agent.""" + agent = await self.get_agent(agent_id) + if not agent: + return False + + self.agents.remove(agent) + return True + + async def get_agent_status(self, agent_id: str) -> Optional[AgentStatus]: + """Récupère le statut d'un agent.""" + agent = await self.get_agent(agent_id) + if not agent: + return None + + # TODO: Implémenter la vérification réelle du statut + return AgentStatus( + status=agent.status, + version=agent.version, + last_seen=agent.last_seen, + containers_count=0, + system_info={ + "os": "Linux", + "arch": "x86_64", + "docker_version": "20.10.0" + } + ) \ No newline at end of file diff --git a/server/app/services/docker.py b/server/app/services/docker.py new file mode 100644 index 0000000..6d9d30f --- /dev/null +++ b/server/app/services/docker.py @@ -0,0 +1,387 @@ +import docker +from typing import List, Dict, Any, Optional, AsyncGenerator +import asyncio +import logging +from ..models.container import Container, ContainerStats, NetworkStats, PortBinding + +logger = logging.getLogger(__name__) + +class DockerService: + def __init__(self): + """Initialise le service Docker.""" + self.client = None + self.init_client() + + def init_client(self): + """Initialise ou réinitialise le client Docker.""" + try: + if self.client: + try: + # Tester si le client existant est toujours valide + self.client.ping() + logger.info("Client Docker existant est valide") + return + except: + logger.warning("Client Docker existant n'est plus valide") + self.client = None + + self.client = docker.from_env() + self.client.ping() # Vérifier que la connexion fonctionne + logger.info("Nouveau client Docker initialisé avec succès") + except Exception as e: + logger.error(f"Erreur lors de l'initialisation du client Docker: {e}") + raise + + def ensure_client(self): + """S'assure que le client Docker est initialisé et valide.""" + try: + if not self.client: + logger.warning("Client Docker non initialisé, tentative de réinitialisation") + self.init_client() + return + + # Test simple pour vérifier que le client est fonctionnel + self.client.ping() + except Exception as e: + logger.warning(f"Client Docker invalide, tentative de réinitialisation: {e}") + self.init_client() + + def _parse_port_bindings(self, ports: Dict) -> List[PortBinding]: + """Convertit les ports Docker en modèle PortBinding.""" + bindings = [] + if not ports: + return bindings + + for container_port, host_bindings in ports.items(): + if not host_bindings: + continue + + # Format: "8080/tcp" + port, proto = container_port.split("/") + for binding in host_bindings: + bindings.append(PortBinding( + host_ip=binding.get("HostIp", "0.0.0.0"), + host_port=int(binding.get("HostPort", port)), + container_port=int(port), + protocol=proto + )) + return bindings + + def _parse_environment(self, env: List[str]) -> Dict[str, str]: + """Convertit les variables d'environnement en dictionnaire.""" + result = {} + if not env: + return result + + for var in env: + if "=" in var: + key, value = var.split("=", 1) + result[key] = value + return result + + async def list_containers(self) -> List[Container]: + """Liste tous les conteneurs Docker.""" + try: + self.ensure_client() + logger.info("Début de la récupération de la liste des conteneurs") + + containers = self.client.containers.list(all=True) + logger.info(f"Nombre de conteneurs trouvés: {len(containers)}") + + result = [] + for container in containers: + try: + attrs = container.attrs + container_model = Container( + id=container.id, + name=container.name, + status=container.status, + image=container.image.tags[0] if container.image.tags else "none", + created=attrs['Created'], + ports=self._parse_port_bindings(attrs['NetworkSettings']['Ports']), + volumes=[v['Source'] for v in attrs['Mounts']], + environment=self._parse_environment(attrs['Config']['Env']), + networks=list(attrs['NetworkSettings']['Networks'].keys()), + health_status=attrs.get('State', {}).get('Health', {}).get('Status'), + restart_policy=attrs['HostConfig']['RestartPolicy']['Name'], + command=attrs['Config']['Cmd'][0] if attrs['Config']['Cmd'] else None + ) + result.append(container_model) + logger.debug(f"Conteneur ajouté: {container.name}") + except Exception as e: + logger.error(f"Erreur lors de la conversion du conteneur {container.name}: {e}") + continue + + return result + except Exception as e: + logger.error(f"Erreur lors de la liste des conteneurs: {e}") + raise + + async def get_container(self, container_id: str) -> Container: + """Récupère les informations d'un conteneur spécifique.""" + self.ensure_client() + container = self.client.containers.get(container_id) + attrs = container.attrs + return Container( + id=container.id, + name=container.name, + status=container.status, + image=container.image.tags[0] if container.image.tags else "none", + created=attrs['Created'], + ports=self._parse_port_bindings(attrs['NetworkSettings']['Ports']), + volumes=[v['Source'] for v in attrs['Mounts']], + environment=self._parse_environment(attrs['Config']['Env']), + networks=list(attrs['NetworkSettings']['Networks'].keys()), + health_status=attrs.get('State', {}).get('Health', {}).get('Status'), + restart_policy=attrs['HostConfig']['RestartPolicy']['Name'], + command=attrs['Config']['Cmd'][0] if attrs['Config']['Cmd'] else None + ) + + async def get_container_logs(self, container_id: str, tail: int = 100) -> List[Dict[str, str]]: + """Récupère les logs d'un conteneur.""" + try: + self.ensure_client() + container = self.client.containers.get(container_id) + if not container: + logger.error(f"Conteneur {container_id} non trouvé") + return [] + + logger.info(f"Récupération des logs pour le conteneur {container_id}") + + # Utilisation de l'API Docker pour obtenir les logs + logs = container.logs( + tail=tail, + timestamps=True, + stdout=True, + stderr=True + ) + + if not logs: + logger.warning(f"Aucun log trouvé pour le conteneur {container_id}") + return [] + + decoded_logs = logs.decode('utf-8') + log_lines = [] + + for line in decoded_logs.split('\n'): + if not line.strip(): + continue + + # Format attendu: "2024-04-01T11:10:27.000000000Z message..." + try: + timestamp, message = line.split(' ', 1) + log_lines.append({ + "timestamp": timestamp, + "message": message.strip(), + "stream": "stdout" # Par défaut stdout + }) + except ValueError: + # Si le format n'est pas celui attendu, on garde la ligne complète + log_lines.append({ + "timestamp": "", + "message": line.strip(), + "stream": "stdout" + }) + + logger.info(f"Nombre de lignes de logs trouvées : {len(log_lines)}") + return log_lines + + except docker.errors.NotFound: + logger.error(f"Conteneur {container_id} non trouvé") + return [] + except Exception as e: + logger.error(f"Erreur lors de la récupération des logs du conteneur {container_id}: {e}") + raise + + async def get_container_stats(self, container_id: str) -> ContainerStats: + """Récupère les statistiques d'un conteneur.""" + try: + self.ensure_client() + container = self.client.containers.get(container_id) + stats = container.stats(stream=False) + + logger.debug(f"Stats brutes reçues pour {container_id}: {stats}") + + # Vérifier la présence des clés nécessaires + if not all(key in stats for key in ['cpu_stats', 'precpu_stats', 'memory_stats']): + logger.error(f"Données de stats incomplètes pour le conteneur {container_id}") + raise ValueError("Données de statistiques incomplètes") + + # Calculer le pourcentage CPU avec vérification des valeurs + try: + cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - stats['precpu_stats']['cpu_usage']['total_usage'] + system_delta = stats['cpu_stats']['system_cpu_usage'] - stats['precpu_stats']['system_cpu_usage'] + online_cpus = stats['cpu_stats'].get('online_cpus', 1) # Utiliser 1 comme valeur par défaut + + if system_delta > 0: + cpu_percent = (cpu_delta / system_delta) * 100.0 * online_cpus + else: + cpu_percent = 0.0 + except (KeyError, TypeError) as e: + logger.warning(f"Erreur lors du calcul du CPU pour {container_id}: {e}") + cpu_percent = 0.0 + + # Convertir les statistiques réseau avec gestion des erreurs + networks = {} + for net_name, net_stats in stats.get('networks', {}).items(): + try: + networks[net_name] = NetworkStats( + rx_bytes=net_stats.get('rx_bytes', 0), + rx_packets=net_stats.get('rx_packets', 0), + rx_errors=net_stats.get('rx_errors', 0), + rx_dropped=net_stats.get('rx_dropped', 0), + tx_bytes=net_stats.get('tx_bytes', 0), + tx_packets=net_stats.get('tx_packets', 0), + tx_errors=net_stats.get('tx_errors', 0), + tx_dropped=net_stats.get('tx_dropped', 0) + ) + except Exception as e: + logger.warning(f"Erreur lors de la conversion des stats réseau pour {net_name}: {e}") + continue + + # Récupérer les statistiques de bloc avec gestion des erreurs + try: + io_stats = stats.get('blkio_stats', {}).get('io_service_bytes_recursive', []) + block_read = io_stats[0].get('value', 0) if len(io_stats) > 0 else 0 + block_write = io_stats[1].get('value', 0) if len(io_stats) > 1 else 0 + except (IndexError, KeyError, AttributeError) as e: + logger.warning(f"Erreur lors de la récupération des stats de bloc pour {container_id}: {e}") + block_read = 0 + block_write = 0 + + return ContainerStats( + cpu_percent=cpu_percent, + memory_usage=stats.get('memory_stats', {}).get('usage', 0), + memory_limit=stats.get('memory_stats', {}).get('limit', 0), + network=networks, + block_read=block_read, + block_write=block_write + ) + + except docker.errors.NotFound: + logger.error(f"Conteneur {container_id} non trouvé") + raise + except Exception as e: + logger.error(f"Erreur lors de la récupération des stats du conteneur {container_id}: {e}") + raise + + async def start_container(self, container_id: str) -> None: + """Démarre un conteneur.""" + self.ensure_client() + container = self.client.containers.get(container_id) + container.start() + + async def stop_container(self, container_id: str) -> None: + """Arrête un conteneur.""" + self.ensure_client() + container = self.client.containers.get(container_id) + container.stop() + + async def restart_container(self, container_id: str) -> None: + """Redémarre un conteneur.""" + self.ensure_client() + container = self.client.containers.get(container_id) + container.restart() + + async def update_container(self, container_id: str) -> None: + """Met à jour un conteneur.""" + self.ensure_client() + container = self.client.containers.get(container_id) + container.restart() + + async def follow_container_logs(self, container_id: str) -> AsyncGenerator[Dict[str, str], None]: + """Suit les logs d'un conteneur en temps réel en utilisant l'API Docker Python.""" + try: + self.ensure_client() + container = self.client.containers.get(container_id) + + logger.info(f"Démarrage du suivi des logs pour le conteneur {container_id}") + + # Utiliser l'API Docker Python pour obtenir un flux de logs continu + log_stream = container.logs( + stream=True, + follow=True, + timestamps=True, + stdout=True, + stderr=True, + tail=100 # Commencer avec les 100 derniers logs + ) + + for log in log_stream: + try: + # Décoder la ligne de log + line = log.decode('utf-8').strip() + if not line: + continue + + logger.debug(f"Ligne de log brute reçue: {line}") + + # Format attendu: "2024-04-01T11:10:27.000000000Z message..." + try: + # Trouver le premier espace qui sépare la date du message + space_index = line.find(' ') + if space_index > 0: + timestamp = line[:space_index] + message = line[space_index + 1:].strip() + + logger.debug(f"Timestamp extrait: {timestamp}") + logger.debug(f"Message extrait: {message}") + + # Vérifier si le timestamp est valide (format ISO 8601) + if timestamp.endswith('Z'): + # Convertir le timestamp en format ISO 8601 standard + timestamp = timestamp.replace('Z', '+00:00') + + # Vérifier que le format est valide + if not timestamp.replace('.', '').replace(':', '').replace('-', '').replace('T', '').replace('+', '').isdigit(): + logger.warning(f"Format de timestamp invalide: {timestamp}") + timestamp = "" + else: + logger.warning(f"Timestamp ne se termine pas par Z: {timestamp}") + timestamp = "" + + yield { + "timestamp": timestamp, + "message": message, + "stream": "stdout" + } + else: + # Si pas de timestamp valide, envoyer le message sans timestamp + logger.warning(f"Pas d'espace trouvé dans la ligne: {line}") + yield { + "timestamp": "", + "message": line, + "stream": "stdout" + } + except ValueError as e: + logger.error(f"Erreur lors du parsing de la ligne: {e}") + yield { + "timestamp": "", + "message": line, + "stream": "stdout" + } + + except UnicodeDecodeError: + # Fallback sur latin-1 si UTF-8 échoue + line = log.decode('latin-1').strip() + yield { + "timestamp": "", + "message": line, + "stream": "stdout" + } + except Exception as e: + logger.error(f"Erreur lors du décodage d'un log : {e}") + continue + + except docker.errors.NotFound: + logger.error(f"Conteneur {container_id} non trouvé") + except Exception as e: + logger.error(f"Erreur lors du suivi des logs du conteneur {container_id}: {e}") + raise + finally: + logger.info(f"Arrêt du suivi des logs pour le conteneur {container_id}") + if 'log_stream' in locals(): + try: + log_stream.close() + except: + pass \ No newline at end of file diff --git a/server/app/services/docker_service.py b/server/app/services/docker_service.py new file mode 100644 index 0000000..255f4a6 --- /dev/null +++ b/server/app/services/docker_service.py @@ -0,0 +1,77 @@ +import docker +from typing import List, Dict, Any +from datetime import datetime +from ..models.container import Container, ContainerLog, ContainerStats + +class DockerService: + def __init__(self): + self.client = docker.from_env() + + async def list_containers(self) -> List[Container]: + """Liste tous les conteneurs Docker.""" + containers = self.client.containers.list(all=True) + return [ + Container( + id=container.id, + name=container.name, + image=container.image.tags[0] if container.image.tags else container.image.id, + status=container.status, + ports=container.ports, + created_at=datetime.fromtimestamp(container.attrs['Created']), + updated_at=datetime.now() + ) + for container in containers + ] + + async def get_container_logs(self, container_id: str) -> List[ContainerLog]: + """Récupère les logs d'un conteneur.""" + container = self.client.containers.get(container_id) + logs = container.logs(tail=100, timestamps=True).decode('utf-8').split('\n') + return [ + ContainerLog( + timestamp=datetime.fromisoformat(line.split(' ')[0].replace('T', ' ').replace('Z', '')), + stream=line.split(' ')[1].strip('[]'), + message=' '.join(line.split(' ')[2:]) + ) + for line in logs if line + ] + + async def update_container(self, container_id: str) -> bool: + """Met à jour un conteneur avec la dernière version de son image.""" + try: + container = self.client.containers.get(container_id) + image = container.image + container.stop() + container.remove() + new_container = self.client.containers.run( + image=image.id, + name=container.name, + ports=container.ports, + environment=container.attrs['Config']['Env'], + restart_policy={"Name": container.attrs['HostConfig']['RestartPolicy']['Name']} + ) + return True + except Exception as e: + print(f"Erreur lors de la mise à jour du conteneur : {e}") + return False + + async def get_container_stats(self, container_id: str) -> ContainerStats: + """Récupère les statistiques d'un conteneur.""" + container = self.client.containers.get(container_id) + stats = container.stats(stream=False) + + # Calcul du pourcentage CPU + cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - stats['precpu_stats']['cpu_usage']['total_usage'] + system_delta = stats['cpu_stats']['system_cpu_usage'] - stats['precpu_stats']['system_cpu_usage'] + cpu_percent = (cpu_delta / system_delta) * 100.0 if system_delta > 0 else 0.0 + + return ContainerStats( + cpu_percent=cpu_percent, + memory_usage=stats['memory_stats']['usage'], + memory_limit=stats['memory_stats']['limit'], + network_rx=stats['networks']['eth0']['rx_bytes'], + network_tx=stats['networks']['eth0']['tx_bytes'], + block_read=stats['blkio_stats']['io_service_bytes_recursive'][0]['value'], + block_write=stats['blkio_stats']['io_service_bytes_recursive'][1]['value'], + timestamp=datetime.now() + ) \ No newline at end of file diff --git a/server/app/services/logs_service.py b/server/app/services/logs_service.py new file mode 100644 index 0000000..ada96c6 --- /dev/null +++ b/server/app/services/logs_service.py @@ -0,0 +1,58 @@ +from typing import AsyncGenerator, Dict, List +import docker +import json +from datetime import datetime +from fastapi import WebSocket +from app.core.config import settings + +class LogsService: + def __init__(self): + self.client = docker.from_env() + self.active_connections: Dict[str, List[WebSocket]] = {} + + async def get_container_logs(self, container_id: str, follow: bool = True) -> AsyncGenerator[str, None]: + """Récupère les logs d'un conteneur en temps réel.""" + try: + container = self.client.containers.get(container_id) + for log in container.logs(stream=True, follow=follow, timestamps=True): + log_entry = { + "timestamp": datetime.fromisoformat(log.decode().split()[0].decode()), + "container_id": container_id, + "container_name": container.name, + "message": log.decode().split(b' ', 1)[1].decode(), + "stream": "stdout" if log.decode().split(b' ', 1)[1].decode().startswith("stdout") else "stderr" + } + yield json.dumps(log_entry) + except docker.errors.NotFound: + yield json.dumps({ + "error": f"Conteneur {container_id} non trouvé" + }) + except Exception as e: + yield json.dumps({ + "error": str(e) + }) + + async def connect(self, websocket: WebSocket, container_id: str = None): + """Connecte un client WebSocket pour recevoir les logs.""" + await websocket.accept() + if container_id not in self.active_connections: + self.active_connections[container_id] = [] + self.active_connections[container_id].append(websocket) + + def disconnect(self, websocket: WebSocket, container_id: str = None): + """Déconnecte un client WebSocket.""" + if container_id in self.active_connections: + self.active_connections[container_id].remove(websocket) + if not self.active_connections[container_id]: + del self.active_connections[container_id] + + async def broadcast_log(self, log: str, container_id: str = None): + """Diffuse un log à tous les clients connectés.""" + if container_id in self.active_connections: + for connection in self.active_connections[container_id]: + try: + await connection.send_text(log) + except Exception: + self.disconnect(connection, container_id) + +logs_service = LogsService() \ No newline at end of file diff --git a/server/app/services/redis.py b/server/app/services/redis.py new file mode 100644 index 0000000..743a200 --- /dev/null +++ b/server/app/services/redis.py @@ -0,0 +1,33 @@ +import redis +import json +from typing import List, Dict, Any +from datetime import datetime +from ..models.container import ContainerLog + +class RedisService: + def __init__(self): + self.redis = redis.Redis(host='localhost', port=6379, db=0) + self.logs_key_prefix = "container_logs:" + + async def get_logs(self, container_id: str, limit: int = 100) -> List[ContainerLog]: + """Récupère les logs d'un conteneur depuis Redis.""" + key = f"{self.logs_key_prefix}{container_id}" + logs = self.redis.lrange(key, 0, limit - 1) + return [ContainerLog(**json.loads(log)) for log in logs] + + async def add_log(self, container_id: str, log: ContainerLog) -> None: + """Ajoute un log pour un conteneur dans Redis.""" + key = f"{self.logs_key_prefix}{container_id}" + self.redis.lpush(key, log.model_dump_json()) + # Garder seulement les 1000 derniers logs + self.redis.ltrim(key, 0, 999) + + async def clear_logs(self, container_id: str) -> None: + """Supprime tous les logs d'un conteneur.""" + key = f"{self.logs_key_prefix}{container_id}" + self.redis.delete(key) + + async def get_container_ids(self) -> List[str]: + """Récupère la liste des IDs de conteneurs ayant des logs.""" + keys = self.redis.keys(f"{self.logs_key_prefix}*") + return [key.replace(self.logs_key_prefix, "") for key in keys] \ No newline at end of file diff --git a/server/requirements.txt b/server/requirements.txt new file mode 100644 index 0000000..aacd024 --- /dev/null +++ b/server/requirements.txt @@ -0,0 +1,14 @@ +fastapi>=0.109.0 +uvicorn>=0.27.0 +python-jose[cryptography]>=3.3.0 +passlib[bcrypt]>=1.7.4 +python-multipart>=0.0.6 +docker>=6.1.3 +pydantic>=2.6.0 +python-dotenv>=1.0.0 +redis>=5.0.1 +sqlalchemy>=2.0.23 +websockets>=12.0 +aiohttp>=3.9.1 +pytest>=7.4.3 +httpx>=0.25.2 \ No newline at end of file