diff --git a/assets/components.d.ts b/assets/components.d.ts
index cfd5f52a..2ac014cc 100644
--- a/assets/components.d.ts
+++ b/assets/components.d.ts
@@ -62,6 +62,7 @@ declare module 'vue' {
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
+ 'MaterialSymbols:terminal': typeof import('~icons/material-symbols/terminal')['default']
'Mdi:account': typeof import('~icons/mdi/account')['default']
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
@@ -103,6 +104,7 @@ declare module 'vue' {
'Ph:stackSimple': typeof import('~icons/ph/stack-simple')['default']
Popup: typeof import('./components/Popup.vue')['default']
RandomColorTag: typeof import('./components/LogViewer/RandomColorTag.vue')['default']
+ 'Ri:terminalWindowFill': typeof import('~icons/ri/terminal-window-fill')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
@@ -121,6 +123,7 @@ declare module 'vue' {
StatSparkline: typeof import('./components/LogViewer/StatSparkline.vue')['default']
SwarmMenu: typeof import('./components/SwarmMenu.vue')['default']
Tag: typeof import('./components/common/Tag.vue')['default']
+ Terminal: typeof import('./components/Terminal.vue')['default']
TimedButton: typeof import('./components/common/TimedButton.vue')['default']
ToastModal: typeof import('./components/common/ToastModal.vue')['default']
Toggle: typeof import('./components/common/Toggle.vue')['default']
diff --git a/assets/components/ContainerViewer/ContainerActionsToolbar.vue b/assets/components/ContainerViewer/ContainerActionsToolbar.vue
index df136bb4..c00e8910 100644
--- a/assets/components/ContainerViewer/ContainerActionsToolbar.vue
+++ b/assets/components/ContainerViewer/ContainerActionsToolbar.vue
@@ -134,6 +134,20 @@
+
+
+
+
+ Attach
+
+
+
+
+
+ Shell
+
+
+
@@ -142,6 +156,7 @@
import { Container } from "@/models/Container";
import { allLevels } from "@/composable/logContext";
import LogAnalytics from "../LogViewer/LogAnalytics.vue";
+import Terminal from "@/components/Terminal.vue";
const { showSearch } = useSearchFilter();
const { enableActions } = config;
@@ -161,6 +176,20 @@ onKeyStroke("f", (e) => {
}
});
+onKeyStroke("a", (e) => {
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
+ showDrawer(Terminal, { container, action: "attach" }, "lg");
+ e.preventDefault();
+ }
+});
+
+onKeyStroke("e", (e) => {
+ if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
+ showDrawer(Terminal, { container, action: "exec" }, "lg");
+ e.preventDefault();
+ }
+});
+
const downloadParams = computed(() =>
Object.entries(toValue(streamConfig))
.filter(([, value]) => value)
diff --git a/assets/components/Terminal.vue b/assets/components/Terminal.vue
new file mode 100644
index 00000000..6b81fd8d
--- /dev/null
+++ b/assets/components/Terminal.vue
@@ -0,0 +1,79 @@
+
+
+
+
+
+
diff --git a/go.mod b/go.mod
index 2842c1ec..fe11be86 100644
--- a/go.mod
+++ b/go.mod
@@ -66,6 +66,7 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
+ github.com/gorilla/websocket v1.5.3 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
diff --git a/go.sum b/go.sum
index 88d94722..46de12e0 100644
--- a/go.sum
+++ b/go.sum
@@ -90,6 +90,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
diff --git a/internal/agent/client.go b/internal/agent/client.go
index 730b93ed..8ec7188e 100644
--- a/internal/agent/client.go
+++ b/internal/agent/client.go
@@ -395,6 +395,14 @@ func (c *Client) ContainerAction(ctx context.Context, containerId string, action
return err
}
+func (c *Client) ContainerAttach(ctx context.Context, containerId string) (io.WriteCloser, io.Reader, error) {
+ panic("not implemented")
+}
+
+func (c *Client) ContainerExec(ctx context.Context, containerId string, cmd []string) (io.WriteCloser, io.Reader, error) {
+ panic("not implemented")
+}
+
func (c *Client) Close() error {
return c.conn.Close()
}
diff --git a/internal/container/client.go b/internal/container/client.go
index ab56b8e4..8b43e8d2 100644
--- a/internal/container/client.go
+++ b/internal/container/client.go
@@ -38,4 +38,6 @@ type Client interface {
Ping(context.Context) error
Host() Host
ContainerActions(ctx context.Context, action ContainerAction, containerID string) error
+ ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error)
+ ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error)
}
diff --git a/internal/docker/client.go b/internal/docker/client.go
index 22583803..2dac4b4a 100644
--- a/internal/docker/client.go
+++ b/internal/docker/client.go
@@ -33,6 +33,10 @@ type DockerCLI interface {
ContainerStart(ctx context.Context, containerID string, options docker.StartOptions) error
ContainerStop(ctx context.Context, containerID string, options docker.StopOptions) error
ContainerRestart(ctx context.Context, containerID string, options docker.StopOptions) error
+ ContainerAttach(ctx context.Context, containerID string, options docker.AttachOptions) (types.HijackedResponse, error)
+ ContainerExecCreate(ctx context.Context, containerID string, options docker.ExecOptions) (docker.ExecCreateResponse, error)
+ ContainerExecAttach(ctx context.Context, execID string, config docker.ExecAttachOptions) (types.HijackedResponse, error)
+ ContainerExecResize(ctx context.Context, execID string, options docker.ResizeOptions) error
Info(ctx context.Context) (system.Info, error)
}
@@ -297,6 +301,54 @@ func (d *DockerClient) Host() container.Host {
return d.host
}
+func (d *DockerClient) ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error) {
+ log.Debug().Str("id", id).Str("host", d.host.Name).Msg("Attaching to container")
+ options := docker.AttachOptions{
+ Stream: true,
+ Stdin: true,
+ Stdout: true,
+ Stderr: true,
+ }
+
+ waiter, err := d.cli.ContainerAttach(ctx, id, options)
+
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return waiter.Conn, waiter.Reader, nil
+}
+
+func (d *DockerClient) ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error) {
+ log.Debug().Str("id", id).Str("host", d.host.Name).Msg("Executing command in container")
+ options := docker.ExecOptions{
+ AttachStdout: true,
+ AttachStderr: true,
+ AttachStdin: true,
+ Cmd: cmd,
+ Tty: true,
+ }
+
+ execID, err := d.cli.ContainerExecCreate(ctx, id, options)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ waiter, err := d.cli.ContainerExecAttach(ctx, execID.ID, docker.ExecAttachOptions{})
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if err = d.cli.ContainerExecResize(ctx, execID.ID, docker.ResizeOptions{
+ Width: 100,
+ Height: 40,
+ }); err != nil {
+ return nil, nil, err
+ }
+
+ return waiter.Conn, waiter.Reader, nil
+}
+
func newContainer(c docker.Summary, host string) container.Container {
name := "no name"
if c.Labels["dev.dozzle.name"] != "" {
diff --git a/internal/k8s/client.go b/internal/k8s/client.go
index 974f59f6..ddbaa8b9 100644
--- a/internal/k8s/client.go
+++ b/internal/k8s/client.go
@@ -241,8 +241,15 @@ func (k *K8sClient) Host() container.Host {
}
func (k *K8sClient) ContainerActions(ctx context.Context, action container.ContainerAction, containerID string) error {
- // Implementation for container actions (start, stop, restart, etc.)
- return nil
+ panic("not implemented")
+}
+
+func (k *K8sClient) ContainerAttach(ctx context.Context, id string) (io.WriteCloser, io.Reader, error) {
+ panic("not implemented")
+}
+
+func (k *K8sClient) ContainerExec(ctx context.Context, id string, cmd []string) (io.WriteCloser, io.Reader, error) {
+ panic("not implemented")
}
// Helper function to parse pod and container names from container ID
diff --git a/internal/support/container/agent_service.go b/internal/support/container/agent_service.go
index a7a0c8b2..57b9b875 100644
--- a/internal/support/container/agent_service.go
+++ b/internal/support/container/agent_service.go
@@ -70,3 +70,11 @@ func (d *agentService) SubscribeContainersStarted(ctx context.Context, container
func (a *agentService) ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error {
return a.client.ContainerAction(ctx, container.ID, action)
}
+
+func (a *agentService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
+ panic("not implemented")
+}
+
+func (a *agentService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
+ panic("not implemented")
+}
diff --git a/internal/support/container/client_service.go b/internal/support/container/client_service.go
index ae1bcc94..77e244b5 100644
--- a/internal/support/container/client_service.go
+++ b/internal/support/container/client_service.go
@@ -16,13 +16,17 @@ type ClientService interface {
Host(ctx context.Context) (container.Host, error)
ContainerAction(ctx context.Context, container container.Container, action container.ContainerAction) error
LogsBetweenDates(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (<-chan *container.LogEvent, error)
- RawLogs(ctx context.Context, container container.Container, from time.Time, to time.Time, stdTypes container.StdType) (io.ReadCloser, error)
+ RawLogs(context.Context, container.Container, time.Time, time.Time, container.StdType) (io.ReadCloser, error)
// Subscriptions
- SubscribeStats(ctx context.Context, stats chan<- container.ContainerStat)
- SubscribeEvents(ctx context.Context, events chan<- container.ContainerEvent)
- SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container)
+ SubscribeStats(context.Context, chan<- container.ContainerStat)
+ SubscribeEvents(context.Context, chan<- container.ContainerEvent)
+ SubscribeContainersStarted(context.Context, chan<- container.Container)
// Blocking streaming functions that should be used in a goroutine
- StreamLogs(ctx context.Context, container container.Container, from time.Time, stdTypes container.StdType, events chan<- *container.LogEvent) error
+ StreamLogs(context.Context, container.Container, time.Time, container.StdType, chan<- *container.LogEvent) error
+
+ // Terminal
+ Attach(context.Context, container.Container, io.Reader, io.Writer) error
+ Exec(context.Context, container.Container, []string, io.Reader, io.Writer) error
}
diff --git a/internal/support/container/container_service.go b/internal/support/container/container_service.go
index 4e97e06e..07a97760 100644
--- a/internal/support/container/container_service.go
+++ b/internal/support/container/container_service.go
@@ -35,3 +35,11 @@ func (c *ContainerService) StreamLogs(ctx context.Context, from time.Time, stdTy
func (c *ContainerService) Action(ctx context.Context, action container.ContainerAction) error {
return c.clientService.ContainerAction(ctx, c.Container, action)
}
+
+func (c *ContainerService) Attach(ctx context.Context, stdin io.Reader, stdout io.Writer) error {
+ return c.clientService.Attach(ctx, c.Container, stdin, stdout)
+}
+
+func (c *ContainerService) Exec(ctx context.Context, cmd []string, stdin io.Reader, stdout io.Writer) error {
+ return c.clientService.Exec(ctx, c.Container, cmd, stdin, stdout)
+}
diff --git a/internal/support/docker/docker_service.go b/internal/support/docker/docker_service.go
index 9952a13a..970f862a 100644
--- a/internal/support/docker/docker_service.go
+++ b/internal/support/docker/docker_service.go
@@ -3,6 +3,7 @@ package docker_support
import (
"context"
"io"
+ "sync"
"time"
"github.com/amir20/dozzle/internal/container"
@@ -109,3 +110,75 @@ func (d *DockerClientService) SubscribeEvents(ctx context.Context, events chan<-
func (d *DockerClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
d.store.SubscribeNewContainers(ctx, containers)
}
+
+func (d *DockerClientService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
+ cancelCtx, cancel := context.WithCancel(ctx)
+ containerWriter, containerReader, err := d.client.ContainerAttach(cancelCtx, container.ID)
+ if err != nil {
+ cancel()
+ return err
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+ if _, err := io.Copy(containerWriter, stdin); err != nil {
+ log.Error().Err(err).Msg("error while reading from ws")
+ }
+ cancel()
+ containerWriter.Close()
+ }()
+
+ go func() {
+ defer wg.Done()
+ if container.Tty {
+ if _, err := io.Copy(stdout, containerReader); err != nil {
+ log.Error().Err(err).Msg("error while writing to ws")
+ }
+ } else {
+ if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
+ log.Error().Err(err).Msg("error while writing to ws")
+ }
+ }
+ cancel()
+ }()
+
+ wg.Wait()
+
+ return nil
+}
+
+func (d *DockerClientService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
+ cancelCtx, cancel := context.WithCancel(ctx)
+ containerWriter, containerReader, err := d.client.ContainerExec(cancelCtx, container.ID, cmd)
+ if err != nil {
+ cancel()
+ return err
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ go func() {
+ defer wg.Done()
+ if _, err := io.Copy(containerWriter, stdin); err != nil {
+ log.Error().Err(err).Msg("error while reading from ws")
+ }
+ cancel()
+ containerWriter.Close()
+ }()
+
+ go func() {
+ defer wg.Done()
+ if _, err := stdcopy.StdCopy(stdout, stdout, containerReader); err != nil {
+ log.Error().Err(err).Msg("error while writing to ws")
+ }
+ cancel()
+ }()
+
+ wg.Wait()
+
+ return nil
+}
diff --git a/internal/support/k8s/k8s_service.go b/internal/support/k8s/k8s_service.go
index 5cc54266..3efcebb6 100644
--- a/internal/support/k8s/k8s_service.go
+++ b/internal/support/k8s/k8s_service.go
@@ -90,3 +90,11 @@ func (k *K8sClientService) SubscribeEvents(ctx context.Context, events chan<- co
func (k *K8sClientService) SubscribeContainersStarted(ctx context.Context, containers chan<- container.Container) {
k.store.SubscribeNewContainers(ctx, containers)
}
+
+func (k *K8sClientService) Attach(ctx context.Context, container container.Container, stdin io.Reader, stdout io.Writer) error {
+ panic("not implemented")
+}
+
+func (k *K8sClientService) Exec(ctx context.Context, container container.Container, cmd []string, stdin io.Reader, stdout io.Writer) error {
+ panic("not implemented")
+}
diff --git a/internal/web/routes.go b/internal/web/routes.go
index 70ccc669..8ce7b83c 100644
--- a/internal/web/routes.go
+++ b/internal/web/routes.go
@@ -107,6 +107,8 @@ func createRouter(h *handler) *chi.Mux {
r.Get("/hosts/{host}/containers/{id}/logs/stream", h.streamContainerLogs)
r.Get("/hosts/{host}/logs/stream", h.streamHostLogs)
r.Get("/hosts/{host}/containers/{id}/logs", h.fetchLogsBetweenDates)
+ r.Get("/hosts/{host}/containers/{id}/attach", h.attach)
+ r.Get("/hosts/{host}/containers/{id}/exec", h.exec)
r.Get("/hosts/{host}/logs/mergedStream/{ids}", h.streamLogsMerged)
r.Get("/containers/{hostIds}/download", h.downloadLogs) // formatted as host:container,host:container
r.Get("/stacks/{stack}/logs/stream", h.streamStackLogs)
diff --git a/internal/web/terminal.go b/internal/web/terminal.go
new file mode 100644
index 00000000..67c6232c
--- /dev/null
+++ b/internal/web/terminal.go
@@ -0,0 +1,120 @@
+package web
+
+import (
+ "net/http"
+
+ "github.com/amir20/dozzle/internal/auth"
+ "github.com/go-chi/chi/v5"
+ "github.com/gorilla/websocket"
+ "github.com/rs/zerolog/log"
+)
+
+var upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ CheckOrigin: func(r *http.Request) bool {
+ return true
+ },
+}
+
+func (h *handler) attach(w http.ResponseWriter, r *http.Request) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Error().Err(err).Msg("error while trying to upgrade connection")
+ return
+ }
+ defer conn.Close()
+
+ id := chi.URLParam(r, "id")
+ userLabels := h.config.Labels
+ if h.config.Authorization.Provider != NONE {
+ user := auth.UserFromContext(r.Context())
+ if user.ContainerLabels.Exists() {
+ userLabels = user.ContainerLabels
+ }
+ }
+
+ containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
+
+ if err != nil {
+ log.Error().Err(err).Msg("error while trying to find container")
+ return
+ }
+
+ wsReader := &webSocketReader{conn: conn}
+ wsWriter := &webSocketWriter{conn: conn}
+ if err = containerService.Attach(r.Context(), wsReader, wsWriter); err != nil {
+ log.Error().Err(err).Msg("error while trying to attach to container")
+ conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
+ return
+ }
+}
+
+func (h *handler) exec(w http.ResponseWriter, r *http.Request) {
+ conn, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Error().Err(err).Msg("error while trying to upgrade connection")
+ return
+ }
+ defer conn.Close()
+
+ id := chi.URLParam(r, "id")
+ userLabels := h.config.Labels
+ if h.config.Authorization.Provider != NONE {
+ user := auth.UserFromContext(r.Context())
+ if user.ContainerLabels.Exists() {
+ userLabels = user.ContainerLabels
+ }
+ }
+
+ containerService, err := h.hostService.FindContainer(hostKey(r), id, userLabels)
+ if err != nil {
+ log.Error().Err(err).Msg("error while trying to find container")
+ return
+ }
+
+ wsReader := &webSocketReader{conn: conn}
+ wsWriter := &webSocketWriter{conn: conn}
+ if err = containerService.Exec(r.Context(), []string{"sh", "-c", "command -v bash >/dev/null 2>&1 && exec bash || exec sh"}, wsReader, wsWriter); err != nil {
+ log.Error().Err(err).Msg("error while trying to attach to container")
+ conn.WriteMessage(websocket.TextMessage, []byte("🚨 Error while trying to attach to container\r\n"))
+ return
+ }
+}
+
+type webSocketWriter struct {
+ conn *websocket.Conn
+}
+
+func (w *webSocketWriter) Write(p []byte) (int, error) {
+ err := w.conn.WriteMessage(websocket.TextMessage, p)
+ return len(p), err
+}
+
+type webSocketReader struct {
+ conn *websocket.Conn
+ buffer []byte
+}
+
+func (r *webSocketReader) Read(p []byte) (n int, err error) {
+ if len(r.buffer) > 0 {
+ n = copy(p, r.buffer)
+ r.buffer = r.buffer[n:]
+ return n, nil
+ }
+
+ // Otherwise, read a new message
+ _, message, err := r.conn.ReadMessage()
+ if err != nil {
+ return 0, err
+ }
+
+ n = copy(p, message)
+
+ // If we couldn't copy the entire message, store the rest in our buffer
+ if n < len(message) {
+ r.buffer = message[n:]
+ }
+
+ return n, nil
+}
diff --git a/package.json b/package.json
index 412f4c38..06654283 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,8 @@
"@vueuse/core": "^13.0.0",
"@vueuse/integrations": "^13.0.0",
"@vueuse/router": "^13.0.0",
+ "@xterm/addon-web-links": "^0.11.0",
+ "@xterm/xterm": "^5.5.0",
"ansi-to-html": "^0.7.2",
"d3-array": "^3.2.4",
"d3-ease": "^3.0.1",
@@ -79,6 +81,8 @@
},
"devDependencies": {
"@apache-arrow/esnext-esm": "^19.0.1",
+ "@iconify-json/material-symbols-light": "^1.2.17",
+ "@iconify-json/ri": "^1.2.5",
"@pinia/testing": "^1.0.0",
"@playwright/test": "^1.51.1",
"@types/d3-array": "^3.2.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f1088985..ff754727 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -59,6 +59,12 @@ importers:
'@vueuse/router':
specifier: ^13.0.0
version: 13.0.0(vue-router@4.5.0(vue@3.5.13(typescript@5.8.2)))(vue@3.5.13(typescript@5.8.2))
+ '@xterm/addon-web-links':
+ specifier: ^0.11.0
+ version: 0.11.0(@xterm/xterm@5.5.0)
+ '@xterm/xterm':
+ specifier: ^5.5.0
+ version: 5.5.0
ansi-to-html:
specifier: ^0.7.2
version: 0.7.2
@@ -153,6 +159,12 @@ importers:
'@apache-arrow/esnext-esm':
specifier: ^19.0.1
version: 19.0.1
+ '@iconify-json/material-symbols-light':
+ specifier: ^1.2.17
+ version: 1.2.17
+ '@iconify-json/ri':
+ specifier: ^1.2.5
+ version: 1.2.5
'@pinia/testing':
specifier: ^1.0.0
version: 1.0.0(pinia@3.0.1(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
@@ -939,6 +951,9 @@ packages:
'@iconify-json/ic@1.2.2':
resolution: {integrity: sha512-QmjwS3lYiOmVWgTCEOTFyGODaR/+689+ajep/VsrCcsUN0Gdle5PmIcibDsdmRyrOsW/E77G41UUijdbjQUofw==}
+ '@iconify-json/material-symbols-light@1.2.17':
+ resolution: {integrity: sha512-sq8pSITGy15SjJ6FuOr/oH/8emJPcukdSJJeuL9YHZ44KKQD0ITL7ZO44TAQxdOsef0ptZbhPWxSwCqK7ytrxQ==}
+
'@iconify-json/material-symbols@1.2.17':
resolution: {integrity: sha512-hKb+Ii5cqLXXefYMxUB2jIc8BNqxixQogud4KU/fn0F4puM1iCdCF2lFV+0U8wnJ6dZIx6E+w8Ree4bIT7To+A==}
@@ -954,6 +969,9 @@ packages:
'@iconify-json/ph@1.2.2':
resolution: {integrity: sha512-PgkEZNtqa8hBGjHXQa4pMwZa93hmfu8FUSjs/nv4oUU6yLsgv+gh9nu28Kqi8Fz9CCVu4hj1MZs9/60J57IzFw==}
+ '@iconify-json/ri@1.2.5':
+ resolution: {integrity: sha512-kWGimOXMZrlYusjBKKXYOWcKhbOHusFsmrmRGmjS7rH0BpML5A9/fy8KHZqFOwZfC4M6amObQYbh8BqO5cMC3w==}
+
'@iconify-json/simple-icons@1.2.23':
resolution: {integrity: sha512-ySyZ0ZXdNveWnR71t7XGV7jhknxSlTtpM2TyIR1cUHTUzZLP36hYHTNqb2pYYsCzH5ed85KTTKz7vYT33FyNIQ==}
@@ -1882,6 +1900,14 @@ packages:
peerDependencies:
vue: ^3.5.0
+ '@xterm/addon-web-links@0.11.0':
+ resolution: {integrity: sha512-nIHQ38pQI+a5kXnRaTgwqSHnX7KE6+4SVoceompgHL26unAxdfP6IPqUTSYPQgSwM56hsElfoNrrW5V7BUED/Q==}
+ peerDependencies:
+ '@xterm/xterm': ^5.0.0
+
+ '@xterm/xterm@5.5.0':
+ resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
+
abbrev@2.0.0:
resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -4596,6 +4622,10 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
+ '@iconify-json/material-symbols-light@1.2.17':
+ dependencies:
+ '@iconify/types': 2.0.0
+
'@iconify-json/material-symbols@1.2.17':
dependencies:
'@iconify/types': 2.0.0
@@ -4616,6 +4646,10 @@ snapshots:
dependencies:
'@iconify/types': 2.0.0
+ '@iconify-json/ri@1.2.5':
+ dependencies:
+ '@iconify/types': 2.0.0
+
'@iconify-json/simple-icons@1.2.23':
dependencies:
'@iconify/types': 2.0.0
@@ -5609,6 +5643,12 @@ snapshots:
dependencies:
vue: 3.5.13(typescript@5.8.2)
+ '@xterm/addon-web-links@0.11.0(@xterm/xterm@5.5.0)':
+ dependencies:
+ '@xterm/xterm': 5.5.0
+
+ '@xterm/xterm@5.5.0': {}
+
abbrev@2.0.0: {}
acorn-jsx@5.3.2(acorn@8.14.1):