diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 461fe61b..912f7e13 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: 1.26.0 + go-version: 1.26.1 check-latest: true - name: Checkout code uses: actions/checkout@v6 @@ -63,13 +63,13 @@ jobs: - name: Install dependencies run: pnpm install - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Writing certs to file run: | echo "${{ secrets.TTL_KEY }}" > shared_key.pem echo "${{ secrets.TTL_CERT }}" > shared_cert.pem - name: Build - uses: docker/bake-action@v6 + uses: docker/bake-action@v7 with: source: . load: true @@ -93,14 +93,14 @@ jobs: packages: write steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Login to DockerHub - uses: docker/login-action@v3.7.0 + uses: docker/login-action@v4.0.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the Container registry - uses: docker/login-action@v3.7.0 + uses: docker/login-action@v4.0.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -109,7 +109,7 @@ jobs: uses: actions/checkout@v6 - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | amir20/dozzle @@ -124,7 +124,7 @@ jobs: echo "${{ secrets.TTL_KEY }}" > shared_key.pem echo "${{ secrets.TTL_CERT }}" > shared_cert.pem - name: Build and push - uses: docker/build-push-action@v6.19.2 + uses: docker/build-push-action@v7.0.0 with: push: true context: . diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 082c636d..8cd85711 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -18,14 +18,14 @@ jobs: if: ${{ !github.event.repository.fork && !github.event.pull_request.head.repo.fork && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == 'amir20/dozzle') }} steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Login to DockerHub - uses: docker/login-action@v3.7.0 + uses: docker/login-action@v4.0.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Log in to the Container registry - uses: docker/login-action@v3.7.0 + uses: docker/login-action@v4.0.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v6 - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: | amir20/dozzle @@ -44,7 +44,7 @@ jobs: echo "${{ secrets.TTL_KEY }}" > shared_key.pem echo "${{ secrets.TTL_CERT }}" > shared_cert.pem - name: Build and push - uses: docker/build-push-action@v6.19.2 + uses: docker/build-push-action@v7.0.0 with: context: . push: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ceb3fb65..126b4590 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,7 @@ jobs: - name: Install Go uses: actions/setup-go@v6 with: - go-version: "1.26.0" + go-version: "1.26.1" check-latest: true - name: Checkout code uses: actions/checkout@v6 @@ -81,12 +81,12 @@ jobs: - name: Setup Go uses: actions/setup-go@v6 with: - go-version: "1.26.0" + go-version: "1.26.1" check-latest: true - name: Generate dependencies run: make fake_assets shared_key.pem shared_cert.pem - name: Stactic checker - uses: dominikh/staticcheck-action@v1.4.0 + uses: dominikh/staticcheck-action@v1.4.1 with: install-go: false int-test: @@ -111,11 +111,11 @@ jobs: - name: Install dependencies run: pnpm install - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Generate certs run: make shared_key.pem shared_cert.pem - name: Build - uses: docker/bake-action@v6 + uses: docker/bake-action@v7 with: source: . load: true diff --git a/Dockerfile b/Dockerfile index cbf2e7dd..2d8ff178 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build assets -FROM --platform=$BUILDPLATFORM node:25.7.0-alpine AS node +FROM --platform=$BUILDPLATFORM node:25.8.0-alpine AS node RUN npm install -g --force corepack && corepack enable diff --git a/assets/components.d.ts b/assets/components.d.ts index e3fdba68..8572cf58 100644 --- a/assets/components.d.ts +++ b/assets/components.d.ts @@ -15,8 +15,10 @@ declare module 'vue' { AlertForm: typeof import('./components/Notification/AlertForm.vue')['default'] Announcements: typeof import('./components/Announcements.vue')['default'] BarChart: typeof import('./components/BarChart.vue')['default'] + 'Carbon:add': typeof import('~icons/carbon/add')['default'] 'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default'] 'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default'] + 'Carbon:close': typeof import('~icons/carbon/close')['default'] 'Carbon:information': typeof import('~icons/carbon/information')['default'] 'Carbon:logoKubernetes': typeof import('~icons/carbon/logo-kubernetes')['default'] 'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default'] @@ -59,7 +61,6 @@ declare module 'vue' { HostList: typeof import('./components/HostList.vue')['default'] HostLog: typeof import('./components/HostViewer/HostLog.vue')['default'] HostMenu: typeof import('./components/HostMenu.vue')['default'] - 'Ic:baselineFiberNew': typeof import('~icons/ic/baseline-fiber-new')['default'] 'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default'] IndeterminateBar: typeof import('./components/common/IndeterminateBar.vue')['default'] 'Ion:ellipsisVertical': typeof import('~icons/ion/ellipsis-vertical')['default'] @@ -107,7 +108,6 @@ declare module 'vue' { 'Mdi:cloudOutline': typeof import('~icons/mdi/cloud-outline')['default'] 'Mdi:cog': typeof import('~icons/mdi/cog')['default'] 'Mdi:contentCopy': typeof import('~icons/mdi/content-copy')['default'] - 'Mdi:creation': typeof import('~icons/mdi/creation')['default'] 'Mdi:docker': typeof import('~icons/mdi/docker')['default'] 'Mdi:gauge': typeof import('~icons/mdi/gauge')['default'] 'Mdi:github': typeof import('~icons/mdi/github')['default'] diff --git a/assets/components/Notification/WebhookDestinationForm.vue b/assets/components/Notification/WebhookDestinationForm.vue index 5e58d02d..57cce12b 100644 --- a/assets/components/Notification/WebhookDestinationForm.vue +++ b/assets/components/Notification/WebhookDestinationForm.vue @@ -57,6 +57,43 @@ > + +
+ + {{ $t("notifications.destination-form.headers") }} + {{ + $t("notifications.destination-form.headers-hint") + }} + +
+
+ + + +
+ +
+
+
{{ error }} @@ -110,6 +147,12 @@ useFocus(nameInput, { initialValue: true }); const webhookUrl = ref(destination?.url ?? ""); const payloadFormat = ref(isEditing ? "custom" : "slack"); const template = ref(isEditing ? (destination?.template ?? "") : PAYLOAD_TEMPLATES[payloadFormat.value]); +let headerKeyCounter = 0; +const headers = ref<{ name: string; value: string; key: number }[]>( + destination?.headers + ? Object.entries(destination.headers).map(([name, value]) => ({ name, value, key: headerKeyCounter++ })) + : [], +); const isTesting = ref(false); const isSaving = ref(false); const error = ref(null); @@ -143,6 +186,12 @@ onScopeDispose(() => { templateEditorView?.destroy(); }); +function headersToRecord(): Record | undefined { + const filtered = headers.value.filter((h) => h.name.trim() && h.value.trim()); + if (filtered.length === 0) return undefined; + return Object.fromEntries(filtered.map((h) => [h.name.trim(), h.value.trim()])); +} + const canTest = computed(() => webhookUrl.value.trim().length > 0); const isValidUrl = computed(() => { @@ -174,6 +223,7 @@ async function testDestination() { body: JSON.stringify({ url: webhookUrl.value.trim(), template: template.value.trim() || undefined, + headers: headersToRecord(), }), }); @@ -198,6 +248,7 @@ async function saveDestination() { type: "webhook", url: webhookUrl.value.trim(), template: template.value.trim() || undefined, + headers: headersToRecord(), }; const url = isEditing diff --git a/assets/types/notifications.ts b/assets/types/notifications.ts index 8283272d..37c7381d 100644 --- a/assets/types/notifications.ts +++ b/assets/types/notifications.ts @@ -19,6 +19,7 @@ export interface Dispatcher { type: string; url?: string; template?: string; + headers?: Record; prefix?: string; expiresAt?: string; } diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index b1b722e2..09af81eb 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -16,7 +16,7 @@ The easiest way to set up Dozzle is to use the CLI and mount `docker.sock` file. ::: code-group ```sh -docker run -d -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle:latest +docker run -d -v /var/run/docker.sock:/var/run/docker.sock -v dozzle_data:/data -p 8080:8080 amir20/dozzle:latest ``` ```yaml [docker-compose.yml] @@ -26,6 +26,7 @@ services: image: amir20/dozzle:latest volumes: - /var/run/docker.sock:/var/run/docker.sock + - dozzle_data:/data ports: - 8080:8080 environment: @@ -37,6 +38,8 @@ services: # # Uncomment to enable authentication. See https://dozzle.dev/guide/authentication # - DOZZLE_AUTH_PROVIDER=simple +volumes: + dozzle_data: ``` ::: @@ -45,6 +48,9 @@ services: > Dozzle supports actions, such as stopping, starting, and restarting containers, or attaching to container shells. But they are disabled by default for security reasons. To enable them, uncomment the corresponding environment variables. > Dozzle also supports connecting to remote agents to monitor multiple Docker hosts. See [agent](/guide/agent) to learn more. +> [!IMPORTANT] +> Dozzle stores notification settings and other data in `/data` inside the container. To persist these settings across container restarts, you need to mount a volume to `/data`. Without this mount, notification settings will be lost when the container is recreated. See the Docker Compose example above for the recommended volume configuration. + ## Docker Swarm Dozzle supports running in Swarm mode by deploying it on every node. To run Dozzle in Swarm mode, you can use the following configuration: diff --git a/docs/guide/k8s.md b/docs/guide/k8s.md index 9fa405a8..9a2712be 100644 --- a/docs/guide/k8s.md +++ b/docs/guide/k8s.md @@ -84,6 +84,8 @@ spec: ``` This configuration creates a service account, a cluster role, and a cluster role binding to allow Dozzle to access the necessary Kubernetes resources. It also creates a deployment for Dozzle and exposes it via a service. +> [!WARNING] +> When deploying this with any GitOps tool (like Flux CD or Argo CD) in a specific namespace apart from `default`, make sure to change the **namespace** in the **ClusterRoleBinding Subject** All other features are supported as well, including authentication, filtering, and more. You can use the same environment variables as you would in Docker to configure Dozzle in Kubernetes. diff --git a/go.mod b/go.mod index 90cf93b1..41e15465 100644 --- a/go.mod +++ b/go.mod @@ -36,8 +36,8 @@ require ( github.com/yuin/goldmark v1.7.16 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/crypto v0.48.0 - golang.org/x/sync v0.19.0 - google.golang.org/grpc v1.79.1 + golang.org/x/sync v0.20.0 + google.golang.org/grpc v1.79.2 google.golang.org/protobuf v1.36.11 k8s.io/api v0.35.2 k8s.io/apimachinery v0.35.2 @@ -138,7 +138,7 @@ require ( sigs.k8s.io/yaml v1.6.0 // indirect ) -go 1.26.0 +go 1.26.1 tool ( github.com/air-verse/air diff --git a/go.sum b/go.sum index 56447c55..9ab1d437 100644 --- a/go.sum +++ b/go.sum @@ -446,6 +446,8 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -516,6 +518,8 @@ google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0 h1:6Al3kEFFP9VJhRz3DID6quisgPnTeZVr4lep9kkxdPA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.0/go.mod h1:QLvsjh0OIR0TYBeiu2bkWGTJBUNQ64st52iWj/yA93I= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.1/go.mod h1:YNKnb2OAApgYn2oYY47Rn7alMr1zWjb2U8Q0aoGWiNc= diff --git a/internal/agent/client.go b/internal/agent/client.go index 4cbf2f4e..d531f7cf 100644 --- a/internal/agent/client.go +++ b/internal/agent/client.go @@ -529,6 +529,7 @@ func (c *Client) UpdateNotificationConfig(ctx context.Context, subscriptions []t Type: d.Type, Url: d.URL, Template: d.Template, + Headers: d.Headers, } } diff --git a/internal/agent/pb/rpc.pb.go b/internal/agent/pb/rpc.pb.go index eebdb59d..5f461580 100644 --- a/internal/agent/pb/rpc.pb.go +++ b/internal/agent/pb/rpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.4 +// protoc v7.34.0 // source: rpc.proto package pb diff --git a/internal/agent/pb/rpc_grpc.pb.go b/internal/agent/pb/rpc_grpc.pb.go index 7a3f8f20..4eb8dad6 100644 --- a/internal/agent/pb/rpc_grpc.pb.go +++ b/internal/agent/pb/rpc_grpc.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.6.0 -// - protoc v6.33.4 +// - protoc v7.34.0 // source: rpc.proto package pb diff --git a/internal/agent/pb/types.pb.go b/internal/agent/pb/types.pb.go index c72ae8df..f19c8088 100644 --- a/internal/agent/pb/types.pb.go +++ b/internal/agent/pb/types.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.4 +// protoc v7.34.0 // source: types.proto package pb @@ -935,6 +935,7 @@ type NotificationDispatcher struct { Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"` Template string `protobuf:"bytes,5,opt,name=template,proto3" json:"template,omitempty"` + Headers map[string]string `protobuf:"bytes,6,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1004,6 +1005,13 @@ func (x *NotificationDispatcher) GetTemplate() string { return "" } +func (x *NotificationDispatcher) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + var File_types_proto protoreflect.FileDescriptor const file_types_proto_rawDesc = "" + @@ -1092,13 +1100,17 @@ const file_types_proto_rawDesc = "" + "\x13containerExpression\x18\x06 \x01(\tR\x13containerExpression\x12*\n" + "\x10metricExpression\x18\a \x01(\tR\x10metricExpression\x12\x1a\n" + "\bcooldown\x18\b \x01(\x05R\bcooldown\x12\"\n" + - "\fsampleWindow\x18\t \x01(\x05R\fsampleWindow\"~\n" + + "\fsampleWindow\x18\t \x01(\x05R\fsampleWindow\"\x83\x02\n" + "\x16NotificationDispatcher\x12\x0e\n" + "\x02id\x18\x01 \x01(\x05R\x02id\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + "\x04type\x18\x03 \x01(\tR\x04type\x12\x10\n" + "\x03url\x18\x04 \x01(\tR\x03url\x12\x1a\n" + - "\btemplate\x18\x05 \x01(\tR\btemplate*3\n" + + "\btemplate\x18\x05 \x01(\tR\btemplate\x12G\n" + + "\aheaders\x18\x06 \x03(\v2-.protobuf.NotificationDispatcher.HeadersEntryR\aheaders\x1a:\n" + + "\fHeadersEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*3\n" + "\x0fContainerAction\x12\t\n" + "\x05Start\x10\x00\x12\b\n" + "\x04Stop\x10\x01\x12\v\n" + @@ -1117,7 +1129,7 @@ func file_types_proto_rawDescGZIP() []byte { } var file_types_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_types_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_types_proto_goTypes = []any{ (ContainerAction)(0), // 0: protobuf.ContainerAction (*Container)(nil), // 1: protobuf.Container @@ -1133,25 +1145,27 @@ var file_types_proto_goTypes = []any{ (*NotificationDispatcher)(nil), // 11: protobuf.NotificationDispatcher nil, // 12: protobuf.Container.LabelsEntry nil, // 13: protobuf.Host.LabelsEntry - (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp - (*anypb.Any)(nil), // 15: google.protobuf.Any + nil, // 14: protobuf.NotificationDispatcher.HeadersEntry + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp + (*anypb.Any)(nil), // 16: google.protobuf.Any } var file_types_proto_depIdxs = []int32{ - 14, // 0: protobuf.Container.created:type_name -> google.protobuf.Timestamp - 14, // 1: protobuf.Container.started:type_name -> google.protobuf.Timestamp + 15, // 0: protobuf.Container.created:type_name -> google.protobuf.Timestamp + 15, // 1: protobuf.Container.started:type_name -> google.protobuf.Timestamp 12, // 2: protobuf.Container.labels:type_name -> protobuf.Container.LabelsEntry 2, // 3: protobuf.Container.stats:type_name -> protobuf.ContainerStat - 14, // 4: protobuf.Container.finished:type_name -> google.protobuf.Timestamp - 15, // 5: protobuf.LogEvent.message:type_name -> google.protobuf.Any - 14, // 6: protobuf.LogEvent.timestamp:type_name -> google.protobuf.Timestamp + 15, // 4: protobuf.Container.finished:type_name -> google.protobuf.Timestamp + 16, // 5: protobuf.LogEvent.message:type_name -> google.protobuf.Any + 15, // 6: protobuf.LogEvent.timestamp:type_name -> google.protobuf.Timestamp 3, // 7: protobuf.GroupMessage.fragments:type_name -> protobuf.LogFragment - 14, // 8: protobuf.ContainerEvent.timestamp:type_name -> google.protobuf.Timestamp + 15, // 8: protobuf.ContainerEvent.timestamp:type_name -> google.protobuf.Timestamp 13, // 9: protobuf.Host.labels:type_name -> protobuf.Host.LabelsEntry - 10, // [10:10] is the sub-list for method output_type - 10, // [10:10] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 14, // 10: protobuf.NotificationDispatcher.headers:type_name -> protobuf.NotificationDispatcher.HeadersEntry + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_types_proto_init() } @@ -1165,7 +1179,7 @@ func file_types_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_types_proto_rawDesc), len(file_types_proto_rawDesc)), NumEnums: 1, - NumMessages: 13, + NumMessages: 14, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/agent/server.go b/internal/agent/server.go index f987f31b..79fd60f1 100644 --- a/internal/agent/server.go +++ b/internal/agent/server.go @@ -429,6 +429,7 @@ func (s *server) UpdateNotificationConfig(ctx context.Context, req *pb.UpdateNot Type: d.Type, URL: d.Url, Template: d.Template, + Headers: d.Headers, } } diff --git a/internal/container/event_generator.go b/internal/container/event_generator.go index d1baf677..14792451 100644 --- a/internal/container/event_generator.go +++ b/internal/container/event_generator.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "hash/fnv" + "math" "strings" "sync" "time" @@ -86,6 +87,9 @@ func (g *EventGenerator) flushGroup(pendingGroup []*LogEvent) bool { func (g *EventGenerator) processBuffer() { var pendingGroup []*LogEvent + seenFirst := false + var lastOrphanTimestamp int64 + orphanCount := 0 loop: for { @@ -95,8 +99,9 @@ loop: break loop } - // Complex logs are emitted immediately + // Complex logs are emitted immediately and always mark seenFirst if !current.IsSimple() { + seenFirst = true if !g.flushGroup(pendingGroup) { break loop } @@ -107,6 +112,24 @@ loop: continue } + // Skip leading simple events without a level that look like orphaned + // continuation lines from a group already emitted in a prior fetch. + // Only skip if they have a timestamp and are close in time, matching + // the group continuation criteria. + if !seenFirst && !current.HasLevel() && current.Timestamp > 0 { + if lastOrphanTimestamp == 0 || math.Abs(float64(lastOrphanTimestamp-current.Timestamp)) < maxGroupTimeDelta { + lastOrphanTimestamp = current.Timestamp + orphanCount++ + continue + } + } + if !seenFirst { + if orphanCount > 0 { + log.Debug().Int("count", orphanCount).Str("container", g.containerID).Msg("skipped orphaned continuation lines") + } + seenFirst = true + } + // Simple log - peek ahead to decide grouping next := g.peek() @@ -125,7 +148,7 @@ loop: pendingGroup = append(pendingGroup, current) - if next == nil || !next.IsSimple() || !canContinueGroup(pendingGroup[0], next) { + if next == nil || !next.IsSimple() || !canContinueGroup(pendingGroup[len(pendingGroup)-1], next) { if !g.flushGroup(pendingGroup) { break loop } @@ -154,32 +177,12 @@ func (g *EventGenerator) nextEvent() *LogEvent { // canStartGroup checks if current can start a group with next func canStartGroup(current, next *LogEvent) bool { - // Current must have a known level - if !current.HasLevel() { - return false - } - // Next must not have its own level (continuation) - if next.HasLevel() { - return false - } - // Must be close in time - if !current.IsCloseToTime(next) { - return false - } - return true + return current.HasLevel() && canContinueGroup(current, next) } -// canContinueGroup checks if next can be added to a group started by first -func canContinueGroup(first, next *LogEvent) bool { - // Next must not have its own level (continuation) - if next.HasLevel() { - return false - } - // Must be close in time to the group leader - if !first.IsCloseToTime(next) { - return false - } - return true +// canContinueGroup checks if next can be appended after prev in a group +func canContinueGroup(prev, next *LogEvent) bool { + return !next.HasLevel() && prev.IsCloseToTime(next) } func (g *EventGenerator) consumeReader() { @@ -220,9 +223,8 @@ func createEvent(message string, streamType StdType) *LogEvent { h := fnv.New32a() h.Write([]byte(message)) logEvent := &LogEvent{Id: h.Sum32(), Message: message, Stream: streamType.String(), Type: LogTypeSingle} - if index := strings.IndexAny(message, " "); index != -1 { - logId := message[:index] - if timestamp, err := time.Parse(time.RFC3339Nano, logId); err == nil { + if index := strings.IndexByte(message, ' '); index != -1 { + if timestamp, err := time.Parse(time.RFC3339Nano, message[:index]); err == nil { logEvent.Timestamp = timestamp.UnixMilli() message = strings.TrimSuffix(message[index+1:], "\n") logEvent.Message = message diff --git a/internal/container/event_generator_test.go b/internal/container/event_generator_test.go index 6a56b543..b4b3690a 100644 --- a/internal/container/event_generator_test.go +++ b/internal/container/event_generator_test.go @@ -240,6 +240,114 @@ func TestEventGenerator_MixedLogs(t *testing.T) { assert.Equal(t, LogTypeSingle, event2.Type) } +func TestEventGenerator_SkipsLeadingOrphanedContinuationLines(t *testing.T) { + // Simulate a pagination boundary where orphaned continuation lines + // (no level, with timestamp) appear before the first real log entry. + // These should be skipped as they belong to a group from a prior fetch. + baseTime := "2020-05-13T18:55:37.772853839Z" + messages := []string{ + baseTime + " at line 42", // orphan continuation (no level) + baseTime + " in function foo", // orphan continuation (no level) + baseTime + " ERROR: Next error", // real entry with level + baseTime + " at line 99", // continuation of the real entry + } + + reader := &mockLogReader{ + messages: messages, + types: []StdType{STDERR, STDERR, STDERR, STDERR}, + } + + g := NewEventGenerator(context.Background(), reader, Container{Tty: false}) + event := <-g.Events + + require.NotNil(t, event, "Expected event to not be nil") + assert.Equal(t, LogTypeGroup, event.Type) + + fragments, ok := event.Message.([]LogFragment) + require.True(t, ok, "Expected Message to be []LogFragment") + assert.Len(t, fragments, 2) + assert.Equal(t, "ERROR: Next error", fragments[0].Message) + assert.Equal(t, "at line 99", fragments[1].Message) +} + +func TestEventGenerator_DoesNotSkipLeadingLevellessLogsWithTimestampGap(t *testing.T) { + // Leading lines without levels but far apart in time should NOT be skipped. + // They are standalone logs, not orphaned group continuations. + messages := []string{ + "2020-05-13T18:55:37.000Z some log without level", + "2020-05-13T18:55:38.000Z another log without level", + } + + reader := &mockLogReader{ + messages: messages, + types: []StdType{STDOUT, STDOUT}, + } + + g := NewEventGenerator(context.Background(), reader, Container{Tty: false}) + + // First line is skipped (potential orphan), but the second is too far in time + // so it should be emitted. + event1 := <-g.Events + require.NotNil(t, event1, "Expected event to not be nil") + assert.Equal(t, LogTypeSingle, event1.Type) + assert.Equal(t, "another log without level", event1.Message) +} + +func TestEventGenerator_AllOrphanedLinesProducesNoEvents(t *testing.T) { + // If all lines are orphaned continuations, no events should be emitted. + baseTime := "2020-05-13T18:55:37.772853839Z" + messages := []string{ + baseTime + " at line 42", + baseTime + " in function foo", + baseTime + " in function bar", + } + + reader := &mockLogReader{ + messages: messages, + types: []StdType{STDERR, STDERR, STDERR}, + } + + g := NewEventGenerator(context.Background(), reader, Container{Tty: false}) + event, ok := <-g.Events + + assert.False(t, ok, "Expected channel to be closed with no events") + assert.Nil(t, event) +} + +func TestEventGenerator_OrphanedLinesFollowedByComplexLog(t *testing.T) { + // Orphaned continuation lines should be skipped even when followed by a complex log. + baseTime := "2020-05-13T18:55:37.772853839Z" + messages := []string{ + baseTime + " at line 42", + baseTime + " in function foo", + baseTime + " {\"level\": \"info\", \"message\": \"test\"}", + } + + reader := &mockLogReader{ + messages: messages, + types: []StdType{STDERR, STDERR, STDOUT}, + } + + g := NewEventGenerator(context.Background(), reader, Container{Tty: false}) + event := <-g.Events + + require.NotNil(t, event, "Expected event to not be nil") + assert.Equal(t, LogTypeComplex, event.Type) +} + +func TestEventGenerator_DoesNotSkipLeadingLinesWithoutTimestamp(t *testing.T) { + // Lines without timestamps (e.g., tty/raw input) should not be skipped + // even if they lack a level. + input := "some raw output" + + g := NewEventGenerator(context.Background(), makeFakeReader(input, STDOUT), Container{Tty: true}) + event := <-g.Events + + require.NotNil(t, event, "Expected event to not be nil") + assert.Equal(t, input, event.Message) + assert.Equal(t, LogTypeSingle, event.Type) +} + func TestEventGenerator_NoGroupingWhenTimestampGap(t *testing.T) { // Messages with different timestamps (too far apart to group) messages := []string{ diff --git a/internal/container/types.go b/internal/container/types.go index ebc1c93e..7893ecad 100644 --- a/internal/container/types.go +++ b/internal/container/types.go @@ -199,8 +199,13 @@ func (l *LogEvent) IsSimple() bool { return l.Type == LogTypeSingle || l.Type == LogTypeGroup } +// maxGroupTimeDelta is the maximum time difference (in milliseconds) between +// consecutive log lines that can be grouped together. Docker can introduce +// up to ~30ms of jitter between related log lines (e.g., a stack trace). +const maxGroupTimeDelta = 50 + func (l *LogEvent) IsCloseToTime(other *LogEvent) bool { - return math.Abs(float64(l.Timestamp-other.Timestamp)) < 10 + return math.Abs(float64(l.Timestamp-other.Timestamp)) < maxGroupTimeDelta } func (l *LogEvent) MessageId() int64 { diff --git a/internal/notification/config.go b/internal/notification/config.go index 20a31502..7e8dc96c 100644 --- a/internal/notification/config.go +++ b/internal/notification/config.go @@ -59,6 +59,7 @@ func (m *Manager) LoadConfig(r io.Reader) error { Type: d.Type, URL: d.URL, Template: d.Template, + Headers: d.Headers, APIKey: d.APIKey, Prefix: d.Prefix, ExpiresAt: d.ExpiresAt, @@ -116,6 +117,7 @@ func (m *Manager) HandleNotificationConfig(subscriptions []types.SubscriptionCon Type: dc.Type, URL: dc.URL, Template: dc.Template, + Headers: dc.Headers, APIKey: dc.APIKey, Prefix: dc.Prefix, ExpiresAt: dc.ExpiresAt, @@ -137,7 +139,7 @@ func (m *Manager) HandleNotificationConfig(subscriptions []types.SubscriptionCon func createDispatcher(config DispatcherConfig) (dispatcher.Dispatcher, error) { switch config.Type { case "webhook": - return dispatcher.NewWebhookDispatcher(config.Name, config.URL, config.Template) + return dispatcher.NewWebhookDispatcher(config.Name, config.URL, config.Template, config.Headers) case "cloud": return dispatcher.NewCloudDispatcher(config.Name, config.APIKey, config.Prefix, config.ExpiresAt) default: diff --git a/internal/notification/dispatcher/webhook.go b/internal/notification/dispatcher/webhook.go index 670cea9f..97506cf8 100644 --- a/internal/notification/dispatcher/webhook.go +++ b/internal/notification/dispatcher/webhook.go @@ -24,16 +24,18 @@ type WebhookDispatcher struct { URL string Template *template.Template TemplateText string // Original template string for serialization + Headers map[string]string client *http.Client } // NewWebhookDispatcher creates a new webhook dispatcher // If templateStr is empty, the notification will be marshaled as JSON directly -func NewWebhookDispatcher(name, url, templateStr string) (*WebhookDispatcher, error) { +func NewWebhookDispatcher(name, url, templateStr string, headers map[string]string) (*WebhookDispatcher, error) { w := &WebhookDispatcher{ Name: name, URL: url, TemplateText: templateStr, + Headers: headers, client: &http.Client{ Timeout: 10 * time.Second, }, @@ -88,6 +90,9 @@ func (w *WebhookDispatcher) SendTest(ctx context.Context, notification types.Not return TestResult{Success: false, Error: fmt.Sprintf("failed to create request: %v", err)} } + for k, v := range w.Headers { + req.Header.Set(k, v) + } req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", UserAgent) diff --git a/internal/notification/manager.go b/internal/notification/manager.go index 6ba165e9..b92b956e 100644 --- a/internal/notification/manager.go +++ b/internal/notification/manager.go @@ -152,8 +152,13 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error { SampleWindow: sub.SampleWindow, MetricCooldowns: sub.MetricCooldowns, MetricSampleBuffers: sub.MetricSampleBuffers, + TriggeredContainerIDs: sub.TriggeredContainerIDs, } + // Preserve runtime stats (atomics can't be copied in struct literal) + updated.TriggerCount.Store(sub.TriggerCount.Load()) + updated.LastTriggeredAt.Store(sub.LastTriggeredAt.Load()) + // Apply updates to the clone for key, value := range updates { switch key { @@ -305,6 +310,7 @@ func (m *Manager) Dispatchers() []DispatcherConfig { Type: "webhook", URL: v.URL, Template: v.TemplateText, + Headers: v.Headers, }) case *dispatcher.CloudDispatcher: result = append(result, DispatcherConfig{ diff --git a/internal/notification/types.go b/internal/notification/types.go index e186a880..3318fad7 100644 --- a/internal/notification/types.go +++ b/internal/notification/types.go @@ -160,14 +160,15 @@ func (s *Subscription) CompileExpressions() error { // DispatcherConfig represents a dispatcher configuration type DispatcherConfig struct { - ID int `json:"id" yaml:"id"` - Name string `json:"name" yaml:"name"` - Type string `json:"type" yaml:"type"` // "webhook", "cloud" - URL string `json:"url,omitempty" yaml:"url,omitempty"` - Template string `json:"template,omitempty" yaml:"template,omitempty"` // Go template for custom payload format - APIKey string `json:"apiKey,omitempty" yaml:"apiKey,omitempty"` // API key for cloud dispatcher - Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` // API key prefix for cloud dispatcher - ExpiresAt *time.Time `json:"expiresAt,omitempty" yaml:"expiresAt,omitempty"` + ID int `json:"id" yaml:"id"` + Name string `json:"name" yaml:"name"` + Type string `json:"type" yaml:"type"` // "webhook", "cloud" + URL string `json:"url,omitempty" yaml:"url,omitempty"` + Template string `json:"template,omitempty" yaml:"template,omitempty"` // Go template for custom payload format + Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` // Custom HTTP headers + APIKey string `json:"apiKey,omitempty" yaml:"apiKey,omitempty"` // API key for cloud dispatcher + Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"` // API key prefix for cloud dispatcher + ExpiresAt *time.Time `json:"expiresAt,omitempty" yaml:"expiresAt,omitempty"` } // Config represents the persisted notification configuration diff --git a/internal/web/download.go b/internal/web/download.go index 967f5e68..e0423292 100644 --- a/internal/web/download.go +++ b/internal/web/download.go @@ -137,7 +137,11 @@ func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) { for _, c := range containers { // Create new file in zip for this container's logs fileName := fmt.Sprintf("%s-%s.log", c.containerService.Container.Name, nowFmt) - f, err := zw.Create(fileName) + f, err := zw.CreateHeader(&zip.FileHeader{ + Name: fileName, + Modified: now, + Method: zip.Deflate, + }) if err != nil { log.Error().Err(err).Msgf("error creating zip entry for container %s", c.id) return diff --git a/internal/web/notifications.go b/internal/web/notifications.go index 689eba87..38263775 100644 --- a/internal/web/notifications.go +++ b/internal/web/notifications.go @@ -37,13 +37,14 @@ type NotificationRuleResponse struct { } type DispatcherResponse struct { - ID int `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - URL *string `json:"url,omitempty"` - Template *string `json:"template,omitempty"` - Prefix *string `json:"prefix,omitempty"` - ExpiresAt *time.Time `json:"expiresAt,omitempty"` + ID int `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + URL *string `json:"url,omitempty"` + Template *string `json:"template,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + Prefix *string `json:"prefix,omitempty"` + ExpiresAt *time.Time `json:"expiresAt,omitempty"` } type NotificationRuleInput struct { @@ -69,10 +70,11 @@ type NotificationRuleUpdateInput struct { } type DispatcherInput struct { - Name string `json:"name"` - Type string `json:"type"` - URL *string `json:"url,omitempty"` - Template *string `json:"template,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + URL *string `json:"url,omitempty"` + Template *string `json:"template,omitempty"` + Headers map[string]string `json:"headers,omitempty"` } type PreviewInput struct { @@ -92,8 +94,9 @@ type PreviewResult struct { } type TestWebhookInput struct { - URL string `json:"url"` - Template *string `json:"template,omitempty"` + URL string `json:"url"` + Template *string `json:"template,omitempty"` + Headers map[string]string `json:"headers,omitempty"` } type TestWebhookResult struct { @@ -142,6 +145,10 @@ func dispatcherConfigToResponse(d *notification.DispatcherConfig) *DispatcherRes if d.Template != "" { template = &d.Template } + var headers map[string]string + if len(d.Headers) > 0 { + headers = d.Headers + } var prefix *string if d.Prefix != "" { prefix = &d.Prefix @@ -152,6 +159,7 @@ func dispatcherConfigToResponse(d *notification.DispatcherConfig) *DispatcherRes Type: d.Type, URL: url, Template: template, + Headers: headers, Prefix: prefix, ExpiresAt: d.ExpiresAt, } @@ -369,7 +377,7 @@ func (h *handler) createDispatcher(w http.ResponseWriter, r *http.Request) { if input.Template != nil { templateStr = *input.Template } - webhook, err := dispatcher.NewWebhookDispatcher(input.Name, url, templateStr) + webhook, err := dispatcher.NewWebhookDispatcher(input.Name, url, templateStr, input.Headers) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return @@ -382,13 +390,17 @@ func (h *handler) createDispatcher(w http.ResponseWriter, r *http.Request) { id := h.hostService.AddDispatcher(d) - writeJSON(w, http.StatusCreated, &DispatcherResponse{ + resp := &DispatcherResponse{ ID: id, Name: input.Name, Type: input.Type, URL: input.URL, Template: input.Template, - }) + } + if len(input.Headers) > 0 { + resp.Headers = input.Headers + } + writeJSON(w, http.StatusCreated, resp) } func (h *handler) updateDispatcher(w http.ResponseWriter, r *http.Request) { @@ -415,7 +427,7 @@ func (h *handler) updateDispatcher(w http.ResponseWriter, r *http.Request) { if input.Template != nil { templateStr = *input.Template } - webhook, err := dispatcher.NewWebhookDispatcher(input.Name, url, templateStr) + webhook, err := dispatcher.NewWebhookDispatcher(input.Name, url, templateStr, input.Headers) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return @@ -428,13 +440,17 @@ func (h *handler) updateDispatcher(w http.ResponseWriter, r *http.Request) { h.hostService.UpdateDispatcher(id, d) - writeJSON(w, http.StatusOK, &DispatcherResponse{ + resp := &DispatcherResponse{ ID: id, Name: input.Name, Type: input.Type, URL: input.URL, Template: input.Template, - }) + } + if len(input.Headers) > 0 { + resp.Headers = input.Headers + } + writeJSON(w, http.StatusOK, resp) } func (h *handler) deleteDispatcher(w http.ResponseWriter, r *http.Request) { @@ -596,7 +612,7 @@ func (h *handler) testWebhook(w http.ResponseWriter, r *http.Request) { templateStr = *input.Template } - webhook, err := dispatcher.NewWebhookDispatcher("test", input.URL, templateStr) + webhook, err := dispatcher.NewWebhookDispatcher("test", input.URL, templateStr, input.Headers) if err != nil { errStr := err.Error() writeJSON(w, http.StatusOK, &TestWebhookResult{ diff --git a/locales/da.yml b/locales/da.yml index 4eca22a8..84a380c3 100644 --- a/locales/da.yml +++ b/locales/da.yml @@ -240,6 +240,11 @@ notifications: format-custom: Brugerdefineret template: Skabelon template-hint: Go skabelon syntaks + headers: Brugerdefinerede Headers + headers-hint: Valgfrie HTTP-headers (f.eks. Authorization) + header-name: Header-navn + header-value: Header-værdi + add-header: Tilføj Header test: Test test-success: Test succesfuld cancel: Annuller diff --git a/locales/de.yml b/locales/de.yml index 08126762..58b7e326 100644 --- a/locales/de.yml +++ b/locales/de.yml @@ -240,6 +240,11 @@ notifications: format-custom: Benutzerdefiniert template: Vorlage template-hint: Go-Template-Syntax + headers: Benutzerdefinierte Header + headers-hint: Optionale HTTP-Header (z.B. Authorization) + header-name: Header-Name + header-value: Header-Wert + add-header: Header hinzufügen test: Testen test-success: Test erfolgreich cancel: Abbrechen diff --git a/locales/en.yml b/locales/en.yml index ef9c76f5..bfce26a6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -249,6 +249,11 @@ notifications: format-custom: Custom template: Template template-hint: Go template syntax + headers: Custom Headers + headers-hint: Optional HTTP headers (e.g., Authorization) + header-name: Header name + header-value: Header value + add-header: Add Header test: Test test-success: Test successful cancel: Cancel diff --git a/locales/es.yml b/locales/es.yml index 7b0cbb4d..3f4edfd2 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -240,6 +240,11 @@ notifications: format-custom: Personalizado template: Plantilla template-hint: Sintaxis de plantilla Go + headers: Encabezados Personalizados + headers-hint: Encabezados HTTP opcionales (ej. Authorization) + header-name: Nombre del encabezado + header-value: Valor del encabezado + add-header: Añadir Encabezado test: Probar test-success: Prueba exitosa cancel: Cancelar diff --git a/locales/fr.yml b/locales/fr.yml index ccd1cb7f..9e60cc9f 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -240,6 +240,11 @@ notifications: format-custom: Personnalisé template: Modèle template-hint: Syntaxe de modèle Go + headers: En-têtes Personnalisés + headers-hint: En-têtes HTTP optionnels (ex. Authorization) + header-name: Nom de l'en-tête + header-value: Valeur de l'en-tête + add-header: Ajouter un En-tête test: Tester test-success: Test réussi cancel: Annuler diff --git a/locales/id.yml b/locales/id.yml index 949da6b6..e87a833c 100644 --- a/locales/id.yml +++ b/locales/id.yml @@ -252,6 +252,11 @@ notifications: format-custom: Kustom template: Template template-hint: Sintaks template Go + headers: Header Kustom + headers-hint: Header HTTP opsional (mis. Authorization) + header-name: Nama header + header-value: Nilai header + add-header: Tambah Header test: Test test-success: Test berhasil cancel: Batal diff --git a/locales/it.yml b/locales/it.yml index 803723b0..ff3e5582 100644 --- a/locales/it.yml +++ b/locales/it.yml @@ -240,6 +240,11 @@ notifications: format-custom: Personalizzato template: Template template-hint: Sintassi template Go + headers: Header Personalizzati + headers-hint: Header HTTP opzionali (es. Authorization) + header-name: Nome header + header-value: Valore header + add-header: Aggiungi Header test: Test test-success: Test riuscito cancel: Annulla diff --git a/locales/ko.yml b/locales/ko.yml index 5da7bb84..e96cc775 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -243,6 +243,11 @@ notifications: format-custom: 사용자 정의 template: 템플릿 template-hint: Go 템플릿 구문 + headers: 사용자 정의 헤더 + headers-hint: "선택적 HTTP 헤더 (예: Authorization)" + header-name: 헤더 이름 + header-value: 헤더 값 + add-header: 헤더 추가 test: 테스트 test-success: 테스트 성공 cancel: 취소 diff --git a/locales/nl.yml b/locales/nl.yml index 0ae17ec7..5bb91588 100644 --- a/locales/nl.yml +++ b/locales/nl.yml @@ -241,6 +241,11 @@ notifications: format-custom: Aangepast template: Sjabloon template-hint: Go-sjabloonsyntaxis + headers: Aangepaste Headers + headers-hint: Optionele HTTP-headers (bijv. Authorization) + header-name: Headernaam + header-value: Headerwaarde + add-header: Header toevoegen test: Testen test-success: Test geslaagd cancel: Annuleren diff --git a/locales/pl.yml b/locales/pl.yml index 484bf9f3..e4a87862 100644 --- a/locales/pl.yml +++ b/locales/pl.yml @@ -247,6 +247,11 @@ notifications: format-custom: Własny template: Szablon template-hint: Składnia szablonu Go + headers: Niestandardowe Nagłówki + headers-hint: Opcjonalne nagłówki HTTP (np. Authorization) + header-name: Nazwa nagłówka + header-value: Wartość nagłówka + add-header: Dodaj Nagłówek test: Test test-success: Test zakończony pomyślnie cancel: Anuluj diff --git a/locales/pr.yml b/locales/pr.yml index a83f76ff..4479e5f7 100644 --- a/locales/pr.yml +++ b/locales/pr.yml @@ -249,6 +249,11 @@ notifications: format-custom: Personalizado template: Modelo template-hint: Sintaxe de modelo Go + headers: Cabeçalhos Personalizados + headers-hint: Cabeçalhos HTTP opcionais (ex. Authorization) + header-name: Nome do cabeçalho + header-value: Valor do cabeçalho + add-header: Adicionar Cabeçalho test: Testar test-success: Teste bem-sucedido cancel: Cancelar diff --git a/locales/pt.yml b/locales/pt.yml index 30fda788..8bafcb61 100644 --- a/locales/pt.yml +++ b/locales/pt.yml @@ -239,6 +239,11 @@ notifications: format-custom: Personalizado template: Template template-hint: Sintaxe de template Go + headers: Cabeçalhos Personalizados + headers-hint: Cabeçalhos HTTP opcionais (ex. Authorization) + header-name: Nome do cabeçalho + header-value: Valor do cabeçalho + add-header: Adicionar Cabeçalho test: Testar test-success: Teste bem-sucedido cancel: Cancelar diff --git a/locales/ru.yml b/locales/ru.yml index 0fdc5749..b12458ea 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -240,6 +240,11 @@ notifications: format-custom: Пользовательский template: Шаблон template-hint: Синтаксис шаблона Go + headers: Пользовательские Заголовки + headers-hint: Необязательные HTTP-заголовки (напр. Authorization) + header-name: Имя заголовка + header-value: Значение заголовка + add-header: Добавить Заголовок test: Тест test-success: Тест успешен cancel: Отмена diff --git a/locales/sl.yml b/locales/sl.yml index bd47db7c..974de745 100644 --- a/locales/sl.yml +++ b/locales/sl.yml @@ -245,6 +245,11 @@ notifications: format-custom: Po meri template: Predloga template-hint: Sintaksa predloge Go + headers: Prilagojene Glave + headers-hint: Neobvezne HTTP glave (npr. Authorization) + header-name: Ime glave + header-value: Vrednost glave + add-header: Dodaj Glavo test: Test test-success: Test uspešen cancel: Prekliči diff --git a/locales/tr.yml b/locales/tr.yml index 9e7ea670..430c41df 100644 --- a/locales/tr.yml +++ b/locales/tr.yml @@ -240,6 +240,11 @@ notifications: format-custom: Özel template: Şablon template-hint: Go şablon söz dizimi + headers: Özel Başlıklar + headers-hint: İsteğe bağlı HTTP başlıkları (ör. Authorization) + header-name: Başlık adı + header-value: Başlık değeri + add-header: Başlık Ekle test: Test test-success: Test başarılı cancel: İptal diff --git a/locales/zh-tw.yml b/locales/zh-tw.yml index 18b067e2..36be3c8d 100644 --- a/locales/zh-tw.yml +++ b/locales/zh-tw.yml @@ -243,6 +243,11 @@ notifications: format-custom: 自訂 template: 範本 template-hint: Go 範本語法 + headers: 自訂標頭 + headers-hint: 選填的 HTTP 標頭(例如 Authorization) + header-name: 標頭名稱 + header-value: 標頭值 + add-header: 新增標頭 test: 測試 test-success: 測試成功 cancel: 取消 diff --git a/locales/zh.yml b/locales/zh.yml index 552ff5cc..171a42e2 100644 --- a/locales/zh.yml +++ b/locales/zh.yml @@ -240,6 +240,11 @@ notifications: format-custom: 自定义 template: 模板 template-hint: Go 模板语法 + headers: 自定义请求头 + headers-hint: 可选的 HTTP 请求头(如 Authorization) + header-name: 请求头名称 + header-value: 请求头值 + add-header: 添加请求头 test: 测试 test-success: 测试成功 cancel: 取消 diff --git a/package.json b/package.json index afa9f4f2..70f71b73 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "dozzle", - "version": "10.0.7", + "version": "10.1.0", "description": "Realtime log viewer for docker containers.", "homepage": "https://github.com/amir20/dozzle#readme", "bugs": { "url": "https://github.com/amir20/dozzle/issues" }, - "packageManager": "pnpm@10.30.3", + "packageManager": "pnpm@10.31.0", "type": "module", "repository": { "type": "git", @@ -40,7 +40,7 @@ "@iconify-json/carbon": "^1.2.19", "@iconify-json/cil": "^1.2.3", "@iconify-json/ic": "^1.2.4", - "@iconify-json/material-symbols": "^1.2.59", + "@iconify-json/material-symbols": "^1.2.60", "@iconify-json/mdi": "^1.2.3", "@iconify-json/mdi-light": "^1.2.2", "@iconify-json/octicon": "^1.2.21", @@ -73,22 +73,22 @@ "unplugin-vue-macros": "^2.14.5", "vite": "7.3.1", "vite-plugin-vue-layouts": "^0.11.0", - "vite-svg-loader": "^5.1.0", + "vite-svg-loader": "^5.1.1", "vitepress": "1.6.4", "vue": "^3.5.29", - "vue-i18n": "^11.2.8", + "vue-i18n": "^11.3.0", "vue-router": "^5.0.3" }, "devDependencies": { "@apache-arrow/esnext-esm": "^21.1.0", "@iconify-json/ion": "^1.2.6", - "@iconify-json/material-symbols-light": "^1.2.59", + "@iconify-json/material-symbols-light": "^1.2.60", "@iconify-json/ri": "^1.2.10", "@iconify-json/svg-spinners": "^1.2.4", "@pinia/testing": "^1.0.3", "@playwright/test": "^1.58.2", "@types/lodash.debounce": "^4.0.9", - "@types/node": "^25.3.3", + "@types/node": "^25.3.5", "@vitejs/plugin-vue": "6.0.4", "@vue/compiler-sfc": "^3.5.29", "@vue/test-utils": "^2.4.6", @@ -97,7 +97,7 @@ "concurrently": "^9.2.1", "eventsourcemock": "^2.0.0", "jsdom": "^28.1.0", - "lint-staged": "^16.3.1", + "lint-staged": "^16.3.2", "prettier": "^3.8.1", "prettier-plugin-tailwindcss": "^0.7.2", "simple-git-hooks": "^2.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79f3b62c..edca4b75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: ^1.2.4 version: 1.2.4 '@iconify-json/material-symbols': - specifier: ^1.2.59 - version: 1.2.59 + specifier: ^1.2.60 + version: 1.2.60 '@iconify-json/mdi': specifier: ^1.2.3 version: 1.2.3 @@ -61,7 +61,7 @@ importers: version: 1.2.2 '@intlify/unplugin-vue-i18n': specifier: ^11.0.7 - version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.19.0(jiti@2.6.1))(rollup@4.53.3)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) + version: 11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.19.0(jiti@2.6.1))(rollup@4.53.3)(typescript@5.9.3)(vue-i18n@11.3.0(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) '@lezer/highlight': specifier: ^1.2.3 version: 1.2.3 @@ -70,7 +70,7 @@ importers: version: 0.5.19(tailwindcss@4.2.1) '@tailwindcss/vite': specifier: 4.2.1 - version: 4.2.1(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) + version: 4.2.1(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) '@vueuse/components': specifier: ^14.2.1 version: 14.2.1(vue@3.5.29(typescript@5.9.3)) @@ -136,25 +136,25 @@ importers: version: 31.0.0(vue@3.5.29(typescript@5.9.3)) unplugin-vue-macros: specifier: ^2.14.5 - version: 2.14.5(@vueuse/core@14.2.1(vue@3.5.29(typescript@5.9.3)))(esbuild@0.27.1)(rollup@4.53.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3)) + version: 2.14.5(@vueuse/core@14.2.1(vue@3.5.29(typescript@5.9.3)))(esbuild@0.27.1)(rollup@4.53.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3)) vite: specifier: 7.3.1 - version: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + version: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) vite-plugin-vue-layouts: specifier: ^0.11.0 - version: 0.11.0(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) + version: 0.11.0(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) vite-svg-loader: - specifier: ^5.1.0 - version: 5.1.0(vue@3.5.29(typescript@5.9.3)) + specifier: ^5.1.1 + version: 5.1.1(vue@3.5.29(typescript@5.9.3)) vitepress: specifier: 1.6.4 - version: 1.6.4(@algolia/client-search@5.23.4)(@types/node@25.3.3)(fuse.js@7.1.0)(lightningcss@1.31.1)(postcss@8.5.6)(react@18.3.1)(search-insights@2.17.3)(sortablejs@1.15.7)(terser@5.39.0)(typescript@5.9.3) + version: 1.6.4(@algolia/client-search@5.23.4)(@types/node@25.3.5)(fuse.js@7.1.0)(lightningcss@1.31.1)(postcss@8.5.6)(react@18.3.1)(search-insights@2.17.3)(sortablejs@1.15.7)(terser@5.39.0)(typescript@5.9.3) vue: specifier: ^3.5.29 version: 3.5.29(typescript@5.9.3) vue-i18n: - specifier: ^11.2.8 - version: 11.2.8(vue@3.5.29(typescript@5.9.3)) + specifier: ^11.3.0 + version: 11.3.0(vue@3.5.29(typescript@5.9.3)) vue-router: specifier: ^5.0.3 version: 5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) @@ -166,8 +166,8 @@ importers: specifier: ^1.2.6 version: 1.2.6 '@iconify-json/material-symbols-light': - specifier: ^1.2.59 - version: 1.2.59 + specifier: ^1.2.60 + version: 1.2.60 '@iconify-json/ri': specifier: ^1.2.10 version: 1.2.10 @@ -184,11 +184,11 @@ importers: specifier: ^4.0.9 version: 4.0.9 '@types/node': - specifier: ^25.3.3 - version: 25.3.3 + specifier: ^25.3.5 + version: 25.3.5 '@vitejs/plugin-vue': specifier: 6.0.4 - version: 6.0.4(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) + version: 6.0.4(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) '@vue/compiler-sfc': specifier: ^3.5.29 version: 3.5.29 @@ -211,8 +211,8 @@ importers: specifier: ^28.1.0 version: 28.1.0 lint-staged: - specifier: ^16.3.1 - version: 16.3.1 + specifier: ^16.3.2 + version: 16.3.2 prettier: specifier: ^3.8.1 version: 3.8.1 @@ -224,13 +224,13 @@ importers: version: 2.13.1 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@25.3.3)(typescript@5.9.3) + version: 10.9.2(@types/node@25.3.5)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@25.3.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + version: 4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) vue-component-type-helpers: specifier: 3.2.5 version: 3.2.5 @@ -1003,11 +1003,11 @@ packages: '@iconify-json/ion@1.2.6': resolution: {integrity: sha512-JftEXKfjvJNn3SrGeSBrG/waRkjeTpLdMLNLwpAX4NgI14QgJoAeXEh2iZjNPqioAkeIgErX4Bi6mnFwpjk3BQ==} - '@iconify-json/material-symbols-light@1.2.59': - resolution: {integrity: sha512-0dO9tGoHI9aV8Y9J0vI5oH2AsFiN8GrWoCECUqokBqoH/8Y8lpTGzDs2vW1qomNQmiMrqxxvz+u/wkQAVGX2OA==} + '@iconify-json/material-symbols-light@1.2.60': + resolution: {integrity: sha512-rBdUMsTK9v8BPI5zM9/2eFwX0lHCYyki4FfcaHCnMDk0BcnfvGueEqFP1TyfUGk4peQpmGHR44ZJ0jW2bPAxBA==} - '@iconify-json/material-symbols@1.2.59': - resolution: {integrity: sha512-difuedW4jZyiDDu2SySjPCr+UsM65PRkEg3zUuKz0YRGMkNyBsk3R6j9s/ofP1NaKr2y32XxrzTawmnJZbzOAw==} + '@iconify-json/material-symbols@1.2.60': + resolution: {integrity: sha512-bmVrnz/9KJNh/j+nO3pyxzo0h8iENIKxv5RASDURhjKdPI53rRcKN7E2Gmwi25k9CBJ+IC6L9l0AGgngeqyRuw==} '@iconify-json/mdi-light@1.2.2': resolution: {integrity: sha512-86UV9uyNve8zRFWiPrOrrDp9GDzsZM7plYV/on4VjgLLqXlyriuy541eHZB7LIOzTUyIPVli7QiUpBbTtBhsFw==} @@ -1048,18 +1048,30 @@ packages: vue-i18n: optional: true - '@intlify/core-base@11.2.8': - resolution: {integrity: sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==} + '@intlify/core-base@11.3.0': + resolution: {integrity: sha512-NNX5jIwF4TJBe7RtSKDMOA6JD9mp2mRcBHAwt2X+Q8PvnZub0yj5YYXlFu2AcESdgQpEv/5Yx2uOCV/yh7YkZg==} + engines: {node: '>= 16'} + + '@intlify/devtools-types@11.3.0': + resolution: {integrity: sha512-G9CNL4WpANWVdUjubOIIS7/D2j/0j+1KJmhBJxHilWNKr9mmt3IjFV3Hq4JoBP23uOoC5ynxz/FHZ42M+YxfGw==} engines: {node: '>= 16'} '@intlify/message-compiler@11.2.8': resolution: {integrity: sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==} engines: {node: '>= 16'} + '@intlify/message-compiler@11.3.0': + resolution: {integrity: sha512-RAJp3TMsqohg/Wa7bVF3cChRhecSYBLrTCQSj7j0UtWVFLP+6iEJoE2zb7GU5fp+fmG5kCbUdzhmlAUCWXiUJw==} + engines: {node: '>= 16'} + '@intlify/shared@11.2.8': resolution: {integrity: sha512-l6e4NZyUgv8VyXXH4DbuucFOBmxLF56C/mqh2tvApbzl2Hrhi1aTDcuv5TKdxzfHYmpO3UB0Cz04fgDT9vszfw==} engines: {node: '>= 16'} + '@intlify/shared@11.3.0': + resolution: {integrity: sha512-LC6P/uay7rXL5zZ5+5iRJfLs/iUN8apu9tm8YqQVmW3Uq3X4A0dOFUIDuAmB7gAC29wTHOS3EiN/IosNSz0eNQ==} + engines: {node: '>= 16'} + '@intlify/unplugin-vue-i18n@11.0.7': resolution: {integrity: sha512-wswKprS1D8VfnxxVhKxug5wa3MbDSOcCoXOBjnzhMK+6NfP6h6UI8pFqSBIvcW8nPDuzweTc0Sk3PeBCcubfoQ==} engines: {node: '>= 20'} @@ -1623,10 +1635,6 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -1690,8 +1698,8 @@ packages: '@types/node@24.10.9': resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==} - '@types/node@25.3.3': - resolution: {integrity: sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==} + '@types/node@25.3.5': + resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==} '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -3170,8 +3178,8 @@ packages: resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} engines: {node: '>= 12.0.0'} - lint-staged@16.3.1: - resolution: {integrity: sha512-bqvvquXzFBAlSbluugR4KXAe4XnO/QZcKVszpkBtqLWa2KEiVy8n6Xp38OeUbv/gOJOX4Vo9u5pFt/ADvbm42Q==} + lint-staged@16.3.2: + resolution: {integrity: sha512-xKqhC2AeXLwiAHXguxBjuChoTTWFC6Pees0SHPwOpwlvI3BH7ZADFPddAdN3pgo3aiKgPUx/bxE78JfUnxQnlg==} engines: {node: '>=20.17'} hasBin: true @@ -3614,6 +3622,10 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + sax@1.5.0: + resolution: {integrity: sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==} + engines: {node: '>=11.0.0'} + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -3768,8 +3780,8 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - svgo@3.3.2: - resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} + svgo@3.3.3: + resolution: {integrity: sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==} engines: {node: '>=14.0.0'} hasBin: true @@ -4052,8 +4064,8 @@ packages: vue: ^3.2.4 vue-router: ^4.0.11 - vite-svg-loader@5.1.0: - resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==} + vite-svg-loader@5.1.1: + resolution: {integrity: sha512-RPzcXA/EpKJA0585x58DBgs7my2VfeJ+j2j1EoHY4Zh82Y7hV4cR1fElgy2aZi85+QSrcLLoTStQ5uZjD68u+Q==} peerDependencies: vue: '>=3.2.13' @@ -4183,8 +4195,8 @@ packages: vue-component-type-helpers@3.2.5: resolution: {integrity: sha512-tkvNr+bU8+xD/onAThIe7CHFvOJ/BO6XCOrxMzeytJq40nTfpGDJuVjyCM8ccGZKfAbGk2YfuZyDMXM56qheZQ==} - vue-i18n@11.2.8: - resolution: {integrity: sha512-vJ123v/PXCZntd6Qj5Jumy7UBmIuE92VrtdX+AXr+1WzdBHojiBxnAxdfctUFL+/JIN+VQH4BhsfTtiGsvVObg==} + vue-i18n@11.3.0: + resolution: {integrity: sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==} engines: {node: '>= 16'} peerDependencies: vue: ^3.0.0 @@ -4931,11 +4943,11 @@ snapshots: dependencies: '@iconify/types': 2.0.0 - '@iconify-json/material-symbols-light@1.2.59': + '@iconify-json/material-symbols-light@1.2.60': dependencies: '@iconify/types': 2.0.0 - '@iconify-json/material-symbols@1.2.59': + '@iconify-json/material-symbols@1.2.60': dependencies: '@iconify/types': 2.0.0 @@ -4975,7 +4987,7 @@ snapshots: '@iconify/types': 2.0.0 mlly: 1.8.0 - '@intlify/bundle-utils@11.0.7(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))': + '@intlify/bundle-utils@11.0.7(vue-i18n@11.3.0(vue@3.5.29(typescript@5.9.3)))': dependencies: '@intlify/message-compiler': 11.2.8 '@intlify/shared': 11.2.8 @@ -4987,26 +4999,39 @@ snapshots: source-map-js: 1.2.1 yaml-eslint-parser: 1.3.0 optionalDependencies: - vue-i18n: 11.2.8(vue@3.5.29(typescript@5.9.3)) + vue-i18n: 11.3.0(vue@3.5.29(typescript@5.9.3)) - '@intlify/core-base@11.2.8': + '@intlify/core-base@11.3.0': dependencies: - '@intlify/message-compiler': 11.2.8 - '@intlify/shared': 11.2.8 + '@intlify/devtools-types': 11.3.0 + '@intlify/message-compiler': 11.3.0 + '@intlify/shared': 11.3.0 + + '@intlify/devtools-types@11.3.0': + dependencies: + '@intlify/core-base': 11.3.0 + '@intlify/shared': 11.3.0 '@intlify/message-compiler@11.2.8': dependencies: '@intlify/shared': 11.2.8 source-map-js: 1.2.1 + '@intlify/message-compiler@11.3.0': + dependencies: + '@intlify/shared': 11.3.0 + source-map-js: 1.2.1 + '@intlify/shared@11.2.8': {} - '@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.19.0(jiti@2.6.1))(rollup@4.53.3)(typescript@5.9.3)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))': + '@intlify/shared@11.3.0': {} + + '@intlify/unplugin-vue-i18n@11.0.7(@vue/compiler-dom@3.5.29)(eslint@9.19.0(jiti@2.6.1))(rollup@4.53.3)(typescript@5.9.3)(vue-i18n@11.3.0(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.19.0(jiti@2.6.1)) - '@intlify/bundle-utils': 11.0.7(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3))) + '@intlify/bundle-utils': 11.0.7(vue-i18n@11.3.0(vue@3.5.29(typescript@5.9.3))) '@intlify/shared': 11.2.8 - '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.29)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) + '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.29)(vue-i18n@11.3.0(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) '@rollup/pluginutils': 5.3.0(rollup@4.53.3) '@typescript-eslint/scope-manager': 8.42.0 '@typescript-eslint/typescript-estree': 8.42.0(typescript@5.9.3) @@ -5017,7 +5042,7 @@ snapshots: unplugin: 2.3.11 vue: 3.5.29(typescript@5.9.3) optionalDependencies: - vue-i18n: 11.2.8(vue@3.5.29(typescript@5.9.3)) + vue-i18n: 11.3.0(vue@3.5.29(typescript@5.9.3)) transitivePeerDependencies: - '@vue/compiler-dom' - eslint @@ -5025,14 +5050,14 @@ snapshots: - supports-color - typescript - '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.29)(vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))': + '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.2.8)(@vue/compiler-dom@3.5.29)(vue-i18n@11.3.0(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3))': dependencies: '@babel/parser': 7.29.0 optionalDependencies: '@intlify/shared': 11.2.8 '@vue/compiler-dom': 3.5.29 vue: 3.5.29(typescript@5.9.3) - vue-i18n: 11.2.8(vue@3.5.29(typescript@5.9.3)) + vue-i18n: 11.3.0(vue@3.5.29(typescript@5.9.3)) '@isaacs/cliui@8.0.2': dependencies: @@ -5430,14 +5455,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.1 - '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))': + '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))': dependencies: '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 tailwindcss: 4.2.1 - vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) - - '@trysound/sax@0.2.0': {} + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) '@tsconfig/node10@1.0.11': {} @@ -5500,7 +5523,7 @@ snapshots: dependencies: undici-types: 7.16.0 - '@types/node@25.3.3': + '@types/node@25.3.5': dependencies: undici-types: 7.18.2 @@ -5551,15 +5574,15 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.3.3)(lightningcss@1.31.1)(terser@5.39.0))(vue@3.5.29(typescript@5.9.3))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@25.3.5)(lightningcss@1.31.1)(terser@5.39.0))(vue@3.5.29(typescript@5.9.3))': dependencies: - vite: 5.4.21(@types/node@25.3.3)(lightningcss@1.31.1)(terser@5.39.0) + vite: 5.4.21(@types/node@25.3.5)(lightningcss@1.31.1)(terser@5.39.0) vue: 3.5.29(typescript@5.9.3) - '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 - vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) vue: 3.5.29(typescript@5.9.3) '@vitest/expect@4.0.18': @@ -5571,13 +5594,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))': + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) '@vitest/pretty-format@4.0.18': dependencies: @@ -5739,12 +5762,12 @@ snapshots: transitivePeerDependencies: - vue - '@vue-macros/devtools@0.4.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))': + '@vue-macros/devtools@0.4.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))': dependencies: sirv: 3.0.1 vue: 3.5.29(typescript@5.9.3) optionalDependencies: - vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) transitivePeerDependencies: - typescript @@ -7150,7 +7173,7 @@ snapshots: lightningcss-win32-arm64-msvc: 1.31.1 lightningcss-win32-x64-msvc: 1.31.1 - lint-staged@16.3.1: + lint-staged@16.3.2: dependencies: commander: 14.0.3 listr2: 9.0.5 @@ -7583,6 +7606,8 @@ snapshots: dependencies: tslib: 2.8.1 + sax@1.5.0: {} + saxes@6.0.0: dependencies: xmlchars: 2.2.0 @@ -7744,15 +7769,15 @@ snapshots: dependencies: has-flag: 4.0.0 - svgo@3.3.2: + svgo@3.3.3: dependencies: - '@trysound/sax': 0.2.0 commander: 7.2.0 css-select: 5.1.0 css-tree: 2.3.1 css-what: 6.1.0 csso: 5.0.5 picocolors: 1.1.1 + sax: 1.5.0 symbol-tree@3.2.4: {} @@ -7829,14 +7854,14 @@ snapshots: transitivePeerDependencies: - typescript - ts-node@10.9.2(@types/node@25.3.3)(typescript@5.9.3): + ts-node@10.9.2(@types/node@25.3.5)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 25.3.3 + '@types/node': 25.3.5 acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 @@ -7935,12 +7960,12 @@ snapshots: optionalDependencies: '@vueuse/core': 14.2.1(vue@3.5.29(typescript@5.9.3)) - unplugin-combine@1.2.1(esbuild@0.27.1)(rollup@4.53.3)(unplugin@1.16.1)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)): + unplugin-combine@1.2.1(esbuild@0.27.1)(rollup@4.53.3)(unplugin@1.16.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)): optionalDependencies: esbuild: 0.27.1 rollup: 4.53.3 unplugin: 1.16.1 - vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) unplugin-icons@23.0.1(@vue/compiler-sfc@3.5.29): dependencies: @@ -7983,7 +8008,7 @@ snapshots: transitivePeerDependencies: - vue - unplugin-vue-macros@2.14.5(@vueuse/core@14.2.1(vue@3.5.29(typescript@5.9.3)))(esbuild@0.27.1)(rollup@4.53.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3)): + unplugin-vue-macros@2.14.5(@vueuse/core@14.2.1(vue@3.5.29(typescript@5.9.3)))(esbuild@0.27.1)(rollup@4.53.3)(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3)): dependencies: '@vue-macros/better-define': 1.11.4(vue@3.5.29(typescript@5.9.3)) '@vue-macros/boolean-prop': 0.5.5(vue@3.5.29(typescript@5.9.3)) @@ -7998,7 +8023,7 @@ snapshots: '@vue-macros/define-render': 1.6.6(vue@3.5.29(typescript@5.9.3)) '@vue-macros/define-slots': 1.2.6(vue@3.5.29(typescript@5.9.3)) '@vue-macros/define-stylex': 0.2.3(vue@3.5.29(typescript@5.9.3)) - '@vue-macros/devtools': 0.4.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) + '@vue-macros/devtools': 0.4.1(typescript@5.9.3)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) '@vue-macros/export-expose': 0.3.5(vue@3.5.29(typescript@5.9.3)) '@vue-macros/export-props': 0.6.5(vue@3.5.29(typescript@5.9.3)) '@vue-macros/export-render': 0.3.5(vue@3.5.29(typescript@5.9.3)) @@ -8015,7 +8040,7 @@ snapshots: '@vue-macros/short-vmodel': 1.5.5(vue@3.5.29(typescript@5.9.3)) '@vue-macros/volar': 0.30.15(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3)) unplugin: 1.16.1 - unplugin-combine: 1.2.1(esbuild@0.27.1)(rollup@4.53.3)(unplugin@1.16.1)(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) + unplugin-combine: 1.2.1(esbuild@0.27.1)(rollup@4.53.3)(unplugin@1.16.1)(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) unplugin-vue-define-options: 1.5.5(vue@3.5.29(typescript@5.9.3)) vue: 3.5.29(typescript@5.9.3) transitivePeerDependencies: @@ -8071,33 +8096,36 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-plugin-vue-layouts@0.11.0(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)): + vite-plugin-vue-layouts@0.11.0(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2))(vue-router@5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)): dependencies: debug: 4.4.0 fast-glob: 3.3.3 - vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) vue: 3.5.29(typescript@5.9.3) vue-router: 5.0.3(@vue/compiler-sfc@3.5.29)(pinia@3.0.4(typescript@5.9.3)(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) transitivePeerDependencies: - supports-color - vite-svg-loader@5.1.0(vue@3.5.29(typescript@5.9.3)): + vite-svg-loader@5.1.1(vue@3.5.29(typescript@5.9.3)): dependencies: - svgo: 3.3.2 + debug: 4.4.3 + svgo: 3.3.3 vue: 3.5.29(typescript@5.9.3) + transitivePeerDependencies: + - supports-color - vite@5.4.21(@types/node@25.3.3)(lightningcss@1.31.1)(terser@5.39.0): + vite@5.4.21(@types/node@25.3.5)(lightningcss@1.31.1)(terser@5.39.0): dependencies: esbuild: 0.25.10 postcss: 8.5.6 rollup: 4.52.4 optionalDependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 fsevents: 2.3.3 lightningcss: 1.31.1 terser: 5.39.0 - vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2): + vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2): dependencies: esbuild: 0.27.1 fdir: 6.5.0(picomatch@4.0.3) @@ -8106,7 +8134,7 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.31.1 @@ -8114,7 +8142,7 @@ snapshots: tsx: 4.19.2 yaml: 2.8.2 - vitepress@1.6.4(@algolia/client-search@5.23.4)(@types/node@25.3.3)(fuse.js@7.1.0)(lightningcss@1.31.1)(postcss@8.5.6)(react@18.3.1)(search-insights@2.17.3)(sortablejs@1.15.7)(terser@5.39.0)(typescript@5.9.3): + vitepress@1.6.4(@algolia/client-search@5.23.4)(@types/node@25.3.5)(fuse.js@7.1.0)(lightningcss@1.31.1)(postcss@8.5.6)(react@18.3.1)(search-insights@2.17.3)(sortablejs@1.15.7)(terser@5.39.0)(typescript@5.9.3): dependencies: '@docsearch/css': 3.8.2 '@docsearch/js': 3.8.2(@algolia/client-search@5.23.4)(react@18.3.1)(search-insights@2.17.3) @@ -8123,7 +8151,7 @@ snapshots: '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.3.3)(lightningcss@1.31.1)(terser@5.39.0))(vue@3.5.29(typescript@5.9.3)) + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@25.3.5)(lightningcss@1.31.1)(terser@5.39.0))(vue@3.5.29(typescript@5.9.3)) '@vue/devtools-api': 7.7.2 '@vue/shared': 3.5.18 '@vueuse/core': 12.8.2(typescript@5.9.3) @@ -8132,7 +8160,7 @@ snapshots: mark.js: 8.11.1 minisearch: 7.1.2 shiki: 2.5.0 - vite: 5.4.21(@types/node@25.3.3)(lightningcss@1.31.1)(terser@5.39.0) + vite: 5.4.21(@types/node@25.3.5)(lightningcss@1.31.1)(terser@5.39.0) vue: 3.5.29(typescript@5.9.3) optionalDependencies: postcss: 8.5.6 @@ -8163,10 +8191,10 @@ snapshots: - typescript - universal-cookie - vitest@4.0.18(@types/node@25.3.3)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2): + vitest@4.0.18(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -8183,10 +8211,10 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.3.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.3.3 + '@types/node': 25.3.5 jsdom: 28.1.0 transitivePeerDependencies: - jiti @@ -8207,10 +8235,11 @@ snapshots: vue-component-type-helpers@3.2.5: {} - vue-i18n@11.2.8(vue@3.5.29(typescript@5.9.3)): + vue-i18n@11.3.0(vue@3.5.29(typescript@5.9.3)): dependencies: - '@intlify/core-base': 11.2.8 - '@intlify/shared': 11.2.8 + '@intlify/core-base': 11.3.0 + '@intlify/devtools-types': 11.3.0 + '@intlify/shared': 11.3.0 '@vue/devtools-api': 6.6.4 vue: 3.5.29(typescript@5.9.3) diff --git a/protos/types.proto b/protos/types.proto index b3d30795..1bc0a631 100644 --- a/protos/types.proto +++ b/protos/types.proto @@ -111,4 +111,5 @@ message NotificationDispatcher { string type = 3; string url = 4; string template = 5; + map headers = 6; } diff --git a/types/notification.go b/types/notification.go index 2f27ce42..f3a8b27c 100644 --- a/types/notification.go +++ b/types/notification.go @@ -71,6 +71,7 @@ type DispatcherConfig struct { Type string URL string Template string + Headers map[string]string APIKey string Prefix string ExpiresAt *time.Time