Merge branch 'master' into claude/implement-mcp-go-server-jebjr

This commit is contained in:
Amir Raminfar
2026-03-09 10:29:48 -07:00
committed by GitHub
46 changed files with 554 additions and 209 deletions
+8 -8
View File
@@ -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: .
+5 -5
View File
@@ -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
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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']
@@ -57,6 +57,43 @@
></div>
</fieldset>
<!-- Custom Headers -->
<fieldset class="fieldset">
<legend class="fieldset-legend text-lg">
{{ $t("notifications.destination-form.headers") }}
<span class="text-base-content/60 ml-2 text-sm font-normal">{{
$t("notifications.destination-form.headers-hint")
}}</span>
</legend>
<div class="space-y-2">
<div v-for="(header, index) in headers" :key="header.key" class="flex items-center gap-2">
<input
v-model="header.name"
type="text"
class="input focus:input-primary flex-1 text-base"
:placeholder="$t('notifications.destination-form.header-name')"
/>
<input
v-model="header.value"
type="text"
class="input focus:input-primary flex-1 text-base"
:placeholder="$t('notifications.destination-form.header-value')"
/>
<button type="button" class="btn btn-ghost btn-sm btn-square" @click="headers.splice(index, 1)">
<carbon:close />
</button>
</div>
<button
type="button"
class="btn btn-ghost btn-sm"
@click="headers.push({ name: '', value: '', key: headerKeyCounter++ })"
>
<carbon:add />
{{ $t("notifications.destination-form.add-header") }}
</button>
</div>
</fieldset>
<!-- Error -->
<div v-if="error" class="alert alert-error">
<span>{{ error }}</span>
@@ -110,6 +147,12 @@ useFocus(nameInput, { initialValue: true });
const webhookUrl = ref(destination?.url ?? "");
const payloadFormat = ref<PayloadFormat>(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<string | null>(null);
@@ -143,6 +186,12 @@ onScopeDispose(() => {
templateEditorView?.destroy();
});
function headersToRecord(): Record<string, string> | 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
+1
View File
@@ -19,6 +19,7 @@ export interface Dispatcher {
type: string;
url?: string;
template?: string;
headers?: Record<string, string>;
prefix?: string;
expiresAt?: string;
}
+7 -1
View File
@@ -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:
+2
View File
@@ -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.
+3 -3
View File
@@ -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
+4
View File
@@ -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=
+1
View File
@@ -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,
}
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+32 -18
View File
@@ -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,
},
+1
View File
@@ -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,
}
}
+31 -29
View File
@@ -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
+108
View File
@@ -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{
+6 -1
View File
@@ -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 {
+3 -1
View File
@@ -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:
+6 -1
View File
@@ -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)
+6
View File
@@ -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{
+9 -8
View File
@@ -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
+5 -1
View File
@@ -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
+36 -20
View File
@@ -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{
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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: 취소
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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: Отмена
+5
View File
@@ -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
+5
View File
@@ -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
+5
View File
@@ -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: 取消
+5
View File
@@ -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: 取消
+8 -8
View File
@@ -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",
+124 -95
View File
@@ -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)
+1
View File
@@ -111,4 +111,5 @@ message NotificationDispatcher {
string type = 3;
string url = 4;
string template = 5;
map<string, string> headers = 6;
}
+1
View File
@@ -71,6 +71,7 @@ type DispatcherConfig struct {
Type string
URL string
Template string
Headers map[string]string
APIKey string
Prefix string
ExpiresAt *time.Time