mirror of
https://github.com/amir20/dozzle.git
synced 2026-08-07 11:44:45 +00:00
feat: implements web hooks and dispatchers with a complete new notificaiton page (#4349)
This commit is contained in:
@@ -41,6 +41,7 @@ COPY internal ./internal
|
||||
COPY types ./types
|
||||
COPY main.go ./
|
||||
COPY protos ./protos
|
||||
COPY graph ./graph
|
||||
COPY shared_key.pem shared_cert.pem ./
|
||||
|
||||
# Copy assets built with node
|
||||
|
||||
Vendored
+9
@@ -27,10 +27,13 @@ declare global {
|
||||
const controlledComputed: typeof import('@vueuse/core').controlledComputed
|
||||
const controlledRef: typeof import('@vueuse/core').controlledRef
|
||||
const createApp: typeof import('vue').createApp
|
||||
const createContainerHints: typeof import('./composable/exprEditor').createContainerHints
|
||||
const createDrawer: typeof import('./composable/drawer').createDrawer
|
||||
const createEventHook: typeof import('@vueuse/core').createEventHook
|
||||
const createExprEditor: typeof import('./composable/exprEditor').createExprEditor
|
||||
const createGlobalState: typeof import('@vueuse/core').createGlobalState
|
||||
const createInjectionState: typeof import('@vueuse/core').createInjectionState
|
||||
const createLogHints: typeof import('./composable/exprEditor').createLogHints
|
||||
const createPinia: typeof import('pinia').createPinia
|
||||
const createReactiveFn: typeof import('@vueuse/core').createReactiveFn
|
||||
const createRef: typeof import('@vueuse/core').createRef
|
||||
@@ -397,6 +400,9 @@ declare global {
|
||||
export type { LogStreamSource } from './composable/eventStreams'
|
||||
import('./composable/eventStreams')
|
||||
// @ts-ignore
|
||||
export type { ExprEditorOptions } from './composable/exprEditor'
|
||||
import('./composable/exprEditor')
|
||||
// @ts-ignore
|
||||
export type { Config, Profile } from './stores/config'
|
||||
import('./stores/config')
|
||||
// @ts-ignore
|
||||
@@ -436,10 +442,13 @@ declare module 'vue' {
|
||||
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
||||
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||
readonly createContainerHints: UnwrapRef<typeof import('./composable/exprEditor')['createContainerHints']>
|
||||
readonly createDrawer: UnwrapRef<typeof import('./composable/drawer')['createDrawer']>
|
||||
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
|
||||
readonly createExprEditor: UnwrapRef<typeof import('./composable/exprEditor')['createExprEditor']>
|
||||
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
|
||||
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
|
||||
readonly createLogHints: UnwrapRef<typeof import('./composable/exprEditor')['createLogHints']>
|
||||
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
||||
readonly createRef: UnwrapRef<typeof import('@vueuse/core')['createRef']>
|
||||
|
||||
Vendored
+15
@@ -11,6 +11,8 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AlertCard: typeof import('./components/Notification/AlertCard.vue')['default']
|
||||
AlertForm: typeof import('./components/Notification/AlertForm.vue')['default']
|
||||
Announcements: typeof import('./components/Announcements.vue')['default']
|
||||
BarChart: typeof import('./components/BarChart.vue')['default']
|
||||
'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default']
|
||||
@@ -41,6 +43,8 @@ declare module 'vue' {
|
||||
ContainerTable: typeof import('./components/ContainerTable.vue')['default']
|
||||
ContainerTitle: typeof import('./components/ContainerViewer/ContainerTitle.vue')['default']
|
||||
DateTime: typeof import('./components/common/DateTime.vue')['default']
|
||||
DestinationCard: typeof import('./components/Notification/DestinationCard.vue')['default']
|
||||
DestinationForm: typeof import('./components/Notification/DestinationForm.vue')['default']
|
||||
Dropdown: typeof import('./components/common/Dropdown.vue')['default']
|
||||
DropdownMenu: typeof import('./components/common/DropdownMenu.vue')['default']
|
||||
EventSource: typeof import('./components/LogViewer/EventSource.vue')['default']
|
||||
@@ -80,15 +84,21 @@ declare module 'vue' {
|
||||
'MaterialSymbols:terminal': typeof import('~icons/material-symbols/terminal')['default']
|
||||
'MaterialSymbolsLight:collapseAll': typeof import('~icons/material-symbols-light/collapse-all')['default']
|
||||
'Mdi:account': typeof import('~icons/mdi/account')['default']
|
||||
'Mdi:alert': typeof import('~icons/mdi/alert')['default']
|
||||
'Mdi:alertOutline': typeof import('~icons/mdi/alert-outline')['default']
|
||||
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
|
||||
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
|
||||
'Mdi:beer': typeof import('~icons/mdi/beer')['default']
|
||||
'Mdi:bell': typeof import('~icons/mdi/bell')['default']
|
||||
'Mdi:bellOutline': typeof import('~icons/mdi/bell-outline')['default']
|
||||
'Mdi:check': typeof import('~icons/mdi/check')['default']
|
||||
'Mdi:chevronDoubleDown': typeof import('~icons/mdi/chevron-double-down')['default']
|
||||
'Mdi:chevronDown': typeof import('~icons/mdi/chevron-down')['default']
|
||||
'Mdi:chevronLeft': typeof import('~icons/mdi/chevron-left')['default']
|
||||
'Mdi:chevronRight': typeof import('~icons/mdi/chevron-right')['default']
|
||||
'Mdi:clockOutline': typeof import('~icons/mdi/clock-outline')['default']
|
||||
'Mdi:close': typeof import('~icons/mdi/close')['default']
|
||||
'Mdi:cloud': typeof import('~icons/mdi/cloud')['default']
|
||||
'Mdi:cog': typeof import('~icons/mdi/cog')['default']
|
||||
'Mdi:docker': typeof import('~icons/mdi/docker')['default']
|
||||
'Mdi:gauge': typeof import('~icons/mdi/gauge')['default']
|
||||
@@ -99,7 +109,12 @@ declare module 'vue' {
|
||||
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
|
||||
'Mdi:lightningBolt': typeof import('~icons/mdi/lightning-bolt')['default']
|
||||
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
|
||||
'Mdi:packageVariantClosed': typeof import('~icons/mdi/package-variant-closed')['default']
|
||||
'Mdi:pencilOutline': typeof import('~icons/mdi/pencil-outline')['default']
|
||||
'Mdi:plus': typeof import('~icons/mdi/plus')['default']
|
||||
'Mdi:satelliteVariant': typeof import('~icons/mdi/satellite-variant')['default']
|
||||
'Mdi:trashCanOutline': typeof import('~icons/mdi/trash-can-outline')['default']
|
||||
'Mdi:webhook': typeof import('~icons/mdi/webhook')['default']
|
||||
MetricCard: typeof import('./components/MetricCard.vue')['default']
|
||||
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
|
||||
MultiContainerActionToolbar: typeof import('./components/LogViewer/MultiContainerActionToolbar.vue')['default']
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
<slot name="more-items"></slot>
|
||||
<Announcements />
|
||||
|
||||
<router-link
|
||||
:to="{ name: '/notifications' }"
|
||||
:aria-label="$t('title.notifications')"
|
||||
data-testid="notifications"
|
||||
class="btn btn-circle btn-sm"
|
||||
>
|
||||
<mdi:bell class="size-6" />
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
:to="{ name: '/settings' }"
|
||||
:aria-label="$t('title.settings')"
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type JSONObject, LogEntry } from "@/models/LogEntry";
|
||||
import type { LogEntry, LogMessage } from "@/models/LogEntry";
|
||||
|
||||
const { progress, currentDate } = useScrollContext();
|
||||
|
||||
const { messages } = defineProps<{
|
||||
messages: LogEntry<string | string[] | JSONObject>[];
|
||||
messages: LogEntry<LogMessage>[];
|
||||
}>();
|
||||
|
||||
const { containers } = useLoggingContext();
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-sm" :class="{ 'opacity-60': !alert.enabled }">
|
||||
<div class="card-body gap-4 p-5">
|
||||
<!-- Header -->
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<h4 class="text-lg font-semibold">{{ alert.name }}</h4>
|
||||
<span v-if="!alert.enabled" class="badge badge-warning badge-sm">{{ $t("notifications.alert.paused") }}</span>
|
||||
</div>
|
||||
<input type="checkbox" class="toggle toggle-primary" :checked="alert.enabled" @change="toggleEnabled" />
|
||||
</div>
|
||||
|
||||
<!-- Expressions -->
|
||||
<div class="text-base-content/80 grid grid-cols-[auto_1fr] gap-x-4 gap-y-2 text-sm">
|
||||
<span>{{ $t("notifications.alert.containers") }}</span>
|
||||
<code class="bg-base-200 text-base-content rounded px-2 py-0.5 font-mono">{{ alert.containerExpression }}</code>
|
||||
<span>{{ $t("notifications.alert.log-filter") }}</span>
|
||||
<code class="bg-base-200 text-base-content rounded px-2 py-0.5 font-mono">{{ alert.logExpression }}</code>
|
||||
<span>{{ $t("notifications.alert.destination") }}</span>
|
||||
<span v-if="alert.dispatcher" class="flex items-center gap-1.5">
|
||||
<mdi:webhook v-if="alert.dispatcher.type === 'webhook'" />
|
||||
<mdi:cloud v-else />
|
||||
{{ alert.dispatcher.name }}
|
||||
</span>
|
||||
<span v-else class="text-warning flex items-center gap-1.5">
|
||||
<mdi:alert-outline />
|
||||
{{ $t("notifications.alert.dispatcher-deleted") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="border-base-content/10 text-base-content/80 flex items-center justify-between border-t pt-3 text-sm">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="flex items-center gap-1">
|
||||
<mdi:package-variant-closed class="text-base" />
|
||||
{{ $t("notifications.alert.containers-count", { count: alert.triggeredContainers }) }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1">
|
||||
<mdi:bell-outline class="text-base" />
|
||||
{{ $t("notifications.alert.triggered-count", { count: alert.triggerCount }) }}
|
||||
</span>
|
||||
<span v-if="alert.lastTriggeredAt" class="flex items-center gap-1">
|
||||
<mdi:clock-outline class="text-base" />
|
||||
{{ $t("notifications.alert.last-triggered", { time: formatTimeAgo(alert.lastTriggeredAt) }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button class="btn btn-ghost btn-square" @click="editAlert">
|
||||
<mdi:pencil-outline />
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-square text-error" @click="deleteAlert" :disabled="isDeleting">
|
||||
<span v-if="isDeleting" class="loading loading-spinner loading-xs"></span>
|
||||
<mdi:trash-can-outline v-else />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useMutation, useQuery } from "@urql/vue";
|
||||
import {
|
||||
GetNotificationRulesDocument,
|
||||
DeleteNotificationRuleDocument,
|
||||
UpdateNotificationRuleDocument,
|
||||
type NotificationRule,
|
||||
} from "@/types/graphql";
|
||||
import AlertForm from "./AlertForm.vue";
|
||||
|
||||
const { alert } = defineProps<{
|
||||
alert: NotificationRule;
|
||||
}>();
|
||||
|
||||
const showDrawer = useDrawer();
|
||||
const deleteMutation = useMutation(DeleteNotificationRuleDocument);
|
||||
const updateMutation = useMutation(UpdateNotificationRuleDocument);
|
||||
const alertsQuery = useQuery({ query: GetNotificationRulesDocument, pause: true });
|
||||
|
||||
const isDeleting = ref(false);
|
||||
|
||||
function formatTimeAgo(dateStr: string): string {
|
||||
const date = new Date(dateStr);
|
||||
if (date.getFullYear() === 0) return "-";
|
||||
return toRelativeTime(date, undefined);
|
||||
}
|
||||
|
||||
async function toggleEnabled() {
|
||||
await updateMutation.executeMutation({
|
||||
id: alert.id,
|
||||
input: { enabled: !alert.enabled },
|
||||
});
|
||||
alertsQuery.executeQuery({ requestPolicy: "network-only" });
|
||||
}
|
||||
|
||||
function editAlert() {
|
||||
showDrawer(AlertForm, { alert, onCreated: () => alertsQuery.executeQuery({ requestPolicy: "network-only" }) }, "lg");
|
||||
}
|
||||
|
||||
async function deleteAlert() {
|
||||
isDeleting.value = true;
|
||||
try {
|
||||
await deleteMutation.executeMutation({ id: alert.id });
|
||||
alertsQuery.executeQuery({ requestPolicy: "network-only" });
|
||||
} finally {
|
||||
isDeleting.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div class="space-y-6 p-4">
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl font-bold">
|
||||
{{ isEditing ? $t("notifications.alert-form.edit-title") : $t("notifications.alert-form.create-title") }}
|
||||
</h2>
|
||||
<p class="text-base-content/60">{{ $t("notifications.alert-form.description") }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Name -->
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.alert-name") }}</legend>
|
||||
<input
|
||||
ref="alertNameInput"
|
||||
v-model="alertName"
|
||||
type="text"
|
||||
class="input focus:input-primary w-full text-base"
|
||||
:class="alertName.trim() ? 'input-primary' : ''"
|
||||
required
|
||||
:placeholder="$t('notifications.alert-form.alert-name-placeholder')"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<!-- Container Filter -->
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.container-filter") }}</legend>
|
||||
<div
|
||||
class="input focus-within:input-primary w-full focus-within:z-50"
|
||||
:class="
|
||||
containerExpression.trim() && !containerResult?.error
|
||||
? 'input-primary'
|
||||
: { 'input-error!': containerResult?.error }
|
||||
"
|
||||
>
|
||||
<div ref="containerEditorRef" class="w-full"></div>
|
||||
</div>
|
||||
<div v-if="containerResult" class="fieldset-label">
|
||||
<span v-if="containerResult.error" class="text-error">{{ containerResult.error }}</span>
|
||||
<span v-else-if="containerResult.containers?.length" class="text-success">
|
||||
<mdi:check class="inline" />
|
||||
{{
|
||||
$t("notifications.alert-form.containers-match", {
|
||||
count: containerResult.containers.length,
|
||||
names: containerResult.containers.map((c) => c.name).join(", "),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else class="text-warning">
|
||||
<mdi:alert class="inline" />
|
||||
{{ $t("notifications.alert-form.no-containers-match") }}
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Log Filter -->
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.log-filter") }}</legend>
|
||||
<div
|
||||
class="input focus-within:input-primary w-full focus-within:z-50"
|
||||
:class="logExpression.trim() && !logError ? 'input-primary' : { 'input-error!': logError }"
|
||||
>
|
||||
<div ref="logEditorRef" class="w-full"></div>
|
||||
</div>
|
||||
<div v-if="logError || logExpression" class="fieldset-label">
|
||||
<span v-if="logError" class="text-error">{{ logError }}</span>
|
||||
<span v-else-if="logMessages.length" class="text-success">
|
||||
<mdi:check class="inline" />
|
||||
{{ $t("notifications.alert-form.logs-match", { count: logTotalCount }) }}
|
||||
</span>
|
||||
<span v-else-if="!isLoading" class="text-warning">
|
||||
<mdi:alert class="inline" />
|
||||
{{ $t("notifications.alert-form.no-logs-match") }}
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Destination -->
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.alert-form.destination") }}</legend>
|
||||
<details class="dropdown w-full" ref="destinationDropdown">
|
||||
<summary class="btn btn-outline w-full justify-between" :class="{ 'btn-primary': selectedDestination }">
|
||||
<span class="flex items-center gap-2">
|
||||
<template v-if="selectedDestination">
|
||||
<mdi:webhook v-if="selectedDestination.type === 'webhook'" />
|
||||
<mdi:cloud v-else />
|
||||
{{ selectedDestination.name }}
|
||||
</template>
|
||||
<span v-else class="text-base-content/60">{{ $t("notifications.alert-form.select-destination") }}</span>
|
||||
</span>
|
||||
<carbon:caret-down />
|
||||
</summary>
|
||||
<ul class="dropdown-content menu bg-base-200 rounded-box z-50 mt-1 w-full border p-2 shadow-sm">
|
||||
<li v-for="dest in destinations" :key="dest.id">
|
||||
<a
|
||||
@click="
|
||||
dispatcherId = dest.id;
|
||||
destinationDropdown?.removeAttribute('open');
|
||||
"
|
||||
:class="{ active: dispatcherId === dest.id }"
|
||||
>
|
||||
<mdi:webhook v-if="dest.type === 'webhook'" />
|
||||
<mdi:cloud v-else />
|
||||
{{ dest.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
<div v-if="!destinations.length" class="fieldset-label">
|
||||
<span class="text-warning">
|
||||
<mdi:alert class="inline" />
|
||||
{{ $t("notifications.alert-form.no-destinations") }}
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Log Preview -->
|
||||
<div v-if="logMessages.length" class="mt-4">
|
||||
<div class="mb-2 text-lg">{{ $t("notifications.alert-form.preview") }}</div>
|
||||
<LogList
|
||||
:messages="logMessages"
|
||||
:last-selected-item="undefined"
|
||||
class="border-base-content/50 h-64 overflow-hidden rounded-lg border"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-if="saveError" class="alert alert-error">
|
||||
<span>{{ saveError }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex justify-end gap-2 pt-4">
|
||||
<button class="btn" @click="close?.()">{{ $t("notifications.alert-form.cancel") }}</button>
|
||||
<button class="btn btn-primary" :disabled="!canSave" @click="saveAlert">
|
||||
<span v-if="isSaving" class="loading loading-spinner loading-sm"></span>
|
||||
{{ isEditing ? $t("notifications.alert-form.save") : $t("notifications.alert-form.create") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { type LogEvent, type LogEntry, type LogMessage, asLogEntry } from "@/models/LogEntry";
|
||||
import { Container } from "@/models/Container";
|
||||
import type { ContainerJson } from "@/types/Container";
|
||||
import { createExprEditor, createContainerHints, createLogHints } from "@/composable/exprEditor";
|
||||
|
||||
import { useQuery, useMutation } from "@urql/vue";
|
||||
import {
|
||||
GetDispatchersDocument,
|
||||
CreateNotificationRuleDocument,
|
||||
ReplaceNotificationRuleDocument,
|
||||
PreviewExpressionDocument,
|
||||
} from "@/types/graphql";
|
||||
|
||||
export interface AlertData {
|
||||
id: number;
|
||||
name: string;
|
||||
containerExpression: string;
|
||||
logExpression: string;
|
||||
dispatcherId: number;
|
||||
}
|
||||
|
||||
const { close, onCreated, alert } = defineProps<{
|
||||
close?: () => void;
|
||||
onCreated?: () => void;
|
||||
alert?: AlertData;
|
||||
}>();
|
||||
|
||||
// GraphQL
|
||||
const dispatchersQuery = useQuery({ query: GetDispatchersDocument });
|
||||
const createMutation = useMutation(CreateNotificationRuleDocument);
|
||||
const replaceMutation = useMutation(ReplaceNotificationRuleDocument);
|
||||
const previewMutation = useMutation(PreviewExpressionDocument);
|
||||
|
||||
// Container store for autocomplete hints
|
||||
const containerStore = useContainerStore();
|
||||
const { containers } = storeToRefs(containerStore);
|
||||
const containerNames = computed(() => [
|
||||
...new Set(containers.value.filter((c) => c.state === "running").map((c) => c.name)),
|
||||
]);
|
||||
const imageNames = computed(() => [...new Set(containers.value.map((c) => c.image))]);
|
||||
const hostNames = computed(() => [...new Set(containers.value.map((c) => c.host))]);
|
||||
|
||||
// Template refs
|
||||
const alertNameInput = ref<HTMLInputElement>();
|
||||
const containerEditorRef = ref<HTMLElement>();
|
||||
const logEditorRef = ref<HTMLElement>();
|
||||
const destinationDropdown = ref<HTMLDetailsElement>();
|
||||
|
||||
// Form state
|
||||
const isEditing = computed(() => !!alert);
|
||||
const alertName = ref(alert?.name ?? "");
|
||||
const containerExpression = ref(alert?.containerExpression ?? "");
|
||||
const logExpression = ref(alert?.logExpression ?? "");
|
||||
const dispatcherId = ref(alert?.dispatcherId ?? 0);
|
||||
const destinations = computed(() => dispatchersQuery.data.value?.dispatchers ?? []);
|
||||
const selectedDestination = computed(() => destinations.value.find((d) => d.id === dispatcherId.value));
|
||||
useFocus(alertNameInput, { initialValue: true });
|
||||
|
||||
// Validation state
|
||||
interface ContainerResult {
|
||||
error?: string;
|
||||
containers?: Container[];
|
||||
}
|
||||
const containerResult = ref<ContainerResult | null>(null);
|
||||
const logError = ref<string | null>(null);
|
||||
const logTotalCount = ref(0);
|
||||
const logMessages = shallowRef<LogEntry<LogMessage>[]>([]);
|
||||
const isLoading = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const saveError = ref<string | null>(null);
|
||||
|
||||
const canSave = computed(
|
||||
() =>
|
||||
alertName.value.trim() &&
|
||||
containerExpression.value.trim() &&
|
||||
dispatcherId.value > 0 &&
|
||||
!containerResult.value?.error &&
|
||||
!logError.value &&
|
||||
!isSaving.value,
|
||||
);
|
||||
|
||||
async function saveAlert() {
|
||||
if (!canSave.value) return;
|
||||
|
||||
isSaving.value = true;
|
||||
saveError.value = null;
|
||||
|
||||
try {
|
||||
const input = {
|
||||
name: alertName.value.trim(),
|
||||
containerExpression: containerExpression.value,
|
||||
logExpression: logExpression.value,
|
||||
dispatcherId: dispatcherId.value!,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
const result = isEditing.value
|
||||
? await replaceMutation.executeMutation({ id: alert!.id, input })
|
||||
: await createMutation.executeMutation({ input });
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
|
||||
onCreated?.();
|
||||
close?.();
|
||||
} catch (e) {
|
||||
saveError.value = e instanceof Error ? e.message : "Failed to save alert";
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function validateExpressions() {
|
||||
if (!containerExpression.value && !logExpression.value) {
|
||||
containerResult.value = null;
|
||||
logError.value = null;
|
||||
logTotalCount.value = 0;
|
||||
logMessages.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
|
||||
try {
|
||||
const result = await previewMutation.executeMutation({
|
||||
input: {
|
||||
containerExpression: containerExpression.value,
|
||||
logExpression: logExpression.value || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
if (result.error) throw new Error(result.error.message);
|
||||
|
||||
const data = result.data?.previewExpression;
|
||||
if (!data) throw new Error("No data returned");
|
||||
|
||||
// Update container result
|
||||
containerResult.value = containerExpression.value
|
||||
? {
|
||||
error: data.containerError ?? undefined,
|
||||
containers: data.matchedContainers?.map((c) => Container.fromJSON(c as ContainerJson)),
|
||||
}
|
||||
: null;
|
||||
|
||||
// Update log result
|
||||
if (logExpression.value && !data.containerError) {
|
||||
logError.value = data.logError ?? null;
|
||||
logTotalCount.value = data.totalLogs;
|
||||
logMessages.value =
|
||||
data.matchedLogs?.map((event) =>
|
||||
asLogEntry({
|
||||
t: (event.type as LogEvent["t"]) ?? "single",
|
||||
m: event.message as LogEvent["m"],
|
||||
ts: event.timestamp,
|
||||
id: event.id,
|
||||
l: (event.level as LogEvent["l"]) ?? "unknown",
|
||||
s: (event.stream as LogEvent["s"]) ?? "unknown",
|
||||
c: "",
|
||||
rm: "",
|
||||
}),
|
||||
) ?? [];
|
||||
} else {
|
||||
logError.value = null;
|
||||
logTotalCount.value = 0;
|
||||
logMessages.value = [];
|
||||
}
|
||||
} catch (e) {
|
||||
containerResult.value = { error: e instanceof Error ? e.message : "Unknown error" };
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedValidate = useDebounceFn(validateExpressions, 500);
|
||||
|
||||
watch([containerExpression, logExpression], () => {
|
||||
isLoading.value = true;
|
||||
debouncedValidate();
|
||||
});
|
||||
|
||||
let containerEditorView: Awaited<ReturnType<typeof createExprEditor>> | undefined;
|
||||
let logEditorView: Awaited<ReturnType<typeof createExprEditor>> | undefined;
|
||||
|
||||
onMounted(async () => {
|
||||
if (containerEditorRef.value) {
|
||||
containerEditorView = await createExprEditor({
|
||||
parent: containerEditorRef.value,
|
||||
placeholder: 'name contains "api"',
|
||||
initialValue: alert?.containerExpression ?? "",
|
||||
getHints: () => createContainerHints(containerNames.value, imageNames.value, hostNames.value),
|
||||
onChange: (v) => (containerExpression.value = v),
|
||||
});
|
||||
}
|
||||
|
||||
if (logEditorRef.value) {
|
||||
logEditorView = await createExprEditor({
|
||||
parent: logEditorRef.value,
|
||||
placeholder: 'level == "error" && message contains "timeout"',
|
||||
initialValue: alert?.logExpression ?? "",
|
||||
getHints: createLogHints,
|
||||
onChange: (v) => (logExpression.value = v),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onScopeDispose(() => {
|
||||
containerEditorView?.destroy();
|
||||
logEditorView?.destroy();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="card bg-base-100">
|
||||
<div class="card-body gap-2 p-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-lg">
|
||||
<mdi:webhook v-if="destination.type === 'webhook'" class="text-lg" />
|
||||
<mdi:cloud v-else class="text-primary-content text-lg" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h4 class="font-semibold">{{ destination.name }}</h4>
|
||||
<p class="text-base-content/60 text-sm">
|
||||
{{
|
||||
destination.type === "webhook"
|
||||
? $t("notifications.destination.http-webhook")
|
||||
: $t("notifications.destination.dozzle-cloud")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="btn btn-ghost btn-sm btn-square">
|
||||
<ion:ellipsis-vertical />
|
||||
</label>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="menu dropdown-content rounded-box bg-base-100 border-base-content/20 z-50 w-40 border p-1 shadow-sm"
|
||||
>
|
||||
<li>
|
||||
<a @click="editDestination">{{ $t("notifications.destination.edit") }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="text-error" @click="deleteDestination">{{ $t("notifications.destination.delete") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useMutation, useQuery } from "@urql/vue";
|
||||
import { DeleteDispatcherDocument, GetDispatchersDocument, type Dispatcher } from "@/types/graphql";
|
||||
import DestinationForm from "./DestinationForm.vue";
|
||||
|
||||
const { destination } = defineProps<{
|
||||
destination: Dispatcher;
|
||||
}>();
|
||||
|
||||
const showDrawer = useDrawer();
|
||||
const deleteMutation = useMutation(DeleteDispatcherDocument);
|
||||
const dispatchersQuery = useQuery({ query: GetDispatchersDocument, pause: true });
|
||||
|
||||
function editDestination() {
|
||||
showDrawer(
|
||||
DestinationForm,
|
||||
{
|
||||
destination,
|
||||
onCreated: () => dispatchersQuery.executeQuery({ requestPolicy: "network-only" }),
|
||||
},
|
||||
"md",
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteDestination() {
|
||||
await deleteMutation.executeMutation({ id: destination.id });
|
||||
dispatchersQuery.executeQuery({ requestPolicy: "network-only" });
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div class="space-y-6 p-4">
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl font-bold">
|
||||
{{
|
||||
isEditing
|
||||
? $t("notifications.destination-form.edit-title")
|
||||
: $t("notifications.destination-form.create-title")
|
||||
}}
|
||||
</h2>
|
||||
<p class="text-base-content/60">{{ $t("notifications.destination-form.description") }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.destination-form.name") }}</legend>
|
||||
<input
|
||||
ref="nameInput"
|
||||
v-model="name"
|
||||
type="text"
|
||||
class="input focus:input-primary w-full text-base"
|
||||
required
|
||||
:class="{ 'input-primary': name.trim().length > 0 }"
|
||||
:placeholder="$t('notifications.destination-form.name-placeholder')"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<!-- Type Selection -->
|
||||
<fieldset class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.destination-form.type") }}</legend>
|
||||
<div class="space-y-3">
|
||||
<label
|
||||
class="card card-border 20 cursor-pointer transition-colors"
|
||||
:class="type === 'webhook' ? 'border-primary bg-primary/10' : ''"
|
||||
>
|
||||
<div class="card-body flex-row items-center gap-3 p-4">
|
||||
<input type="radio" v-model="type" value="webhook" class="radio radio-primary" />
|
||||
<div>
|
||||
<div class="font-semibold">{{ $t("notifications.destination-form.webhook-title") }}</div>
|
||||
<div class="text-base-content/60 text-sm">
|
||||
{{ $t("notifications.destination-form.webhook-description") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<label
|
||||
class="card card-border border-base-content/20 cursor-pointer transition-colors"
|
||||
:class="type === 'cloud' ? 'border-primary bg-primary/10' : ''"
|
||||
>
|
||||
<div class="card-body flex-row items-center gap-3 p-4">
|
||||
<input type="radio" v-model="type" value="cloud" class="radio radio-primary" />
|
||||
<div>
|
||||
<div class="font-semibold">{{ $t("notifications.destination-form.cloud-title") }}</div>
|
||||
<div class="text-base-content/60 text-sm">
|
||||
{{ $t("notifications.destination-form.cloud-description") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Webhook URL (only for webhook type) -->
|
||||
<fieldset v-if="type === 'webhook'" class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.destination-form.webhook-url") }}</legend>
|
||||
<input
|
||||
v-model="webhookUrl"
|
||||
type="url"
|
||||
class="input focus:input-primary w-full text-base"
|
||||
:class="{ 'input-primary': isValidUrl, 'input-error': webhookUrl.trim() && !isValidUrl }"
|
||||
:placeholder="$t('notifications.destination-form.webhook-url-placeholder')"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-if="error" class="alert alert-error">
|
||||
<span>{{ error }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex items-center gap-2 pt-4">
|
||||
<button class="btn" @click="testDestination" :disabled="!canTest || isTesting">
|
||||
<span v-if="isTesting" class="loading loading-spinner loading-sm"></span>
|
||||
{{ $t("notifications.destination-form.test") }}
|
||||
</button>
|
||||
<div class="flex-1"></div>
|
||||
<button class="btn" @click="close?.()">{{ $t("notifications.destination-form.cancel") }}</button>
|
||||
<button class="btn btn-primary" :disabled="!canSave" @click="saveDestination">
|
||||
<span v-if="isSaving" class="loading loading-spinner loading-sm"></span>
|
||||
{{ isEditing ? $t("notifications.destination-form.save") : $t("notifications.destination-form.add") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useMutation } from "@urql/vue";
|
||||
import { CreateDispatcherDocument, UpdateDispatcherDocument, type Dispatcher } from "@/types/graphql";
|
||||
|
||||
const { close, onCreated, destination } = defineProps<{
|
||||
close?: () => void;
|
||||
onCreated?: () => void;
|
||||
destination?: Dispatcher;
|
||||
}>();
|
||||
|
||||
const createMutation = useMutation(CreateDispatcherDocument);
|
||||
const updateMutation = useMutation(UpdateDispatcherDocument);
|
||||
|
||||
const isEditing = computed(() => !!destination);
|
||||
|
||||
const nameInput = ref<HTMLInputElement>();
|
||||
const name = ref(destination?.name ?? "");
|
||||
useFocus(nameInput, { initialValue: true });
|
||||
const type = ref<"webhook" | "cloud">((destination?.type as "webhook" | "cloud") ?? "webhook");
|
||||
const webhookUrl = ref(destination?.url ?? "");
|
||||
const isTesting = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const canTest = computed(() => {
|
||||
if (type.value === "webhook") {
|
||||
return webhookUrl.value.trim().length > 0;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const isValidUrl = computed(() => {
|
||||
try {
|
||||
new URL(webhookUrl.value.trim());
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
const canSave = computed(() => {
|
||||
if (isSaving.value) return false;
|
||||
if (!name.value.trim()) return false;
|
||||
if (type.value === "webhook" && !isValidUrl.value) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
async function testDestination() {
|
||||
isTesting.value = true;
|
||||
// TODO: Implement actual test when backend is ready
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
isTesting.value = false;
|
||||
}
|
||||
|
||||
async function saveDestination() {
|
||||
if (!canSave.value) return;
|
||||
|
||||
isSaving.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const input = {
|
||||
name: name.value.trim(),
|
||||
type: type.value,
|
||||
url: type.value === "webhook" ? webhookUrl.value.trim() : undefined,
|
||||
};
|
||||
|
||||
const result = isEditing.value
|
||||
? await updateMutation.executeMutation({ id: destination!.id, input })
|
||||
: await createMutation.executeMutation({ input });
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
|
||||
onCreated?.();
|
||||
close?.();
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : "Failed to save destination";
|
||||
} finally {
|
||||
isSaving.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -11,7 +11,7 @@
|
||||
<mdi:close class="swap-on" />
|
||||
</button>
|
||||
</form>
|
||||
<slot v-if="open"></slot>
|
||||
<slot v-if="open" :close="close"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
@@ -28,11 +28,16 @@ const { width } = defineProps<{
|
||||
width: DrawerWidth;
|
||||
}>();
|
||||
|
||||
function close() {
|
||||
panel.value?.close();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open: () => {
|
||||
open.value = true;
|
||||
panel.value?.showModal();
|
||||
},
|
||||
close,
|
||||
});
|
||||
|
||||
useEventListener(panel, "close", () => (open.value = false));
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import type { Completion } from "@codemirror/autocomplete";
|
||||
|
||||
export interface ExprEditorOptions {
|
||||
parent: HTMLElement;
|
||||
placeholder: string;
|
||||
initialValue: string;
|
||||
getHints: () => Completion[];
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
// Common operators for expr language
|
||||
const exprOperators: Completion[] = [
|
||||
{ label: "==", detail: "equals", type: "operator" },
|
||||
{ label: "!=", detail: "not equals", type: "operator" },
|
||||
{ label: "contains", detail: "string contains", type: "keyword" },
|
||||
{ label: "startsWith", detail: "string starts with", type: "keyword" },
|
||||
{ label: "endsWith", detail: "string ends with", type: "keyword" },
|
||||
{ label: "matches", detail: "regex match", type: "keyword" },
|
||||
{ label: "&&", detail: "logical AND", type: "operator" },
|
||||
{ label: "||", detail: "logical OR", type: "operator" },
|
||||
{ label: "!", detail: "logical NOT", type: "operator" },
|
||||
{ label: "in", detail: "membership test", type: "keyword" },
|
||||
{ label: "not in", detail: "negative membership", type: "keyword" },
|
||||
];
|
||||
|
||||
export function createContainerHints(
|
||||
containerNames: string[],
|
||||
imageNames: string[],
|
||||
hostNames: string[],
|
||||
): Completion[] {
|
||||
return [
|
||||
{ label: "name", detail: "container name", type: "property" },
|
||||
{ label: "id", detail: "container ID", type: "property" },
|
||||
{ label: "image", detail: "container image", type: "property" },
|
||||
{ label: "state", detail: "running, exited, etc.", type: "property" },
|
||||
{ label: "health", detail: "healthy, unhealthy, none", type: "property" },
|
||||
{ label: "host", detail: "docker host", type: "property" },
|
||||
{ label: "labels", detail: "container labels map", type: "property" },
|
||||
...exprOperators,
|
||||
{ label: '"running"', detail: "state value", type: "string" },
|
||||
{ label: '"exited"', detail: "state value", type: "string" },
|
||||
{ label: '"created"', detail: "state value", type: "string" },
|
||||
{ label: '"paused"', detail: "state value", type: "string" },
|
||||
{ label: '"healthy"', detail: "health value", type: "string" },
|
||||
{ label: '"unhealthy"', detail: "health value", type: "string" },
|
||||
{ label: '"none"', detail: "health value", type: "string" },
|
||||
...containerNames.map((name) => ({ label: `"${name}"`, detail: "container name", type: "string" }) as Completion),
|
||||
...imageNames.map((image) => ({ label: `"${image}"`, detail: "image name", type: "string" }) as Completion),
|
||||
...hostNames.map((host) => ({ label: `"${host}"`, detail: "host name", type: "string" }) as Completion),
|
||||
];
|
||||
}
|
||||
|
||||
export function createLogHints(): Completion[] {
|
||||
return [
|
||||
{ label: "message", detail: "log message content", type: "property" },
|
||||
{ label: "level", detail: "log level", type: "property" },
|
||||
{ label: "stream", detail: "stdout or stderr", type: "property" },
|
||||
{ label: "type", detail: "log type", type: "property" },
|
||||
{ label: "timestamp", detail: "unix timestamp", type: "property" },
|
||||
{ label: "id", detail: "log entry ID", type: "property" },
|
||||
...exprOperators,
|
||||
{ label: '"error"', detail: "level value", type: "string" },
|
||||
{ label: '"warn"', detail: "level value", type: "string" },
|
||||
{ label: '"info"', detail: "level value", type: "string" },
|
||||
{ label: '"debug"', detail: "level value", type: "string" },
|
||||
{ label: '"trace"', detail: "level value", type: "string" },
|
||||
{ label: '"stdout"', detail: "stream value", type: "string" },
|
||||
{ label: '"stderr"', detail: "stream value", type: "string" },
|
||||
{ label: 'level == "error"', detail: "match error logs", type: "text", boost: 10 },
|
||||
{ label: 'message contains ""', detail: "search in message", type: "text", boost: 10 },
|
||||
{ label: 'stream == "stderr"', detail: "match stderr", type: "text", boost: 10 },
|
||||
];
|
||||
}
|
||||
|
||||
function createAutocomplete(getHints: () => Completion[]) {
|
||||
return (context: any) => {
|
||||
const word = context.matchBefore(/[\w"=!&|]+/);
|
||||
if (!word && !context.explicit) return null;
|
||||
|
||||
const currentWord = word ? word.text.toLowerCase() : "";
|
||||
const hints = getHints();
|
||||
const filtered = currentWord ? hints.filter((h) => h.label.toLowerCase().includes(currentWord)) : hints;
|
||||
|
||||
return { from: word ? word.from : context.pos, options: filtered };
|
||||
};
|
||||
}
|
||||
|
||||
export async function createExprEditor(options: ExprEditorOptions) {
|
||||
const [
|
||||
{ EditorView, keymap, placeholder },
|
||||
{ EditorState },
|
||||
{ autocompletion, completionKeymap },
|
||||
{ HighlightStyle, syntaxHighlighting },
|
||||
{ tags },
|
||||
] = await Promise.all([
|
||||
import("@codemirror/view"),
|
||||
import("@codemirror/state"),
|
||||
import("@codemirror/autocomplete"),
|
||||
import("@codemirror/language"),
|
||||
import("@lezer/highlight"),
|
||||
]);
|
||||
|
||||
const editorTheme = EditorView.theme({
|
||||
"&": {
|
||||
backgroundColor: "var(--color-base-100)",
|
||||
color: "var(--color-base-content)",
|
||||
},
|
||||
".cm-content": {
|
||||
caretColor: "var(--color-primary)",
|
||||
},
|
||||
".cm-cursor": {
|
||||
borderLeftColor: "var(--color-primary)",
|
||||
},
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground": {
|
||||
backgroundColor: "var(--color-base-300)",
|
||||
},
|
||||
".cm-activeLine": {
|
||||
backgroundColor: "color-mix(in oklch, var(--color-base-200) 50%, transparent)",
|
||||
},
|
||||
".cm-gutters": {
|
||||
backgroundColor: "var(--color-base-200)",
|
||||
color: "color-mix(in oklch, var(--color-base-content) 50%, transparent)",
|
||||
border: "none",
|
||||
},
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: "var(--color-base-300)",
|
||||
},
|
||||
});
|
||||
|
||||
const highlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.keyword, color: "var(--color-primary)" },
|
||||
{ tag: tags.operator, color: "var(--color-secondary)" },
|
||||
{ tag: tags.string, color: "var(--color-success)" },
|
||||
{ tag: tags.number, color: "var(--color-warning)" },
|
||||
{ tag: tags.bool, color: "var(--color-warning)" },
|
||||
{ tag: tags.propertyName, color: "var(--color-info)" },
|
||||
{ tag: tags.variableName, color: "var(--color-base-content)" },
|
||||
{
|
||||
tag: tags.comment,
|
||||
color: "color-mix(in oklch, var(--color-base-content) 50%, transparent)",
|
||||
fontStyle: "italic",
|
||||
},
|
||||
]);
|
||||
|
||||
const state = EditorState.create({
|
||||
doc: options.initialValue,
|
||||
extensions: [
|
||||
EditorView.lineWrapping,
|
||||
placeholder(options.placeholder),
|
||||
autocompletion({
|
||||
override: [createAutocomplete(options.getHints)],
|
||||
activateOnTyping: true,
|
||||
}),
|
||||
keymap.of(completionKeymap),
|
||||
editorTheme,
|
||||
syntaxHighlighting(highlightStyle),
|
||||
EditorView.updateListener.of((update) => {
|
||||
if (update.docChanged && options.onChange) {
|
||||
options.onChange(update.view.state.doc.toString());
|
||||
}
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
return new EditorView({ state, parent: options.parent });
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
query GetNotificationRules {
|
||||
notificationRules {
|
||||
id
|
||||
name
|
||||
enabled
|
||||
containerExpression
|
||||
logExpression
|
||||
triggerCount
|
||||
triggeredContainers
|
||||
lastTriggeredAt
|
||||
dispatcher {
|
||||
id
|
||||
name
|
||||
type
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetDispatchers {
|
||||
dispatchers {
|
||||
id
|
||||
name
|
||||
type
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
mutation CreateNotificationRule($input: NotificationRuleInput!) {
|
||||
createNotificationRule(input: $input) {
|
||||
id
|
||||
name
|
||||
enabled
|
||||
containerExpression
|
||||
logExpression
|
||||
dispatcher {
|
||||
id
|
||||
name
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateNotificationRule($id: Int!, $input: NotificationRuleUpdateInput!) {
|
||||
updateNotificationRule(id: $id, input: $input) {
|
||||
id
|
||||
name
|
||||
enabled
|
||||
containerExpression
|
||||
logExpression
|
||||
dispatcher {
|
||||
id
|
||||
name
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation ReplaceNotificationRule($id: Int!, $input: NotificationRuleInput!) {
|
||||
replaceNotificationRule(id: $id, input: $input) {
|
||||
id
|
||||
name
|
||||
enabled
|
||||
containerExpression
|
||||
logExpression
|
||||
dispatcher {
|
||||
id
|
||||
name
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteNotificationRule($id: Int!) {
|
||||
deleteNotificationRule(id: $id)
|
||||
}
|
||||
|
||||
mutation CreateDispatcher($input: DispatcherInput!) {
|
||||
createDispatcher(input: $input) {
|
||||
id
|
||||
name
|
||||
type
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
mutation UpdateDispatcher($id: Int!, $input: DispatcherInput!) {
|
||||
updateDispatcher(id: $id, input: $input) {
|
||||
id
|
||||
name
|
||||
type
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
mutation DeleteDispatcher($id: Int!) {
|
||||
deleteDispatcher(id: $id)
|
||||
}
|
||||
|
||||
mutation PreviewExpression($input: PreviewInput!) {
|
||||
previewExpression(input: $input) {
|
||||
containerError
|
||||
logError
|
||||
matchedContainers {
|
||||
id
|
||||
name
|
||||
image
|
||||
host
|
||||
}
|
||||
matchedLogs {
|
||||
id
|
||||
type
|
||||
message
|
||||
timestamp
|
||||
level
|
||||
stream
|
||||
}
|
||||
totalLogs
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,10 @@
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
<SideDrawer ref="drawer" :width="drawerWidth">
|
||||
<SideDrawer ref="drawer" :width="drawerWidth" v-slot="{ close }">
|
||||
<Suspense :timeout="0">
|
||||
<component :is="drawerComponent" v-bind="drawerProperties" />
|
||||
<template #fallback> Loading dependencies... </template>
|
||||
<component :is="drawerComponent" v-bind="drawerProperties" :close="close" />
|
||||
<template #fallback> <span class="loading loading-spinner loading-sm"></span></template>
|
||||
</Suspense>
|
||||
</SideDrawer>
|
||||
<ToastModal />
|
||||
|
||||
@@ -151,3 +151,44 @@ body {
|
||||
.splitpanes--vertical .splitpanes__pane {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* CodeMirror autocomplete tooltip styles */
|
||||
.cm-tooltip {
|
||||
@apply bg-base-200! border-base-content/40! min-w-96 rounded-sm border shadow-md;
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete ul {
|
||||
@apply font-sans;
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete ul li {
|
||||
@apply my-1 px-2;
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete ul li[aria-selected] {
|
||||
@apply bg-primary/20 text-base-content!;
|
||||
}
|
||||
|
||||
.cm-completionLabel {
|
||||
@apply text-base-content!;
|
||||
}
|
||||
|
||||
.cm-completionDetail {
|
||||
@apply text-base-content/60! ml-2 italic;
|
||||
}
|
||||
|
||||
.cm-completionMatchedText {
|
||||
@apply text-primary! font-bold no-underline;
|
||||
}
|
||||
|
||||
.cm-completionIcon {
|
||||
@apply mr-2 opacity-70;
|
||||
}
|
||||
|
||||
.cm-editor.cm-focused {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ContainerHealth, ContainerStat, ContainerState } from "@/types/Container";
|
||||
import type { ContainerHealth, ContainerJson, ContainerStat, ContainerState } from "@/types/Container";
|
||||
import { useExponentialMovingAverage, useSimpleRefHistory } from "@/utils";
|
||||
import { Ref } from "vue";
|
||||
|
||||
@@ -122,4 +122,24 @@ export class Container {
|
||||
this._stat = stat;
|
||||
}
|
||||
}
|
||||
|
||||
static fromJSON(c: ContainerJson): Container {
|
||||
return new Container(
|
||||
c.id,
|
||||
new Date(c.created),
|
||||
new Date(c.startedAt),
|
||||
new Date(c.finishedAt),
|
||||
c.image,
|
||||
c.name,
|
||||
c.command,
|
||||
c.host,
|
||||
c.labels,
|
||||
c.state,
|
||||
c.cpuLimit,
|
||||
c.memoryLimit,
|
||||
c.stats ?? [],
|
||||
c.group,
|
||||
c.health,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { type App } from "vue";
|
||||
import urql, { cacheExchange, fetchExchange } from "@urql/vue";
|
||||
import { withBase } from "@/stores/config";
|
||||
|
||||
export const install = (app: App) => {
|
||||
app.use(urql, {
|
||||
url: withBase("/api/graphql"),
|
||||
exchanges: [cacheExchange, fetchExchange],
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<PageWithLinks>
|
||||
<section>
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-2xl font-bold">{{ $t("notifications.title") }}</h2>
|
||||
<p class="text-base-content/60">{{ $t("notifications.description") }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Destinations Section -->
|
||||
<div class="mb-8">
|
||||
<h3 class="text-base-content/60 mb-4 font-semibold tracking-wide uppercase">
|
||||
{{ $t("notifications.destinations") }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<DestinationCard v-for="dest in dispatchers" :key="dest.id" :destination="dest" class="w-full md:w-72" />
|
||||
<!-- Add Destination Card -->
|
||||
<button
|
||||
class="card card-border border-base-content/30 hover:border-base-content/50 w-full cursor-pointer border-dashed transition-colors md:w-72"
|
||||
@click="openAddDestination"
|
||||
>
|
||||
<div class="card-body items-center justify-center gap-1 p-4">
|
||||
<mdi:plus class="text-2xl" />
|
||||
<span class="text-base-content/60 text-sm">{{ $t("notifications.add-destination") }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alerts Section -->
|
||||
<div>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h3 class="text-base-content/60 font-semibold tracking-wide uppercase">{{ $t("notifications.alerts") }}</h3>
|
||||
<button class="btn btn-ghost text-primary" @click="openCreateAlert">
|
||||
<mdi:plus />
|
||||
{{ $t("notifications.add") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter Tabs -->
|
||||
<div class="tabs tabs-box mb-6">
|
||||
<button class="tab" :class="{ 'tab-active': filter === 'all' }" @click="filter = 'all'">
|
||||
{{ $t("notifications.filter.all", { count: alerts.length }) }}
|
||||
</button>
|
||||
<button class="tab" :class="{ 'tab-active': filter === 'enabled' }" @click="filter = 'enabled'">
|
||||
{{ $t("notifications.filter.enabled", { count: enabledCount }) }}
|
||||
</button>
|
||||
<button class="tab" :class="{ 'tab-active': filter === 'paused' }" @click="filter = 'paused'">
|
||||
{{ $t("notifications.filter.paused", { count: pausedCount }) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Alerts List -->
|
||||
<div v-if="!alerts.length" class="text-base-content/60 py-4">
|
||||
{{ $t("notifications.no-alerts") }}
|
||||
</div>
|
||||
<div v-else class="space-y-4">
|
||||
<AlertCard v-for="alert in filteredAlerts" :key="alert.id" :alert="alert" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</PageWithLinks>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useQuery } from "@urql/vue";
|
||||
import { GetNotificationRulesDocument, GetDispatchersDocument, type NotificationRule } from "@/types/graphql";
|
||||
import AlertForm from "@/components/Notification/AlertForm.vue";
|
||||
import DestinationForm from "@/components/Notification/DestinationForm.vue";
|
||||
import DestinationCard from "@/components/Notification/DestinationCard.vue";
|
||||
|
||||
const showDrawer = useDrawer();
|
||||
|
||||
// GraphQL queries
|
||||
const alertsQuery = useQuery({ query: GetNotificationRulesDocument });
|
||||
const dispatchersQuery = useQuery({ query: GetDispatchersDocument });
|
||||
|
||||
// Computed data from queries
|
||||
const alerts = computed(() => alertsQuery.data.value?.notificationRules ?? []);
|
||||
const dispatchers = computed(() => dispatchersQuery.data.value?.dispatchers ?? []);
|
||||
|
||||
// Local state
|
||||
const filter = ref<"all" | "enabled" | "paused">("all");
|
||||
|
||||
const enabledCount = computed(() => alerts.value.filter((a: NotificationRule) => a.enabled).length);
|
||||
const pausedCount = computed(() => alerts.value.filter((a: NotificationRule) => !a.enabled).length);
|
||||
|
||||
const filteredAlerts = computed(() => {
|
||||
if (filter.value === "enabled") return alerts.value.filter((a: NotificationRule) => a.enabled);
|
||||
if (filter.value === "paused") return alerts.value.filter((a: NotificationRule) => !a.enabled);
|
||||
return alerts.value;
|
||||
});
|
||||
|
||||
function openCreateAlert() {
|
||||
showDrawer(AlertForm, { onCreated: () => alertsQuery.executeQuery({ requestPolicy: "network-only" }) }, "lg");
|
||||
}
|
||||
|
||||
function openAddDestination() {
|
||||
showDrawer(
|
||||
DestinationForm,
|
||||
{ onCreated: () => dispatchersQuery.executeQuery({ requestPolicy: "network-only" }) },
|
||||
"md",
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -140,28 +140,7 @@ export const useContainerStore = defineStore("container", () => {
|
||||
existing.name = c.name;
|
||||
});
|
||||
|
||||
containers.value = [
|
||||
...containers.value,
|
||||
...newContainers.map((c) => {
|
||||
return new Container(
|
||||
c.id,
|
||||
new Date(c.created),
|
||||
new Date(c.startedAt),
|
||||
new Date(c.finishedAt),
|
||||
c.image,
|
||||
c.name,
|
||||
c.command,
|
||||
c.host,
|
||||
c.labels,
|
||||
c.state,
|
||||
c.cpuLimit,
|
||||
c.memoryLimit,
|
||||
c.stats,
|
||||
c.group,
|
||||
c.health,
|
||||
);
|
||||
}),
|
||||
];
|
||||
containers.value = [...containers.value, ...newContainers.map(Container.fromJSON)];
|
||||
};
|
||||
|
||||
const currentContainer = (id: Ref<string>) => computed(() => allContainersById.value[id.value]);
|
||||
|
||||
Vendored
+13
@@ -86,6 +86,13 @@ declare module 'vue-router/auto-routes' {
|
||||
{ name: ParamValue<false> },
|
||||
| never
|
||||
>,
|
||||
'/notifications': RouteRecordInfo<
|
||||
'/notifications',
|
||||
'/notifications',
|
||||
Record<never, never>,
|
||||
Record<never, never>,
|
||||
| never
|
||||
>,
|
||||
'/owner/[name]': RouteRecordInfo<
|
||||
'/owner/[name]',
|
||||
'/owner/:name',
|
||||
@@ -188,6 +195,12 @@ declare module 'vue-router/auto-routes' {
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/notifications.vue': {
|
||||
routes:
|
||||
| '/notifications'
|
||||
views:
|
||||
| never
|
||||
}
|
||||
'assets/pages/owner/[name].vue': {
|
||||
routes:
|
||||
| '/owner/[name]'
|
||||
|
||||
@@ -0,0 +1,802 @@
|
||||
import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/core";
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
|
||||
export type Incremental<T> = T | { [P in keyof T]?: P extends " $fragmentName" | "__typename" ? T[P] : never };
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: { input: string; output: string };
|
||||
String: { input: string; output: string };
|
||||
Boolean: { input: boolean; output: boolean };
|
||||
Int: { input: number; output: number };
|
||||
Float: { input: number; output: number };
|
||||
Any: { input: unknown; output: unknown };
|
||||
Int64: { input: number; output: number };
|
||||
Map: { input: Record<string, string>; output: Record<string, string> };
|
||||
Time: { input: string; output: string };
|
||||
};
|
||||
|
||||
export type Container = {
|
||||
__typename?: "Container";
|
||||
command: Scalars["String"]["output"];
|
||||
created: Scalars["Time"]["output"];
|
||||
group?: Maybe<Scalars["String"]["output"]>;
|
||||
health?: Maybe<Scalars["String"]["output"]>;
|
||||
host?: Maybe<Scalars["String"]["output"]>;
|
||||
id: Scalars["String"]["output"];
|
||||
image: Scalars["String"]["output"];
|
||||
labels?: Maybe<Scalars["Map"]["output"]>;
|
||||
name: Scalars["String"]["output"];
|
||||
startedAt: Scalars["Time"]["output"];
|
||||
state: Scalars["String"]["output"];
|
||||
};
|
||||
|
||||
export type Dispatcher = {
|
||||
__typename?: "Dispatcher";
|
||||
id: Scalars["Int"]["output"];
|
||||
name: Scalars["String"]["output"];
|
||||
type: Scalars["String"]["output"];
|
||||
url?: Maybe<Scalars["String"]["output"]>;
|
||||
};
|
||||
|
||||
export type DispatcherInput = {
|
||||
name: Scalars["String"]["input"];
|
||||
type: Scalars["String"]["input"];
|
||||
url?: InputMaybe<Scalars["String"]["input"]>;
|
||||
};
|
||||
|
||||
export type LogEvent = {
|
||||
__typename?: "LogEvent";
|
||||
containerId?: Maybe<Scalars["String"]["output"]>;
|
||||
id: Scalars["Int"]["output"];
|
||||
level?: Maybe<Scalars["String"]["output"]>;
|
||||
message?: Maybe<Scalars["Any"]["output"]>;
|
||||
stream?: Maybe<Scalars["String"]["output"]>;
|
||||
timestamp: Scalars["Int64"]["output"];
|
||||
type?: Maybe<Scalars["String"]["output"]>;
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: "Mutation";
|
||||
createDispatcher: Dispatcher;
|
||||
createNotificationRule: NotificationRule;
|
||||
deleteDispatcher: Scalars["Boolean"]["output"];
|
||||
deleteNotificationRule: Scalars["Boolean"]["output"];
|
||||
previewExpression: PreviewResult;
|
||||
replaceNotificationRule: NotificationRule;
|
||||
updateDispatcher: Dispatcher;
|
||||
updateNotificationRule: NotificationRule;
|
||||
};
|
||||
|
||||
export type MutationCreateDispatcherArgs = {
|
||||
input: DispatcherInput;
|
||||
};
|
||||
|
||||
export type MutationCreateNotificationRuleArgs = {
|
||||
input: NotificationRuleInput;
|
||||
};
|
||||
|
||||
export type MutationDeleteDispatcherArgs = {
|
||||
id: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type MutationDeleteNotificationRuleArgs = {
|
||||
id: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type MutationPreviewExpressionArgs = {
|
||||
input: PreviewInput;
|
||||
};
|
||||
|
||||
export type MutationReplaceNotificationRuleArgs = {
|
||||
id: Scalars["Int"]["input"];
|
||||
input: NotificationRuleInput;
|
||||
};
|
||||
|
||||
export type MutationUpdateDispatcherArgs = {
|
||||
id: Scalars["Int"]["input"];
|
||||
input: DispatcherInput;
|
||||
};
|
||||
|
||||
export type MutationUpdateNotificationRuleArgs = {
|
||||
id: Scalars["Int"]["input"];
|
||||
input: NotificationRuleUpdateInput;
|
||||
};
|
||||
|
||||
export type NotificationRule = {
|
||||
__typename?: "NotificationRule";
|
||||
containerExpression: Scalars["String"]["output"];
|
||||
dispatcher?: Maybe<Dispatcher>;
|
||||
enabled: Scalars["Boolean"]["output"];
|
||||
id: Scalars["Int"]["output"];
|
||||
lastTriggeredAt?: Maybe<Scalars["Time"]["output"]>;
|
||||
logExpression: Scalars["String"]["output"];
|
||||
name: Scalars["String"]["output"];
|
||||
triggerCount: Scalars["Int64"]["output"];
|
||||
triggeredContainers: Scalars["Int"]["output"];
|
||||
};
|
||||
|
||||
export type NotificationRuleInput = {
|
||||
containerExpression: Scalars["String"]["input"];
|
||||
dispatcherId: Scalars["Int"]["input"];
|
||||
enabled: Scalars["Boolean"]["input"];
|
||||
logExpression: Scalars["String"]["input"];
|
||||
name: Scalars["String"]["input"];
|
||||
};
|
||||
|
||||
export type NotificationRuleUpdateInput = {
|
||||
containerExpression?: InputMaybe<Scalars["String"]["input"]>;
|
||||
dispatcherId?: InputMaybe<Scalars["Int"]["input"]>;
|
||||
enabled?: InputMaybe<Scalars["Boolean"]["input"]>;
|
||||
logExpression?: InputMaybe<Scalars["String"]["input"]>;
|
||||
name?: InputMaybe<Scalars["String"]["input"]>;
|
||||
};
|
||||
|
||||
export type PreviewInput = {
|
||||
containerExpression: Scalars["String"]["input"];
|
||||
logExpression?: InputMaybe<Scalars["String"]["input"]>;
|
||||
};
|
||||
|
||||
export type PreviewResult = {
|
||||
__typename?: "PreviewResult";
|
||||
containerError?: Maybe<Scalars["String"]["output"]>;
|
||||
logError?: Maybe<Scalars["String"]["output"]>;
|
||||
matchedContainers: Array<Container>;
|
||||
matchedLogs: Array<LogEvent>;
|
||||
totalLogs: Scalars["Int"]["output"];
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: "Query";
|
||||
dispatcher?: Maybe<Dispatcher>;
|
||||
dispatchers: Array<Dispatcher>;
|
||||
notificationRule?: Maybe<NotificationRule>;
|
||||
notificationRules: Array<NotificationRule>;
|
||||
};
|
||||
|
||||
export type QueryDispatcherArgs = {
|
||||
id: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type QueryNotificationRuleArgs = {
|
||||
id: Scalars["Int"]["input"];
|
||||
};
|
||||
|
||||
export type GetNotificationRulesQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type GetNotificationRulesQuery = {
|
||||
__typename?: "Query";
|
||||
notificationRules: Array<{
|
||||
__typename?: "NotificationRule";
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
containerExpression: string;
|
||||
logExpression: string;
|
||||
triggerCount: number;
|
||||
triggeredContainers: number;
|
||||
lastTriggeredAt?: string | null;
|
||||
dispatcher?: { __typename?: "Dispatcher"; id: number; name: string; type: string; url?: string | null } | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type GetDispatchersQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type GetDispatchersQuery = {
|
||||
__typename?: "Query";
|
||||
dispatchers: Array<{ __typename?: "Dispatcher"; id: number; name: string; type: string; url?: string | null }>;
|
||||
};
|
||||
|
||||
export type CreateNotificationRuleMutationVariables = Exact<{
|
||||
input: NotificationRuleInput;
|
||||
}>;
|
||||
|
||||
export type CreateNotificationRuleMutation = {
|
||||
__typename?: "Mutation";
|
||||
createNotificationRule: {
|
||||
__typename?: "NotificationRule";
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
containerExpression: string;
|
||||
logExpression: string;
|
||||
dispatcher?: { __typename?: "Dispatcher"; id: number; name: string; type: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type UpdateNotificationRuleMutationVariables = Exact<{
|
||||
id: Scalars["Int"]["input"];
|
||||
input: NotificationRuleUpdateInput;
|
||||
}>;
|
||||
|
||||
export type UpdateNotificationRuleMutation = {
|
||||
__typename?: "Mutation";
|
||||
updateNotificationRule: {
|
||||
__typename?: "NotificationRule";
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
containerExpression: string;
|
||||
logExpression: string;
|
||||
dispatcher?: { __typename?: "Dispatcher"; id: number; name: string; type: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type ReplaceNotificationRuleMutationVariables = Exact<{
|
||||
id: Scalars["Int"]["input"];
|
||||
input: NotificationRuleInput;
|
||||
}>;
|
||||
|
||||
export type ReplaceNotificationRuleMutation = {
|
||||
__typename?: "Mutation";
|
||||
replaceNotificationRule: {
|
||||
__typename?: "NotificationRule";
|
||||
id: number;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
containerExpression: string;
|
||||
logExpression: string;
|
||||
dispatcher?: { __typename?: "Dispatcher"; id: number; name: string; type: string } | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type DeleteNotificationRuleMutationVariables = Exact<{
|
||||
id: Scalars["Int"]["input"];
|
||||
}>;
|
||||
|
||||
export type DeleteNotificationRuleMutation = { __typename?: "Mutation"; deleteNotificationRule: boolean };
|
||||
|
||||
export type CreateDispatcherMutationVariables = Exact<{
|
||||
input: DispatcherInput;
|
||||
}>;
|
||||
|
||||
export type CreateDispatcherMutation = {
|
||||
__typename?: "Mutation";
|
||||
createDispatcher: { __typename?: "Dispatcher"; id: number; name: string; type: string; url?: string | null };
|
||||
};
|
||||
|
||||
export type UpdateDispatcherMutationVariables = Exact<{
|
||||
id: Scalars["Int"]["input"];
|
||||
input: DispatcherInput;
|
||||
}>;
|
||||
|
||||
export type UpdateDispatcherMutation = {
|
||||
__typename?: "Mutation";
|
||||
updateDispatcher: { __typename?: "Dispatcher"; id: number; name: string; type: string; url?: string | null };
|
||||
};
|
||||
|
||||
export type DeleteDispatcherMutationVariables = Exact<{
|
||||
id: Scalars["Int"]["input"];
|
||||
}>;
|
||||
|
||||
export type DeleteDispatcherMutation = { __typename?: "Mutation"; deleteDispatcher: boolean };
|
||||
|
||||
export type PreviewExpressionMutationVariables = Exact<{
|
||||
input: PreviewInput;
|
||||
}>;
|
||||
|
||||
export type PreviewExpressionMutation = {
|
||||
__typename?: "Mutation";
|
||||
previewExpression: {
|
||||
__typename?: "PreviewResult";
|
||||
containerError?: string | null;
|
||||
logError?: string | null;
|
||||
totalLogs: number;
|
||||
matchedContainers: Array<{
|
||||
__typename?: "Container";
|
||||
id: string;
|
||||
name: string;
|
||||
image: string;
|
||||
host?: string | null;
|
||||
}>;
|
||||
matchedLogs: Array<{
|
||||
__typename?: "LogEvent";
|
||||
id: number;
|
||||
type?: string | null;
|
||||
message?: unknown | null;
|
||||
timestamp: number;
|
||||
level?: string | null;
|
||||
stream?: string | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export const GetNotificationRulesDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "query",
|
||||
name: { kind: "Name", value: "GetNotificationRules" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "notificationRules" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "enabled" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "containerExpression" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logExpression" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "triggerCount" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "triggeredContainers" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "lastTriggeredAt" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "dispatcher" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "url" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<GetNotificationRulesQuery, GetNotificationRulesQueryVariables>;
|
||||
export const GetDispatchersDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "query",
|
||||
name: { kind: "Name", value: "GetDispatchers" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "dispatchers" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "url" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<GetDispatchersQuery, GetDispatchersQueryVariables>;
|
||||
export const CreateNotificationRuleDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "CreateNotificationRule" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "NotificationRuleInput" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "createNotificationRule" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "enabled" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "containerExpression" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logExpression" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "dispatcher" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<CreateNotificationRuleMutation, CreateNotificationRuleMutationVariables>;
|
||||
export const UpdateNotificationRuleDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "UpdateNotificationRule" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "Int" } } },
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "NotificationRuleUpdateInput" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "updateNotificationRule" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
},
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "enabled" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "containerExpression" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logExpression" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "dispatcher" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UpdateNotificationRuleMutation, UpdateNotificationRuleMutationVariables>;
|
||||
export const ReplaceNotificationRuleDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "ReplaceNotificationRule" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "Int" } } },
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
type: {
|
||||
kind: "NonNullType",
|
||||
type: { kind: "NamedType", name: { kind: "Name", value: "NotificationRuleInput" } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "replaceNotificationRule" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
},
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "enabled" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "containerExpression" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logExpression" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "dispatcher" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<ReplaceNotificationRuleMutation, ReplaceNotificationRuleMutationVariables>;
|
||||
export const DeleteNotificationRuleDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "DeleteNotificationRule" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "Int" } } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "deleteNotificationRule" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<DeleteNotificationRuleMutation, DeleteNotificationRuleMutationVariables>;
|
||||
export const CreateDispatcherDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "CreateDispatcher" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "DispatcherInput" } } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "createDispatcher" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "url" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<CreateDispatcherMutation, CreateDispatcherMutationVariables>;
|
||||
export const UpdateDispatcherDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "UpdateDispatcher" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "Int" } } },
|
||||
},
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "DispatcherInput" } } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "updateDispatcher" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
},
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "url" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<UpdateDispatcherMutation, UpdateDispatcherMutationVariables>;
|
||||
export const DeleteDispatcherDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "DeleteDispatcher" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "Int" } } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "deleteDispatcher" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "id" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "id" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<DeleteDispatcherMutation, DeleteDispatcherMutationVariables>;
|
||||
export const PreviewExpressionDocument = {
|
||||
kind: "Document",
|
||||
definitions: [
|
||||
{
|
||||
kind: "OperationDefinition",
|
||||
operation: "mutation",
|
||||
name: { kind: "Name", value: "PreviewExpression" },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: "VariableDefinition",
|
||||
variable: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
type: { kind: "NonNullType", type: { kind: "NamedType", name: { kind: "Name", value: "PreviewInput" } } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "previewExpression" },
|
||||
arguments: [
|
||||
{
|
||||
kind: "Argument",
|
||||
name: { kind: "Name", value: "input" },
|
||||
value: { kind: "Variable", name: { kind: "Name", value: "input" } },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "containerError" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "logError" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "matchedContainers" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "name" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "image" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "host" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "matchedLogs" },
|
||||
selectionSet: {
|
||||
kind: "SelectionSet",
|
||||
selections: [
|
||||
{ kind: "Field", name: { kind: "Name", value: "id" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "type" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "message" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "timestamp" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "level" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "stream" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "totalLogs" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<PreviewExpressionMutation, PreviewExpressionMutationVariables>;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import type { CodegenConfig } from "@graphql-codegen/cli";
|
||||
|
||||
const config: CodegenConfig = {
|
||||
schema: "graph/schema.graphqls",
|
||||
documents: ["assets/**/*.graphql"],
|
||||
generates: {
|
||||
"assets/types/graphql.ts": {
|
||||
plugins: ["typescript", "typescript-operations", "typed-document-node"],
|
||||
config: {
|
||||
scalars: {
|
||||
Time: "string",
|
||||
Int64: "number",
|
||||
Any: "unknown",
|
||||
Map: "Record<string, string>",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -8,7 +8,6 @@ require (
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
@@ -23,8 +22,10 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.17.86
|
||||
github.com/PuerkitoBio/goquery v1.11.0
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/expr-lang/expr v1.17.7
|
||||
github.com/go-chi/chi/v5 v5.2.4
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||
github.com/go-faker/faker/v4 v4.7.0
|
||||
@@ -32,8 +33,10 @@ require (
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/samber/lo v1.52.0
|
||||
github.com/vektah/gqlparser/v2 v2.5.31
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
github.com/yuin/goldmark v1.7.16
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/sync v0.19.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
@@ -45,6 +48,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/agnivade/levenshtein v1.2.1 // indirect
|
||||
github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
@@ -64,12 +68,12 @@ require (
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
@@ -91,7 +95,9 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.9 // indirect
|
||||
github.com/urfave/cli/v3 v3.6.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
@@ -100,11 +106,12 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/oauth2 v0.32.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
@@ -114,9 +121,10 @@ require (
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
go 1.25.6
|
||||
|
||||
tool github.com/99designs/gqlgen
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
github.com/99designs/gqlgen v0.17.86 h1:C8N3UTa5heXX6twl+b0AJyGkTwYL6dNmFrgZNLRcU6w=
|
||||
github.com/99designs/gqlgen v0.17.86/go.mod h1:KTrPl+vHA1IUzNlh4EYkl7+tcErL3MgKnhHrBcV74Fw=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y=
|
||||
github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||
github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo=
|
||||
github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8=
|
||||
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||
github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
|
||||
github.com/alexflint/go-arg v1.6.1 h1:uZogJ6VDBjcuosydKgvYYRhh9sRCusjOvoOLZopBlnA=
|
||||
github.com/alexflint/go-arg v1.6.1/go.mod h1:nQ0LFYftLJ6njcaee0sU+G0iS2+2XJQfA8I062D0LGc=
|
||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
@@ -44,28 +46,10 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
|
||||
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
||||
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.2.1+incompatible h1:aTSWVTDStpHbnRu0xBcGoJEjRf5EQKt6nik6Vif8sWw=
|
||||
github.com/docker/docker v28.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
|
||||
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.0+incompatible h1:ffS62aKWupCWdvcee7nBU9fhnmknOqDPaJAMtfK0ImQ=
|
||||
github.com/docker/docker v28.3.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.1+incompatible h1:20+BmuA9FXlCX4ByQ0vYJcUEnOmRM6XljDnFWR+jCyY=
|
||||
github.com/docker/docker v28.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA=
|
||||
github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.0+incompatible h1:ZdSQoRUE9XxhFI/B8YLvhnEFMmYN9Pp8Egd2qcaFk1E=
|
||||
github.com/docker/docker v28.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
@@ -74,37 +58,21 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8=
|
||||
github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.4 h1:WtFKPHwlywe8Srng8j2BhOD9312j9cGUxG1SP4V2cR4=
|
||||
github.com/go-chi/chi/v5 v5.2.4/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3 h1:50Uzmacu35/ZP9ER2Ht6SazwPsnLQ9LRJy6zTZJpHEo=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
|
||||
github.com/go-faker/faker/v4 v4.6.0 h1:6aOPzNptRiDwD14HuAnEtlTa+D1IfFuEHO8+vEFwjTs=
|
||||
github.com/go-faker/faker/v4 v4.6.0/go.mod h1:ZmrHuVtTTm2Em9e0Du6CJ9CADaLEzGXW62z1YqFH0m0=
|
||||
github.com/go-faker/faker/v4 v4.6.1 h1:xUyVpAjEtB04l6XFY0V/29oR332rOSPWV4lU8RwDt4k=
|
||||
github.com/go-faker/faker/v4 v4.6.1/go.mod h1:arSdxNCSt7mOhdk8tEolvHeIJ7eX4OX80wXjKKvkKBY=
|
||||
github.com/go-faker/faker/v4 v4.6.2 h1:IR1uQUYotFZnuTL7Iuy0FDGtHM5Rt1Q+2nipH9gnqKs=
|
||||
github.com/go-faker/faker/v4 v4.6.2/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
|
||||
github.com/go-faker/faker/v4 v4.7.0 h1:VboC02cXHl/NuQh5lM2W8b87yp4iFXIu59x4w0RZi4E=
|
||||
github.com/go-faker/faker/v4 v4.7.0/go.mod h1:u1dIRP5neLB6kTzgyVjdBOV5R1uP7BdxkcWk7tiKQXk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
@@ -117,43 +85,37 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -195,7 +157,6 @@ github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXy
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
@@ -205,10 +166,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
|
||||
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
|
||||
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
|
||||
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
|
||||
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@@ -219,25 +180,13 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.0.0 h1:F1za+MBXzDQtQq+OVgFsojSX4w66rsNDmQNebPFAncA=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.0.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.1.0 h1:x9eHRl4QhZFIPJ17yl4KKW9xLyVWbb3/Yq4SXpjF71U=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.1.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0 h1:dlxm77dZj2c3rxq0/XNvvUKISAmovoXF4a4qM6Wvkr0=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.2.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q=
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
@@ -246,13 +195,11 @@ github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -263,127 +210,62 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
|
||||
github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/vektah/gqlparser/v2 v2.5.31 h1:YhWGA1mfTjID7qJhd1+Vxhpk5HTgydrGU9IgkWBTJ7k=
|
||||
github.com/vektah/gqlparser/v2 v2.5.31/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
|
||||
github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo=
|
||||
github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
|
||||
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
|
||||
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.14 h1:9F3UqVQdZ5GG5y6TU0l1TbbDhZmqfevaOcinQt88Qi8=
|
||||
github.com/yuin/goldmark v1.7.14/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
@@ -392,59 +274,21 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE=
|
||||
golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
|
||||
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -457,22 +301,6 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
@@ -484,22 +312,6 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -511,24 +323,6 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||
@@ -536,74 +330,28 @@ golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
|
||||
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
|
||||
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
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/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
@@ -616,124 +364,25 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=
|
||||
k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=
|
||||
k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU=
|
||||
k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM=
|
||||
k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw=
|
||||
k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw=
|
||||
k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY=
|
||||
k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs=
|
||||
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8=
|
||||
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE=
|
||||
k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=
|
||||
k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=
|
||||
k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE=
|
||||
k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug=
|
||||
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
|
||||
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
|
||||
k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
|
||||
k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
|
||||
k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4=
|
||||
k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk=
|
||||
k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY=
|
||||
k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA=
|
||||
k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=
|
||||
k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
|
||||
k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ=
|
||||
k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4=
|
||||
k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
|
||||
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA=
|
||||
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=
|
||||
k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
|
||||
k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0=
|
||||
k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
|
||||
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
|
||||
k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE=
|
||||
k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
|
||||
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=
|
||||
k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=
|
||||
k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98=
|
||||
k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg=
|
||||
k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4=
|
||||
k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA=
|
||||
k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E=
|
||||
k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo=
|
||||
k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA=
|
||||
k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg=
|
||||
k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw=
|
||||
k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY=
|
||||
k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo=
|
||||
k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY=
|
||||
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
|
||||
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
|
||||
k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
|
||||
k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
|
||||
k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A=
|
||||
k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM=
|
||||
k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE=
|
||||
k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg=
|
||||
k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
|
||||
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
|
||||
k8s.io/metrics v0.32.3 h1:2vsBvw0v8rIIlczZ/lZ8Kcqk9tR6Fks9h+dtFNbc2a4=
|
||||
k8s.io/metrics v0.32.3/go.mod h1:9R1Wk5cb+qJpCQon9h52mgkVCcFeYxcY+YkumfwHVCU=
|
||||
k8s.io/metrics v0.33.0 h1:sKe5sC9qb1RakMhs8LWYNuN2ne6OTCWexj8Jos3rO2Y=
|
||||
k8s.io/metrics v0.33.0/go.mod h1:XewckTFXmE2AJiP7PT3EXaY7hi7bler3t2ZLyOdQYzU=
|
||||
k8s.io/metrics v0.33.1 h1:Ypd5ITCf+fM+LDNFk7hESXTc3vh02CQYGiwRoVRaGsM=
|
||||
k8s.io/metrics v0.33.1/go.mod h1:wK8cFTK5ykBdhL0Wy4RZwLH28XM7j/Klc+NQrMRWVxg=
|
||||
k8s.io/metrics v0.33.2 h1:gNCBmtnUMDMCRg9Ly5ehxP3OdKISMsOnh1vzk01iCgE=
|
||||
k8s.io/metrics v0.33.2/go.mod h1:yxoAosKGRsZisv3BGekC5W6T1J8XSV+PoUEevACRv7c=
|
||||
k8s.io/metrics v0.33.3 h1:9CcqBz15JZfISqwca33gdHS8I6XfsK1vA8WUdEnG70g=
|
||||
k8s.io/metrics v0.33.3/go.mod h1:Aw+cdg4AYHw0HvUY+lCyq40FOO84awrqvJRTw0cmXDs=
|
||||
k8s.io/metrics v0.33.4 h1:eJ6UdTpKTUQVZbKpUdm5ve39aPpAvvNwLrs13oQcWKc=
|
||||
k8s.io/metrics v0.33.4/go.mod h1:NO/lgFtyIPTurz56debdSh5qRqRfpO8MlkMpau1Ue8U=
|
||||
k8s.io/metrics v0.34.0 h1:nYSfG2+tnL6/MRC2I+sGHjtNEGoEoM/KktgGOoQFwws=
|
||||
k8s.io/metrics v0.34.0/go.mod h1:KCuXmotE0v4AvoARKUP8NC4lUnbK/Du1mluGdor5h4M=
|
||||
k8s.io/metrics v0.34.1 h1:374Rexmp1xxgRt64Bi0TsjAM8cA/Y8skwCoPdjtIslE=
|
||||
k8s.io/metrics v0.34.1/go.mod h1:Drf5kPfk2NJrlpcNdSiAAHn/7Y9KqxpRNagByM7Ei80=
|
||||
k8s.io/metrics v0.34.2 h1:zao91FNDVPRGIiHLO2vqqe21zZVPien1goyzn0hsz90=
|
||||
k8s.io/metrics v0.34.2/go.mod h1:Ydulln+8uZZctUM8yrUQX4rfq/Ay6UzsuXf24QJ37Vc=
|
||||
k8s.io/metrics v0.34.3 h1:zKco9A0q7Ibl3alcO1kqRandTt4GKwKGOBflYJTIBHc=
|
||||
k8s.io/metrics v0.34.3/go.mod h1:BWmkYCQ9x4I120OmCtMUeuXn0VTGkJLwBErneDL5aSQ=
|
||||
k8s.io/metrics v0.35.0 h1:xVFoqtAGm2dMNJAcB5TFZJPCen0uEqqNt52wW7ABbX8=
|
||||
k8s.io/metrics v0.35.0/go.mod h1:g2Up4dcBygZi2kQSEQVDByFs+VUwepJMzzQLJJLpq4M=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
||||
schema:
|
||||
- graph/*.graphqls
|
||||
|
||||
# Where should the generated server code go?
|
||||
exec:
|
||||
package: graph
|
||||
layout: single-file # Only other option is "follow-schema," ie multi-file.
|
||||
|
||||
# Only for single-file layout:
|
||||
filename: graph/generated.go
|
||||
|
||||
# Only for follow-schema layout:
|
||||
# dir: graph
|
||||
# filename_template: "{name}.generated.go"
|
||||
|
||||
# Optional: Maximum number of goroutines in concurrency to use per child resolvers(default: unlimited)
|
||||
# worker_limit: 1000
|
||||
|
||||
# Uncomment to enable federation
|
||||
# federation:
|
||||
# filename: graph/federation.go
|
||||
# package: graph
|
||||
# version: 2
|
||||
# options:
|
||||
# computed_requires: true
|
||||
|
||||
# Where should any generated models go?
|
||||
model:
|
||||
filename: graph/model/models_gen.go
|
||||
package: model
|
||||
|
||||
# Optional: Pass in a path to a new gotpl template to use for generating the models
|
||||
# model_template: [your/path/model.gotpl]
|
||||
|
||||
# Where should the resolver implementations go?
|
||||
resolver:
|
||||
package: graph
|
||||
layout: follow-schema # Only other option is "single-file."
|
||||
|
||||
# Only for single-file layout:
|
||||
# filename: graph/resolver.go
|
||||
|
||||
# Only for follow-schema layout:
|
||||
dir: graph
|
||||
filename_template: "{name}.resolvers.go"
|
||||
|
||||
# Optional: turn on to not generate template comments above resolvers
|
||||
# omit_template_comment: false
|
||||
# Optional: Pass in a path to a new gotpl template to use for generating resolvers
|
||||
# resolver_template: [your/path/resolver.gotpl]
|
||||
# Optional: turn on to avoid rewriting existing resolver(s) when generating
|
||||
# preserve_resolver: false
|
||||
|
||||
# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
|
||||
# struct_tag: json
|
||||
|
||||
# Optional: turn on to use []Thing instead of []*Thing
|
||||
# omit_slice_element_pointers: false
|
||||
|
||||
# Optional: turn on to omit Is<Name>() methods to interface and unions
|
||||
# omit_interface_checks: true
|
||||
|
||||
# Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function
|
||||
# omit_complexity: false
|
||||
|
||||
# Optional: turn on to not generate any file notice comments in generated files
|
||||
# omit_gqlgen_file_notice: false
|
||||
|
||||
# Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true.
|
||||
# omit_gqlgen_version_in_file_notice: false
|
||||
|
||||
# Optional: turn on to exclude root models such as Query and Mutation from the generated models file.
|
||||
# omit_root_models: false
|
||||
|
||||
# Optional: turn on to exclude resolver fields from the generated models file.
|
||||
# omit_resolver_fields: false
|
||||
|
||||
# Optional: turn off to make struct-type struct fields not use pointers
|
||||
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
|
||||
# struct_fields_always_pointers: true
|
||||
|
||||
# Optional: turn off to make resolvers return values instead of pointers for structs
|
||||
# resolvers_always_return_pointers: true
|
||||
|
||||
# Optional: turn on to return pointers instead of values in unmarshalInput
|
||||
# return_pointers_in_unmarshalinput: false
|
||||
|
||||
# Optional: wrap nullable input fields with Omittable
|
||||
# nullable_input_omittable: true
|
||||
|
||||
# Optional: set to speed up generation time by not performing a final validation pass.
|
||||
# skip_validation: true
|
||||
|
||||
# Optional: set to skip running `go mod tidy` when generating server code
|
||||
# skip_mod_tidy: true
|
||||
|
||||
# Optional: if this is set to true, argument directives that
|
||||
# decorate a field with a null value will still be called.
|
||||
#
|
||||
# This enables argumment directives to not just mutate
|
||||
# argument values but to set them even if they're null.
|
||||
call_argument_directives_with_null: true
|
||||
|
||||
# This enables gql server to use function syntax for execution context
|
||||
# instead of generating receiver methods of the execution context.
|
||||
# use_function_syntax_for_execution_context: true
|
||||
|
||||
# Optional: set build tags that will be used to load packages
|
||||
# go_build_tags:
|
||||
# - private
|
||||
# - enterprise
|
||||
|
||||
# Optional: set to modify the initialisms regarded for Go names
|
||||
# go_initialisms:
|
||||
# replace_defaults: false # if true, the default initialisms will get dropped in favor of the new ones instead of being added
|
||||
# initialisms: # List of initialisms to for Go names
|
||||
# - 'CC'
|
||||
# - 'BCC'
|
||||
|
||||
# gqlgen will search for any type names in the schema in these go packages
|
||||
# if they match it will use them, otherwise it will generate them.
|
||||
autobind:
|
||||
# - "github.com/amir20/dozzle/graph/model"
|
||||
|
||||
# This section declares type mapping between the GraphQL and go type systems
|
||||
#
|
||||
# The first line in each type will be used as defaults for resolver arguments and
|
||||
# modelgen, the others will be allowed when binding to fields. Configure them to
|
||||
# your liking
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.ID
|
||||
- github.com/99designs/gqlgen/graphql.Int
|
||||
- github.com/99designs/gqlgen/graphql.Int64
|
||||
- github.com/99designs/gqlgen/graphql.Int32
|
||||
# gqlgen provides a default GraphQL UUID convenience wrapper for github.com/google/uuid
|
||||
# but you can override this to provide your own GraphQL UUID implementation
|
||||
UUID:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.UUID
|
||||
|
||||
# The GraphQL spec explicitly states that the Int type is a signed 32-bit
|
||||
# integer. Using Go int or int64 to represent it can lead to unexpected
|
||||
# behavior, and some GraphQL tools like Apollo Router will fail when
|
||||
# communicating numbers that overflow 32-bits.
|
||||
#
|
||||
# You may choose to use the custom, built-in Int64 scalar to represent 64-bit
|
||||
# integers, or ignore the spec and bind Int to graphql.Int / graphql.Int64
|
||||
# (the default behavior of gqlgen). This is fine in simple use cases when you
|
||||
# do not need to worry about interoperability and only expect small numbers.
|
||||
Int:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.Int32
|
||||
Int64:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.Int
|
||||
- github.com/99designs/gqlgen/graphql.Int64
|
||||
Time:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.Time
|
||||
Any:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.Any
|
||||
Map:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.Map
|
||||
Container:
|
||||
model:
|
||||
- github.com/amir20/dozzle/internal/container.Container
|
||||
LogEvent:
|
||||
model:
|
||||
- github.com/amir20/dozzle/internal/container.LogEvent
|
||||
+6210
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/graph/model"
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
)
|
||||
|
||||
func subscriptionToNotificationRule(sub *notification.Subscription, dispatchers []notification.DispatcherConfig) *model.NotificationRule {
|
||||
var lastTriggeredAt *time.Time
|
||||
if t := sub.LastTriggeredAt.Load(); t != nil && !t.IsZero() {
|
||||
lastTriggeredAt = t
|
||||
}
|
||||
|
||||
// Find the dispatcher
|
||||
var dispatcher *model.Dispatcher
|
||||
for _, d := range dispatchers {
|
||||
if d.ID == sub.DispatcherID {
|
||||
dispatcher = dispatcherConfigToDispatcher(&d)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &model.NotificationRule{
|
||||
ID: int32(sub.ID),
|
||||
Name: sub.Name,
|
||||
Enabled: sub.Enabled,
|
||||
Dispatcher: dispatcher,
|
||||
LogExpression: sub.LogExpression,
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
TriggerCount: int(sub.TriggerCount.Load()),
|
||||
LastTriggeredAt: lastTriggeredAt,
|
||||
TriggeredContainers: int32(sub.TriggeredContainersCount()),
|
||||
}
|
||||
}
|
||||
|
||||
func dispatcherConfigToDispatcher(d *notification.DispatcherConfig) *model.Dispatcher {
|
||||
var url *string
|
||||
if d.URL != "" {
|
||||
url = &d.URL
|
||||
}
|
||||
return &model.Dispatcher{
|
||||
ID: int32(d.ID),
|
||||
Name: d.Name,
|
||||
Type: d.Type,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
// Error is a simple error type for GraphQL errors
|
||||
type Error struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
)
|
||||
|
||||
type Dispatcher struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type DispatcherInput struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type Mutation struct {
|
||||
}
|
||||
|
||||
type NotificationRule struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Dispatcher *Dispatcher `json:"dispatcher,omitempty"`
|
||||
LogExpression string `json:"logExpression"`
|
||||
ContainerExpression string `json:"containerExpression"`
|
||||
TriggerCount int `json:"triggerCount"`
|
||||
LastTriggeredAt *time.Time `json:"lastTriggeredAt,omitempty"`
|
||||
TriggeredContainers int32 `json:"triggeredContainers"`
|
||||
}
|
||||
|
||||
type NotificationRuleInput struct {
|
||||
Name string `json:"name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
DispatcherID int32 `json:"dispatcherId"`
|
||||
LogExpression string `json:"logExpression"`
|
||||
ContainerExpression string `json:"containerExpression"`
|
||||
}
|
||||
|
||||
type NotificationRuleUpdateInput struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
DispatcherID *int32 `json:"dispatcherId,omitempty"`
|
||||
LogExpression *string `json:"logExpression,omitempty"`
|
||||
ContainerExpression *string `json:"containerExpression,omitempty"`
|
||||
}
|
||||
|
||||
type PreviewInput struct {
|
||||
ContainerExpression string `json:"containerExpression"`
|
||||
LogExpression *string `json:"logExpression,omitempty"`
|
||||
}
|
||||
|
||||
type PreviewResult struct {
|
||||
ContainerError *string `json:"containerError,omitempty"`
|
||||
LogError *string `json:"logError,omitempty"`
|
||||
MatchedContainers []*container.Container `json:"matchedContainers"`
|
||||
MatchedLogs []*container.LogEvent `json:"matchedLogs"`
|
||||
TotalLogs int32 `json:"totalLogs"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//go:generate go run github.com/99designs/gqlgen generate
|
||||
|
||||
package graph
|
||||
|
||||
import (
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
)
|
||||
|
||||
// This file will not be regenerated automatically.
|
||||
//
|
||||
// It serves as dependency injection for your app, add any dependencies you require here.
|
||||
|
||||
type HostService interface {
|
||||
// Notification rules (subscriptions)
|
||||
Subscriptions() []*notification.Subscription
|
||||
AddSubscription(sub *notification.Subscription) error
|
||||
ReplaceSubscription(sub *notification.Subscription) error
|
||||
UpdateSubscription(id int, updates map[string]any) error
|
||||
RemoveSubscription(id int)
|
||||
|
||||
// Dispatchers
|
||||
Dispatchers() []notification.DispatcherConfig
|
||||
AddDispatcher(d dispatcher.Dispatcher) int
|
||||
UpdateDispatcher(id int, d dispatcher.Dispatcher)
|
||||
RemoveDispatcher(id int)
|
||||
|
||||
// Containers for preview
|
||||
ListAllContainers(labels container.ContainerLabels) ([]container.Container, []error)
|
||||
FindContainer(host string, id string, labels container.ContainerLabels) (*container_support.ContainerService, error)
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
HostService HostService
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
scalar Time
|
||||
scalar Int64
|
||||
scalar Any
|
||||
scalar Map
|
||||
|
||||
# Reuse existing domain types
|
||||
type Container {
|
||||
id: String!
|
||||
name: String!
|
||||
image: String!
|
||||
command: String!
|
||||
created: Time!
|
||||
startedAt: Time!
|
||||
state: String!
|
||||
health: String
|
||||
host: String
|
||||
labels: Map
|
||||
group: String
|
||||
}
|
||||
|
||||
type LogEvent {
|
||||
id: Int!
|
||||
type: String
|
||||
message: Any
|
||||
timestamp: Int64!
|
||||
level: String
|
||||
stream: String
|
||||
containerId: String
|
||||
}
|
||||
|
||||
# Notification-specific types
|
||||
type NotificationRule {
|
||||
id: Int!
|
||||
name: String!
|
||||
enabled: Boolean!
|
||||
dispatcher: Dispatcher
|
||||
logExpression: String!
|
||||
containerExpression: String!
|
||||
triggerCount: Int64!
|
||||
lastTriggeredAt: Time
|
||||
triggeredContainers: Int!
|
||||
}
|
||||
|
||||
type Dispatcher {
|
||||
id: Int!
|
||||
name: String!
|
||||
type: String!
|
||||
url: String
|
||||
}
|
||||
|
||||
type PreviewResult {
|
||||
containerError: String
|
||||
logError: String
|
||||
matchedContainers: [Container!]!
|
||||
matchedLogs: [LogEvent!]!
|
||||
totalLogs: Int!
|
||||
}
|
||||
|
||||
# Inputs
|
||||
input NotificationRuleInput {
|
||||
name: String!
|
||||
enabled: Boolean!
|
||||
dispatcherId: Int!
|
||||
logExpression: String!
|
||||
containerExpression: String!
|
||||
}
|
||||
|
||||
input NotificationRuleUpdateInput {
|
||||
name: String
|
||||
enabled: Boolean
|
||||
dispatcherId: Int
|
||||
logExpression: String
|
||||
containerExpression: String
|
||||
}
|
||||
|
||||
input DispatcherInput {
|
||||
name: String!
|
||||
type: String!
|
||||
url: String
|
||||
}
|
||||
|
||||
input PreviewInput {
|
||||
containerExpression: String!
|
||||
logExpression: String
|
||||
}
|
||||
|
||||
# Queries
|
||||
type Query {
|
||||
notificationRules: [NotificationRule!]!
|
||||
notificationRule(id: Int!): NotificationRule
|
||||
dispatchers: [Dispatcher!]!
|
||||
dispatcher(id: Int!): Dispatcher
|
||||
}
|
||||
|
||||
# Mutations
|
||||
type Mutation {
|
||||
createNotificationRule(input: NotificationRuleInput!): NotificationRule!
|
||||
updateNotificationRule(id: Int!, input: NotificationRuleUpdateInput!): NotificationRule!
|
||||
replaceNotificationRule(id: Int!, input: NotificationRuleInput!): NotificationRule!
|
||||
deleteNotificationRule(id: Int!): Boolean!
|
||||
|
||||
createDispatcher(input: DispatcherInput!): Dispatcher!
|
||||
updateDispatcher(id: Int!, input: DispatcherInput!): Dispatcher!
|
||||
deleteDispatcher(id: Int!): Boolean!
|
||||
|
||||
previewExpression(input: PreviewInput!): PreviewResult!
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
package graph
|
||||
|
||||
// This file will be automatically regenerated based on the schema, any resolver
|
||||
// implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.86
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/graph/model"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
)
|
||||
|
||||
// Labels is the resolver for the labels field.
|
||||
func (r *containerResolver) Labels(ctx context.Context, obj *container.Container) (map[string]any, error) {
|
||||
result := make(map[string]any)
|
||||
for k, v := range obj.Labels {
|
||||
result[k] = v
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ID is the resolver for the id field.
|
||||
func (r *logEventResolver) ID(ctx context.Context, obj *container.LogEvent) (int32, error) {
|
||||
return int32(obj.Id), nil
|
||||
}
|
||||
|
||||
// Type is the resolver for the type field.
|
||||
func (r *logEventResolver) Type(ctx context.Context, obj *container.LogEvent) (*string, error) {
|
||||
t := string(obj.Type)
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// CreateNotificationRule is the resolver for the createNotificationRule field.
|
||||
func (r *mutationResolver) CreateNotificationRule(ctx context.Context, input model.NotificationRuleInput) (*model.NotificationRule, error) {
|
||||
sub := ¬ification.Subscription{
|
||||
Name: input.Name,
|
||||
Enabled: input.Enabled,
|
||||
DispatcherID: int(input.DispatcherID),
|
||||
LogExpression: input.LogExpression,
|
||||
ContainerExpression: input.ContainerExpression,
|
||||
}
|
||||
|
||||
if err := r.HostService.AddSubscription(sub); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscriptionToNotificationRule(sub, r.HostService.Dispatchers()), nil
|
||||
}
|
||||
|
||||
// UpdateNotificationRule is the resolver for the updateNotificationRule field.
|
||||
func (r *mutationResolver) UpdateNotificationRule(ctx context.Context, id int32, input model.NotificationRuleUpdateInput) (*model.NotificationRule, error) {
|
||||
updates := make(map[string]any)
|
||||
if input.Name != nil {
|
||||
updates["name"] = *input.Name
|
||||
}
|
||||
if input.Enabled != nil {
|
||||
updates["enabled"] = *input.Enabled
|
||||
}
|
||||
if input.DispatcherID != nil {
|
||||
updates["dispatcherId"] = int(*input.DispatcherID)
|
||||
}
|
||||
if input.LogExpression != nil {
|
||||
updates["logExpression"] = *input.LogExpression
|
||||
}
|
||||
if input.ContainerExpression != nil {
|
||||
updates["containerExpression"] = *input.ContainerExpression
|
||||
}
|
||||
|
||||
if err := r.HostService.UpdateSubscription(int(id), updates); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch the updated subscription
|
||||
dispatchers := r.HostService.Dispatchers()
|
||||
for _, sub := range r.HostService.Subscriptions() {
|
||||
if sub.ID == int(id) {
|
||||
return subscriptionToNotificationRule(sub, dispatchers), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ReplaceNotificationRule is the resolver for the replaceNotificationRule field.
|
||||
func (r *mutationResolver) ReplaceNotificationRule(ctx context.Context, id int32, input model.NotificationRuleInput) (*model.NotificationRule, error) {
|
||||
sub := ¬ification.Subscription{
|
||||
ID: int(id),
|
||||
Name: input.Name,
|
||||
Enabled: input.Enabled,
|
||||
DispatcherID: int(input.DispatcherID),
|
||||
LogExpression: input.LogExpression,
|
||||
ContainerExpression: input.ContainerExpression,
|
||||
}
|
||||
|
||||
if err := r.HostService.ReplaceSubscription(sub); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return subscriptionToNotificationRule(sub, r.HostService.Dispatchers()), nil
|
||||
}
|
||||
|
||||
// DeleteNotificationRule is the resolver for the deleteNotificationRule field.
|
||||
func (r *mutationResolver) DeleteNotificationRule(ctx context.Context, id int32) (bool, error) {
|
||||
r.HostService.RemoveSubscription(int(id))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CreateDispatcher is the resolver for the createDispatcher field.
|
||||
func (r *mutationResolver) CreateDispatcher(ctx context.Context, input model.DispatcherInput) (*model.Dispatcher, error) {
|
||||
var d dispatcher.Dispatcher
|
||||
switch input.Type {
|
||||
case "webhook":
|
||||
url := ""
|
||||
if input.URL != nil {
|
||||
url = *input.URL
|
||||
}
|
||||
d = dispatcher.NewWebhookDispatcher(input.Name, url)
|
||||
default:
|
||||
return nil, &Error{Message: "unknown dispatcher type"}
|
||||
}
|
||||
|
||||
id := r.HostService.AddDispatcher(d)
|
||||
|
||||
return &model.Dispatcher{
|
||||
ID: int32(id),
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
URL: input.URL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateDispatcher is the resolver for the updateDispatcher field.
|
||||
func (r *mutationResolver) UpdateDispatcher(ctx context.Context, id int32, input model.DispatcherInput) (*model.Dispatcher, error) {
|
||||
var d dispatcher.Dispatcher
|
||||
switch input.Type {
|
||||
case "webhook":
|
||||
url := ""
|
||||
if input.URL != nil {
|
||||
url = *input.URL
|
||||
}
|
||||
d = dispatcher.NewWebhookDispatcher(input.Name, url)
|
||||
default:
|
||||
return nil, &Error{Message: "unknown dispatcher type"}
|
||||
}
|
||||
|
||||
r.HostService.UpdateDispatcher(int(id), d)
|
||||
|
||||
return &model.Dispatcher{
|
||||
ID: id,
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
URL: input.URL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteDispatcher is the resolver for the deleteDispatcher field.
|
||||
func (r *mutationResolver) DeleteDispatcher(ctx context.Context, id int32) (bool, error) {
|
||||
r.HostService.RemoveDispatcher(int(id))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// PreviewExpression is the resolver for the previewExpression field.
|
||||
func (r *mutationResolver) PreviewExpression(ctx context.Context, input model.PreviewInput) (*model.PreviewResult, error) {
|
||||
result := &model.PreviewResult{
|
||||
MatchedContainers: []*container.Container{},
|
||||
MatchedLogs: []*container.LogEvent{},
|
||||
}
|
||||
|
||||
// Compile and test container expression
|
||||
var containerProgram *vm.Program
|
||||
if input.ContainerExpression != "" {
|
||||
program, err := expr.Compile(input.ContainerExpression, expr.Env(notification.Container{}))
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
result.ContainerError = &errStr
|
||||
} else {
|
||||
containerProgram = program
|
||||
}
|
||||
}
|
||||
|
||||
// Compile and test log expression
|
||||
var logProgram *vm.Program
|
||||
if input.LogExpression != nil && *input.LogExpression != "" {
|
||||
program, err := expr.Compile(*input.LogExpression, expr.Env(notification.Log{}))
|
||||
if err != nil {
|
||||
errStr := err.Error()
|
||||
result.LogError = &errStr
|
||||
} else {
|
||||
logProgram = program
|
||||
}
|
||||
}
|
||||
|
||||
// If container expression is valid, find matching running containers
|
||||
if containerProgram != nil {
|
||||
containers, _ := r.HostService.ListAllContainers(container.ContainerLabels{})
|
||||
for _, c := range containers {
|
||||
if c.State != "running" {
|
||||
continue
|
||||
}
|
||||
nc := notification.FromContainerModel(c)
|
||||
evalResult, err := expr.Run(containerProgram, nc)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if match, ok := evalResult.(bool); ok && match {
|
||||
containerCopy := c
|
||||
result.MatchedContainers = append(result.MatchedContainers, &containerCopy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If log expression is valid and we have matching containers, fetch real logs
|
||||
if logProgram != nil && len(result.MatchedContainers) > 0 {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
const maxLogs = 10
|
||||
totalMatched := 0
|
||||
|
||||
for _, c := range result.MatchedContainers {
|
||||
if len(result.MatchedLogs) >= maxLogs {
|
||||
break
|
||||
}
|
||||
|
||||
containerService, err := r.HostService.FindContainer(c.Host, c.ID, container.ContainerLabels{})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Fetch recent logs (last 5 minutes)
|
||||
from := time.Now().Add(-5 * time.Minute)
|
||||
to := time.Now()
|
||||
|
||||
logChan, err := containerService.LogsBetweenDates(ctx, from, to, container.STDALL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for logEvent := range logChan {
|
||||
if logEvent == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert to notification.Log for expression evaluation
|
||||
l := notification.FromLogEvent(*logEvent)
|
||||
evalResult, err := expr.Run(logProgram, l)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if match, ok := evalResult.(bool); ok && match {
|
||||
totalMatched++
|
||||
if len(result.MatchedLogs) < maxLogs {
|
||||
result.MatchedLogs = append(result.MatchedLogs, logEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.TotalLogs = int32(totalMatched)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// NotificationRules is the resolver for the notificationRules field.
|
||||
func (r *queryResolver) NotificationRules(ctx context.Context) ([]*model.NotificationRule, error) {
|
||||
subscriptions := r.HostService.Subscriptions()
|
||||
dispatchers := r.HostService.Dispatchers()
|
||||
rules := make([]*model.NotificationRule, len(subscriptions))
|
||||
for i, sub := range subscriptions {
|
||||
rules[i] = subscriptionToNotificationRule(sub, dispatchers)
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
// NotificationRule is the resolver for the notificationRule field.
|
||||
func (r *queryResolver) NotificationRule(ctx context.Context, id int32) (*model.NotificationRule, error) {
|
||||
dispatchers := r.HostService.Dispatchers()
|
||||
for _, sub := range r.HostService.Subscriptions() {
|
||||
if sub.ID == int(id) {
|
||||
return subscriptionToNotificationRule(sub, dispatchers), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Dispatchers is the resolver for the dispatchers field.
|
||||
func (r *queryResolver) Dispatchers(ctx context.Context) ([]*model.Dispatcher, error) {
|
||||
dispatchers := r.HostService.Dispatchers()
|
||||
result := make([]*model.Dispatcher, len(dispatchers))
|
||||
for i, d := range dispatchers {
|
||||
result[i] = dispatcherConfigToDispatcher(&d)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Dispatcher is the resolver for the dispatcher field.
|
||||
func (r *queryResolver) Dispatcher(ctx context.Context, id int32) (*model.Dispatcher, error) {
|
||||
for _, d := range r.HostService.Dispatchers() {
|
||||
if d.ID == int(id) {
|
||||
return dispatcherConfigToDispatcher(&d), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Container returns ContainerResolver implementation.
|
||||
func (r *Resolver) Container() ContainerResolver { return &containerResolver{r} }
|
||||
|
||||
// LogEvent returns LogEventResolver implementation.
|
||||
func (r *Resolver) LogEvent() LogEventResolver { return &logEventResolver{r} }
|
||||
|
||||
// Mutation returns MutationResolver implementation.
|
||||
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
// Query returns QueryResolver implementation.
|
||||
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||
|
||||
type containerResolver struct{ *Resolver }
|
||||
type logEventResolver struct{ *Resolver }
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.3
|
||||
// protoc v6.33.4
|
||||
// source: rpc.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -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.3
|
||||
// - protoc v6.33.4
|
||||
// source: rpc.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.3
|
||||
// protoc v6.33.4
|
||||
// source: types.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package dispatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// Dispatcher is responsible for sending notifications to external systems
|
||||
type Dispatcher interface {
|
||||
// Send sends a notification to the configured destination
|
||||
Send(ctx context.Context, notification any) error
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package dispatcher
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WebhookDispatcher sends notifications to a webhook URL
|
||||
type WebhookDispatcher struct {
|
||||
Name string
|
||||
URL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewWebhookDispatcher creates a new webhook dispatcher
|
||||
func NewWebhookDispatcher(name, url string) *WebhookDispatcher {
|
||||
return &WebhookDispatcher{
|
||||
Name: name,
|
||||
URL: url,
|
||||
client: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends a notification to the webhook URL
|
||||
func (w *WebhookDispatcher) Send(ctx context.Context, notification any) error {
|
||||
payload, err := json.Marshal(notification)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal notification: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, w.URL, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := w.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send webhook: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("webhook returned non-success status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// ContainerMatcher is an interface for checking if a container should be listened to
|
||||
type ContainerMatcher interface {
|
||||
ShouldListenToContainer(c container.Container) bool
|
||||
}
|
||||
|
||||
// ContainerLogListener manages active log streams for containers across multiple clients
|
||||
type ContainerLogListener struct {
|
||||
clients []container_support.ClientService
|
||||
containerClients *xsync.Map[string, container_support.ClientService] // containerID -> owning client
|
||||
activeStreams *xsync.Map[string, context.CancelFunc] // containerID -> cancel function
|
||||
matcher ContainerMatcher
|
||||
logChannel chan *container.LogEvent
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewContainerLogListener creates a new listener for multiple clients
|
||||
func NewContainerLogListener(ctx context.Context, clients []container_support.ClientService) *ContainerLogListener {
|
||||
return &ContainerLogListener{
|
||||
clients: clients,
|
||||
containerClients: xsync.NewMap[string, container_support.ClientService](),
|
||||
activeStreams: xsync.NewMap[string, context.CancelFunc](),
|
||||
logChannel: make(chan *container.LogEvent, 1000),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins listening for container events and processes log streams
|
||||
func (l *ContainerLogListener) Start(matcher ContainerMatcher) error {
|
||||
l.matcher = matcher
|
||||
|
||||
// Subscribe to new containers from all clients
|
||||
containerChan := make(chan container.Container, 10)
|
||||
|
||||
// Get all current containers from all clients
|
||||
for _, client := range l.clients {
|
||||
containers, err := client.ListContainers(l.ctx, nil)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to list containers from client")
|
||||
continue
|
||||
}
|
||||
|
||||
// Start listening to containers that match
|
||||
for _, c := range containers {
|
||||
if l.matcher.ShouldListenToContainer(c) {
|
||||
l.startListening(c, client)
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to new containers from this client
|
||||
client.SubscribeContainersStarted(l.ctx, containerChan)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
return
|
||||
case c := <-containerChan:
|
||||
if l.matcher.ShouldListenToContainer(c) {
|
||||
l.startListeningByID(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateStreams updates which containers to listen to based on current matcher rules
|
||||
func (l *ContainerLogListener) UpdateStreams() error {
|
||||
// Get all current containers from all clients
|
||||
for _, client := range l.clients {
|
||||
containers, err := client.ListContainers(l.ctx, nil)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to list containers from client")
|
||||
continue
|
||||
}
|
||||
|
||||
// Check each container against matcher
|
||||
for _, c := range containers {
|
||||
shouldListen := l.matcher.ShouldListenToContainer(c)
|
||||
isListening := l.isListening(c.ID)
|
||||
|
||||
if shouldListen && !isListening {
|
||||
l.startListening(c, client)
|
||||
} else if !shouldListen && isListening {
|
||||
l.stopListening(c.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// startListening starts listening to a container's logs with a known client
|
||||
func (l *ContainerLogListener) startListening(c container.Container, client container_support.ClientService) {
|
||||
streamCtx, cancel := context.WithCancel(l.ctx)
|
||||
|
||||
// Only store if not already present
|
||||
_, loaded := l.activeStreams.LoadOrStore(c.ID, cancel)
|
||||
if loaded {
|
||||
cancel() // Already listening, cancel the new context
|
||||
return
|
||||
}
|
||||
|
||||
l.containerClients.Store(c.ID, client)
|
||||
|
||||
go func() {
|
||||
log.Info().Str("containerID", c.ID).Str("name", c.Name).Msg("Started listening to container")
|
||||
if err := client.StreamLogs(streamCtx, c, time.Now(), container.STDALL, l.logChannel); err != nil {
|
||||
log.Error().Err(err).Str("containerID", c.ID).Msg("Error streaming logs")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// startListeningByID finds the client for a container and starts listening
|
||||
func (l *ContainerLogListener) startListeningByID(c container.Container) {
|
||||
for _, client := range l.clients {
|
||||
containers, err := client.ListContainers(l.ctx, nil)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, container := range containers {
|
||||
if container.ID == c.ID {
|
||||
l.startListening(c, client)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Warn().Str("containerID", c.ID).Msg("Could not find client for container")
|
||||
}
|
||||
|
||||
// stopListening stops listening to a container's logs
|
||||
func (l *ContainerLogListener) stopListening(containerID string) {
|
||||
if cancel, exists := l.activeStreams.LoadAndDelete(containerID); exists {
|
||||
cancel()
|
||||
l.containerClients.Delete(containerID)
|
||||
log.Info().Str("containerID", containerID).Msg("Stopped listening to container")
|
||||
}
|
||||
}
|
||||
|
||||
// isListening returns true if listening to a container
|
||||
func (l *ContainerLogListener) isListening(containerID string) bool {
|
||||
_, exists := l.activeStreams.Load(containerID)
|
||||
return exists
|
||||
}
|
||||
|
||||
// FindContainer finds a container by ID using the client that owns it
|
||||
func (l *ContainerLogListener) FindContainer(ctx context.Context, id string, labels container.ContainerLabels) (container.Container, error) {
|
||||
client, exists := l.containerClients.Load(id)
|
||||
if !exists {
|
||||
return container.Container{}, fmt.Errorf("container %s not found in any client", id)
|
||||
}
|
||||
|
||||
return client.FindContainer(ctx, id, labels)
|
||||
}
|
||||
|
||||
// LogChannel returns the channel for log events
|
||||
func (l *ContainerLogListener) LogChannel() <-chan *container.LogEvent {
|
||||
return l.logChannel
|
||||
}
|
||||
|
||||
// ListContainers returns all containers from all clients
|
||||
func (l *ContainerLogListener) ListContainers() []container.Container {
|
||||
var result []container.Container
|
||||
for _, client := range l.clients {
|
||||
containers, err := client.ListContainers(l.ctx, nil)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to list containers from client")
|
||||
continue
|
||||
}
|
||||
result = append(result, containers...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
// Manager manages notification subscriptions and dispatches notifications
|
||||
type Manager struct {
|
||||
subscriptions *xsync.Map[int, *Subscription]
|
||||
dispatchers *xsync.Map[int, dispatcher.Dispatcher]
|
||||
subscriptionCounter atomic.Int32
|
||||
dispatcherCounter atomic.Int32
|
||||
listener *ContainerLogListener
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// NewManager creates a new notification manager
|
||||
func NewManager(listener *ContainerLogListener) *Manager {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
m := &Manager{
|
||||
subscriptions: xsync.NewMap[int, *Subscription](),
|
||||
dispatchers: xsync.NewMap[int, dispatcher.Dispatcher](),
|
||||
listener: listener,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
|
||||
// Start processing log events from the listener
|
||||
go m.processLogEvents()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Start initializes the manager and starts the log listener
|
||||
func (m *Manager) Start() error {
|
||||
if m.listener != nil {
|
||||
return m.listener.Start(m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldListenToContainer implements ContainerMatcher interface
|
||||
func (m *Manager) ShouldListenToContainer(c container.Container) bool {
|
||||
notificationContainer := FromContainerModel(c)
|
||||
|
||||
shouldListen := false
|
||||
m.subscriptions.Range(func(_ int, sub *Subscription) bool {
|
||||
if sub.Enabled && sub.MatchesContainer(notificationContainer) {
|
||||
shouldListen = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return shouldListen
|
||||
}
|
||||
|
||||
// AddSubscription adds a new subscription with compiled expressions
|
||||
func (m *Manager) AddSubscription(sub *Subscription) error {
|
||||
// Auto-increment ID using atomic counter
|
||||
sub.ID = int(m.subscriptionCounter.Add(1))
|
||||
sub.Enabled = true
|
||||
|
||||
// Compile container expression if provided
|
||||
if sub.ContainerExpression != "" {
|
||||
program, err := expr.Compile(sub.ContainerExpression, expr.Env(Container{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile container expression: %w", err)
|
||||
}
|
||||
sub.ContainerProgram = program
|
||||
}
|
||||
|
||||
// Compile log expression if provided
|
||||
if sub.LogExpression != "" {
|
||||
program, err := expr.Compile(sub.LogExpression, expr.Env(Log{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile log expression: %w", err)
|
||||
}
|
||||
sub.LogProgram = program
|
||||
}
|
||||
|
||||
m.subscriptions.Store(sub.ID, sub)
|
||||
log.Info().Str("name", sub.Name).Int("id", sub.ID).Msg("Added subscription")
|
||||
|
||||
// Update listener to start/stop streams based on new subscription
|
||||
if m.listener != nil {
|
||||
if err := m.listener.UpdateStreams(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to update listener streams")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSubscription removes a subscription by ID
|
||||
func (m *Manager) RemoveSubscription(id int) {
|
||||
if sub, ok := m.subscriptions.LoadAndDelete(id); ok {
|
||||
log.Info().Int("id", id).Str("name", sub.Name).Msg("Removed subscription")
|
||||
|
||||
// Update listener to stop streams that are no longer needed
|
||||
if m.listener != nil {
|
||||
if err := m.listener.UpdateStreams(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to update listener streams")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceSubscription replaces a subscription with new data
|
||||
func (m *Manager) ReplaceSubscription(sub *Subscription) error {
|
||||
// Compile container expression if provided
|
||||
if sub.ContainerExpression != "" {
|
||||
program, err := expr.Compile(sub.ContainerExpression, expr.Env(Container{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile container expression: %w", err)
|
||||
}
|
||||
sub.ContainerProgram = program
|
||||
}
|
||||
|
||||
// Compile log expression if provided
|
||||
if sub.LogExpression != "" {
|
||||
program, err := expr.Compile(sub.LogExpression, expr.Env(Log{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile log expression: %w", err)
|
||||
}
|
||||
sub.LogProgram = program
|
||||
}
|
||||
|
||||
// Preserve enabled state from existing subscription if it exists
|
||||
if existing, ok := m.subscriptions.Load(sub.ID); ok {
|
||||
sub.Enabled = existing.Enabled
|
||||
} else {
|
||||
sub.Enabled = true
|
||||
}
|
||||
|
||||
m.subscriptions.Store(sub.ID, sub)
|
||||
log.Info().Str("name", sub.Name).Int("id", sub.ID).Msg("Replaced subscription")
|
||||
|
||||
// Update listener to start/stop streams based on new subscription
|
||||
if m.listener != nil {
|
||||
if err := m.listener.UpdateStreams(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to update listener streams")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSubscription updates a subscription with the provided fields
|
||||
func (m *Manager) UpdateSubscription(id int, updates map[string]any) error {
|
||||
var updateErr error
|
||||
_, ok := m.subscriptions.Compute(id, func(sub *Subscription, loaded bool) (*Subscription, xsync.ComputeOp) {
|
||||
if !loaded {
|
||||
updateErr = fmt.Errorf("subscription not found")
|
||||
return nil, xsync.CancelOp
|
||||
}
|
||||
|
||||
// Clone the subscription
|
||||
updated := &Subscription{
|
||||
ID: sub.ID,
|
||||
Name: sub.Name,
|
||||
Enabled: sub.Enabled,
|
||||
DispatcherID: sub.DispatcherID,
|
||||
ContainerExpression: sub.ContainerExpression,
|
||||
ContainerProgram: sub.ContainerProgram,
|
||||
LogExpression: sub.LogExpression,
|
||||
LogProgram: sub.LogProgram,
|
||||
}
|
||||
|
||||
// Apply updates to the clone
|
||||
for key, value := range updates {
|
||||
switch key {
|
||||
case "name":
|
||||
if name, ok := value.(string); ok {
|
||||
updated.Name = name
|
||||
}
|
||||
case "enabled":
|
||||
if enabled, ok := value.(bool); ok {
|
||||
updated.Enabled = enabled
|
||||
}
|
||||
case "dispatcherId":
|
||||
if dispatcherID, ok := value.(int); ok {
|
||||
updated.DispatcherID = dispatcherID
|
||||
}
|
||||
case "containerExpression":
|
||||
if exprStr, ok := value.(string); ok {
|
||||
program, err := expr.Compile(exprStr, expr.Env(Container{}))
|
||||
if err != nil {
|
||||
updateErr = fmt.Errorf("failed to compile container expression: %w", err)
|
||||
return nil, xsync.CancelOp
|
||||
}
|
||||
updated.ContainerExpression = exprStr
|
||||
updated.ContainerProgram = program
|
||||
}
|
||||
case "logExpression":
|
||||
if exprStr, ok := value.(string); ok {
|
||||
if exprStr != "" {
|
||||
program, err := expr.Compile(exprStr, expr.Env(Log{}))
|
||||
if err != nil {
|
||||
updateErr = fmt.Errorf("failed to compile log expression: %w", err)
|
||||
return nil, xsync.CancelOp
|
||||
}
|
||||
updated.LogExpression = exprStr
|
||||
updated.LogProgram = program
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated, xsync.UpdateOp
|
||||
})
|
||||
|
||||
if updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("subscription not found")
|
||||
}
|
||||
|
||||
log.Debug().Int("id", id).Interface("updates", updates).Msg("Updated subscription")
|
||||
|
||||
// Update listener streams in case expressions changed
|
||||
if m.listener != nil {
|
||||
if err := m.listener.UpdateStreams(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to update listener streams")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddDispatcher adds a dispatcher and returns its auto-generated ID
|
||||
func (m *Manager) AddDispatcher(d dispatcher.Dispatcher) int {
|
||||
id := int(m.dispatcherCounter.Add(1))
|
||||
m.dispatchers.Store(id, d)
|
||||
log.Info().Int("id", id).Msg("Added dispatcher")
|
||||
return id
|
||||
}
|
||||
|
||||
// UpdateDispatcher updates a dispatcher by ID
|
||||
func (m *Manager) UpdateDispatcher(id int, d dispatcher.Dispatcher) {
|
||||
m.dispatchers.Store(id, d)
|
||||
log.Info().Int("id", id).Msg("Updated dispatcher")
|
||||
}
|
||||
|
||||
// RemoveDispatcher removes a dispatcher by ID
|
||||
func (m *Manager) RemoveDispatcher(id int) {
|
||||
if _, ok := m.dispatchers.LoadAndDelete(id); ok {
|
||||
log.Info().Int("id", id).Msg("Removed dispatcher")
|
||||
}
|
||||
}
|
||||
|
||||
// Subscriptions returns all subscriptions sorted by ID
|
||||
func (m *Manager) Subscriptions() []*Subscription {
|
||||
result := make([]*Subscription, 0)
|
||||
m.subscriptions.Range(func(_ int, sub *Subscription) bool {
|
||||
result = append(result, sub)
|
||||
return true
|
||||
})
|
||||
slices.SortFunc(result, func(a, b *Subscription) int {
|
||||
return a.ID - b.ID
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// Dispatchers returns all dispatchers as DispatcherConfig sorted by ID
|
||||
func (m *Manager) Dispatchers() []DispatcherConfig {
|
||||
result := make([]DispatcherConfig, 0)
|
||||
m.dispatchers.Range(func(id int, d dispatcher.Dispatcher) bool {
|
||||
switch v := d.(type) {
|
||||
case *dispatcher.WebhookDispatcher:
|
||||
result = append(result, DispatcherConfig{
|
||||
ID: id,
|
||||
Name: v.Name,
|
||||
Type: "webhook",
|
||||
URL: v.URL,
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
slices.SortFunc(result, func(a, b DispatcherConfig) int {
|
||||
return a.ID - b.ID
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// processLogEvents processes log events from the listener channel
|
||||
func (m *Manager) processLogEvents() {
|
||||
for {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
return
|
||||
case logEvent := <-m.listener.LogChannel():
|
||||
if logEvent == nil {
|
||||
return
|
||||
}
|
||||
m.processLogEvent(logEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processLogEvent processes a single log event and sends notifications for matching subscriptions
|
||||
func (m *Manager) processLogEvent(logEvent *container.LogEvent) {
|
||||
// Get container from log event's ContainerID
|
||||
ctx, cancel := context.WithTimeout(m.ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
c, err := m.listener.FindContainer(ctx, logEvent.ContainerID, nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("containerID", logEvent.ContainerID).Msg("Failed to find container")
|
||||
return
|
||||
}
|
||||
|
||||
notificationContainer := FromContainerModel(c)
|
||||
notificationLog := FromLogEvent(*logEvent)
|
||||
|
||||
m.subscriptions.Range(func(_ int, sub *Subscription) bool {
|
||||
// Skip disabled subscriptions
|
||||
if !sub.Enabled {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check container filter
|
||||
if !sub.MatchesContainer(notificationContainer) {
|
||||
return true
|
||||
}
|
||||
|
||||
sub.AddTriggeredContainer(notificationContainer.ID)
|
||||
|
||||
// Check log filter
|
||||
if !sub.MatchesLog(notificationLog) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Update stats
|
||||
sub.TriggerCount.Add(1)
|
||||
now := time.Now()
|
||||
sub.LastTriggeredAt.Store(&now)
|
||||
|
||||
log.Debug().Str("containerID", notificationContainer.ID).Interface("log", notificationLog.Message).Msg("Matched subscription")
|
||||
|
||||
// Create notification
|
||||
notification := Notification{
|
||||
ID: fmt.Sprintf("%s-%d", c.ID, time.Now().UnixNano()),
|
||||
Container: notificationContainer,
|
||||
Log: notificationLog,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
|
||||
// Send to the subscription's dispatcher
|
||||
if d, ok := m.dispatchers.Load(sub.DispatcherID); ok {
|
||||
go m.sendNotification(d, notification, sub.DispatcherID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// sendNotification sends a notification using the dispatcher
|
||||
func (m *Manager) sendNotification(d dispatcher.Dispatcher, notification Notification, id int) {
|
||||
ctx, cancel := context.WithTimeout(m.ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := d.Send(ctx, notification); err != nil {
|
||||
log.Error().Err(err).Int("subscription", id).Msg("Failed to send notification")
|
||||
}
|
||||
}
|
||||
|
||||
// WriteConfig writes the current configuration to a writer in YAML format
|
||||
func (m *Manager) WriteConfig(w io.Writer) error {
|
||||
config := Config{
|
||||
Subscriptions: m.Subscriptions(),
|
||||
Dispatchers: m.Dispatchers(),
|
||||
}
|
||||
|
||||
encoder := yaml.NewEncoder(w)
|
||||
defer encoder.Close()
|
||||
|
||||
return encoder.Encode(config)
|
||||
}
|
||||
|
||||
// LoadConfig reads configuration from a reader in YAML format and loads it
|
||||
func (m *Manager) LoadConfig(r io.Reader) error {
|
||||
var config Config
|
||||
|
||||
decoder := yaml.NewDecoder(r)
|
||||
if err := decoder.Decode(&config); err != nil {
|
||||
return fmt.Errorf("failed to decode config: %w", err)
|
||||
}
|
||||
|
||||
// Find max IDs to initialize counters
|
||||
var maxSubID, maxDispatcherID int
|
||||
for _, sub := range config.Subscriptions {
|
||||
if sub.ID > maxSubID {
|
||||
maxSubID = sub.ID
|
||||
}
|
||||
}
|
||||
for _, d := range config.Dispatchers {
|
||||
if d.ID > maxDispatcherID {
|
||||
maxDispatcherID = d.ID
|
||||
}
|
||||
}
|
||||
m.subscriptionCounter.Store(int32(maxSubID))
|
||||
m.dispatcherCounter.Store(int32(maxDispatcherID))
|
||||
|
||||
// Load subscriptions
|
||||
for _, sub := range config.Subscriptions {
|
||||
if err := m.loadSubscription(sub); err != nil {
|
||||
return fmt.Errorf("failed to add subscription %s: %w", sub.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load dispatchers
|
||||
for _, dispatcherConfig := range config.Dispatchers {
|
||||
var d dispatcher.Dispatcher
|
||||
switch dispatcherConfig.Type {
|
||||
case "webhook":
|
||||
d = dispatcher.NewWebhookDispatcher(dispatcherConfig.Name, dispatcherConfig.URL)
|
||||
default:
|
||||
return fmt.Errorf("unknown dispatcher type: %s", dispatcherConfig.Type)
|
||||
}
|
||||
m.dispatchers.Store(dispatcherConfig.ID, d)
|
||||
log.Info().Int("id", dispatcherConfig.ID).Msg("Loaded dispatcher")
|
||||
}
|
||||
|
||||
// Update listener to start streams for loaded subscriptions
|
||||
if m.listener != nil {
|
||||
if err := m.listener.UpdateStreams(); err != nil {
|
||||
return fmt.Errorf("failed to update listener streams: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadSubscription loads a subscription with its existing ID (used when loading from config)
|
||||
func (m *Manager) loadSubscription(sub *Subscription) error {
|
||||
// Compile container expression if provided
|
||||
if sub.ContainerExpression != "" {
|
||||
program, err := expr.Compile(sub.ContainerExpression, expr.Env(Container{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile container expression: %w", err)
|
||||
}
|
||||
sub.ContainerProgram = program
|
||||
}
|
||||
|
||||
// Compile log expression if provided
|
||||
if sub.LogExpression != "" {
|
||||
program, err := expr.Compile(sub.LogExpression, expr.Env(Log{}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compile log expression: %w", err)
|
||||
}
|
||||
sub.LogProgram = program
|
||||
}
|
||||
|
||||
m.subscriptions.Store(sub.ID, sub)
|
||||
log.Info().Str("name", sub.Name).Int("id", sub.ID).Msg("Loaded subscription")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
)
|
||||
|
||||
// Notification represents a notification event that can be filtered and sent
|
||||
type Notification struct {
|
||||
ID string `json:"id"`
|
||||
Container Container `json:"container"`
|
||||
Log Log `json:"log"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Container represents a simplified container structure optimized for expr filtering
|
||||
type Container struct {
|
||||
ID string `json:"id" expr:"id"`
|
||||
Name string `json:"name" expr:"name"`
|
||||
Image string `json:"image" expr:"image"`
|
||||
State string `json:"state" expr:"state"`
|
||||
Health string `json:"health" expr:"health"`
|
||||
Host string `json:"host" expr:"host"`
|
||||
Labels map[string]string `json:"labels" expr:"labels"`
|
||||
}
|
||||
|
||||
// FromContainerModel converts internal container.Container to notification.Container
|
||||
func FromContainerModel(c container.Container) Container {
|
||||
return Container{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Image: c.Image,
|
||||
State: c.State,
|
||||
Health: c.Health,
|
||||
Host: c.Host,
|
||||
Labels: c.Labels,
|
||||
}
|
||||
}
|
||||
|
||||
// Log represents a log entry with message that can be string or object
|
||||
type Log struct {
|
||||
ID uint32 `json:"id" expr:"id"`
|
||||
Message any `json:"message" expr:"message"` // string for simple/grouped logs, map for complex logs
|
||||
Timestamp int64 `json:"timestamp" expr:"timestamp"`
|
||||
Level string `json:"level" expr:"level"`
|
||||
Stream string `json:"stream" expr:"stream"`
|
||||
Type string `json:"type" expr:"type"`
|
||||
}
|
||||
|
||||
// FromLogEvent converts container.LogEvent to notification.Log
|
||||
func FromLogEvent(l container.LogEvent) Log {
|
||||
message := extractMessage(l)
|
||||
|
||||
return Log{
|
||||
ID: l.Id,
|
||||
Message: message,
|
||||
Timestamp: l.Timestamp,
|
||||
Level: l.Level,
|
||||
Stream: l.Stream,
|
||||
Type: string(l.Type),
|
||||
}
|
||||
}
|
||||
|
||||
// extractMessage extracts and joins message from LogEvent
|
||||
// For grouped logs (fragments), joins them into a single string
|
||||
// For complex logs (JSON/objects), keeps the original map
|
||||
// For simple logs, returns the string as-is
|
||||
func extractMessage(l container.LogEvent) any {
|
||||
switch v := l.Message.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []container.LogFragment:
|
||||
var parts []string
|
||||
for _, fragment := range v {
|
||||
parts = append(parts, fragment.Message)
|
||||
}
|
||||
return strings.Join(parts, "")
|
||||
default:
|
||||
// For complex objects (maps/JSON), keep the original structure
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// Subscription represents a subscription to log streams with filtering
|
||||
type Subscription struct {
|
||||
ID int `json:"id" yaml:"id"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
DispatcherID int `json:"dispatcherId" yaml:"dispatcherId"`
|
||||
LogExpression string `json:"logExpression" yaml:"logExpression"`
|
||||
ContainerExpression string `json:"containerExpression" yaml:"containerExpression"`
|
||||
|
||||
// Compiled log filter expression
|
||||
LogProgram *vm.Program `json:"-" yaml:"-"` // Compiled log filter expression
|
||||
ContainerProgram *vm.Program `json:"-" yaml:"-"` // Compiled container filter expression
|
||||
|
||||
// Runtime stats (not persisted)
|
||||
TriggerCount atomic.Int64 `json:"-" yaml:"-"`
|
||||
LastTriggeredAt atomic.Pointer[time.Time] `json:"-" yaml:"-"`
|
||||
TriggeredContainerIDs *xsync.Map[string, struct{}] `json:"-" yaml:"-"` // unique container IDs that triggered
|
||||
}
|
||||
|
||||
// TriggeredContainersCount returns the number of unique containers that triggered this subscription
|
||||
func (s *Subscription) TriggeredContainersCount() int {
|
||||
if s.TriggeredContainerIDs == nil {
|
||||
return 0
|
||||
}
|
||||
return s.TriggeredContainerIDs.Size()
|
||||
}
|
||||
|
||||
// AddTriggeredContainer adds a container ID to the triggered set
|
||||
func (s *Subscription) AddTriggeredContainer(id string) {
|
||||
if s.TriggeredContainerIDs == nil {
|
||||
s.TriggeredContainerIDs = xsync.NewMap[string, struct{}]()
|
||||
}
|
||||
s.TriggeredContainerIDs.Store(id, struct{}{})
|
||||
}
|
||||
|
||||
// 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", etc.
|
||||
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Config represents the persisted notification configuration
|
||||
type Config struct {
|
||||
Subscriptions []*Subscription `json:"subscriptions" yaml:"subscriptions"`
|
||||
Dispatchers []DispatcherConfig `json:"dispatchers" yaml:"dispatchers"`
|
||||
}
|
||||
|
||||
// MatchesContainer checks if a container matches this subscription's container filter
|
||||
func (s *Subscription) MatchesContainer(c Container) bool {
|
||||
if s.ContainerProgram == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
result, err := expr.Run(s.ContainerProgram, c)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
match, ok := result.(bool)
|
||||
return ok && match
|
||||
}
|
||||
|
||||
// MatchesLog checks if a log matches this subscription's log filter
|
||||
func (s *Subscription) MatchesLog(l Log) bool {
|
||||
if s.LogProgram == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
result, err := expr.Run(s.LogProgram, l)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
match, ok := result.(bool)
|
||||
return ok && match
|
||||
}
|
||||
@@ -3,9 +3,12 @@ package docker_support
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
"github.com/rs/zerolog/log"
|
||||
lop "github.com/samber/lo/parallel"
|
||||
@@ -27,11 +30,13 @@ type ClientManager interface {
|
||||
Subscribe(ctx context.Context, channel chan<- container.Host)
|
||||
Hosts(ctx context.Context) []container.Host
|
||||
LocalClients() []container.Client
|
||||
LocalClientServices() []container_support.ClientService
|
||||
}
|
||||
|
||||
type MultiHostService struct {
|
||||
manager ClientManager
|
||||
timeout time.Duration
|
||||
manager ClientManager
|
||||
timeout time.Duration
|
||||
notificationManager *notification.Manager
|
||||
}
|
||||
|
||||
func NewMultiHostService(manager ClientManager, timeout time.Duration) *MultiHostService {
|
||||
@@ -168,6 +173,124 @@ func (m *MultiHostService) LocalClients() []container.Client {
|
||||
return m.manager.LocalClients()
|
||||
}
|
||||
|
||||
func (m *MultiHostService) LocalClientServices() []container_support.ClientService {
|
||||
return m.manager.LocalClientServices()
|
||||
}
|
||||
|
||||
func (m *MultiHostService) TotalClients() int {
|
||||
return len(m.manager.List())
|
||||
}
|
||||
|
||||
const notificationConfigPath = "./data/notifications.yml"
|
||||
|
||||
// StartNotificationManager initializes and starts the notification manager
|
||||
func (m *MultiHostService) StartNotificationManager(ctx context.Context) error {
|
||||
clients := m.manager.LocalClientServices()
|
||||
listener := notification.NewContainerLogListener(ctx, clients)
|
||||
m.notificationManager = notification.NewManager(listener)
|
||||
|
||||
// Start first so matcher is available for LoadConfig
|
||||
if err := m.notificationManager.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load config if exists
|
||||
if file, err := os.Open(notificationConfigPath); err == nil {
|
||||
defer file.Close()
|
||||
if err := m.notificationManager.LoadConfig(file); err != nil {
|
||||
log.Warn().Err(err).Msg("Could not load notification config")
|
||||
} else {
|
||||
log.Debug().Str("path", notificationConfigPath).Msg("Loaded notification config")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MultiHostService) saveNotificationConfig() {
|
||||
if err := os.MkdirAll("./data", 0755); err != nil {
|
||||
log.Error().Err(err).Msg("Could not create data directory")
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Create(notificationConfigPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Could not create notification config file")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := m.notificationManager.WriteConfig(file); err != nil {
|
||||
log.Error().Err(err).Msg("Could not write notification config")
|
||||
}
|
||||
}
|
||||
|
||||
// AddSubscription adds a subscription to local manager and broadcasts to agents
|
||||
func (m *MultiHostService) AddSubscription(sub *notification.Subscription) error {
|
||||
// Add to local manager
|
||||
if err := m.notificationManager.AddSubscription(sub); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Broadcast to agents via gRPC when agent notification support is added
|
||||
|
||||
m.saveNotificationConfig()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSubscription removes a subscription from local manager and broadcasts to agents
|
||||
func (m *MultiHostService) RemoveSubscription(id int) {
|
||||
// Remove from local manager
|
||||
m.notificationManager.RemoveSubscription(id)
|
||||
|
||||
// TODO: Broadcast to agents via gRPC when agent notification support is added
|
||||
|
||||
m.saveNotificationConfig()
|
||||
}
|
||||
|
||||
// AddDispatcher adds a dispatcher and returns its auto-generated ID
|
||||
func (m *MultiHostService) AddDispatcher(d dispatcher.Dispatcher) int {
|
||||
id := m.notificationManager.AddDispatcher(d)
|
||||
m.saveNotificationConfig()
|
||||
return id
|
||||
}
|
||||
|
||||
// UpdateDispatcher updates a dispatcher by ID
|
||||
func (m *MultiHostService) UpdateDispatcher(id int, d dispatcher.Dispatcher) {
|
||||
m.notificationManager.UpdateDispatcher(id, d)
|
||||
m.saveNotificationConfig()
|
||||
}
|
||||
|
||||
// RemoveDispatcher removes a dispatcher by ID
|
||||
func (m *MultiHostService) RemoveDispatcher(id int) {
|
||||
m.notificationManager.RemoveDispatcher(id)
|
||||
m.saveNotificationConfig()
|
||||
}
|
||||
|
||||
// ReplaceSubscription replaces a subscription with new data
|
||||
func (m *MultiHostService) ReplaceSubscription(sub *notification.Subscription) error {
|
||||
if err := m.notificationManager.ReplaceSubscription(sub); err != nil {
|
||||
return err
|
||||
}
|
||||
m.saveNotificationConfig()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSubscription updates a subscription with the provided fields
|
||||
func (m *MultiHostService) UpdateSubscription(id int, updates map[string]any) error {
|
||||
if err := m.notificationManager.UpdateSubscription(id, updates); err != nil {
|
||||
return err
|
||||
}
|
||||
m.saveNotificationConfig()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subscriptions returns all subscriptions
|
||||
func (m *MultiHostService) Subscriptions() []*notification.Subscription {
|
||||
return m.notificationManager.Subscriptions()
|
||||
}
|
||||
|
||||
// Dispatchers returns all dispatchers
|
||||
func (m *MultiHostService) Dispatchers() []notification.DispatcherConfig {
|
||||
return m.notificationManager.Dispatchers()
|
||||
}
|
||||
|
||||
@@ -195,3 +195,17 @@ func (m *RetriableClientManager) LocalClients() []container.Client {
|
||||
|
||||
return clients
|
||||
}
|
||||
|
||||
func (m *RetriableClientManager) LocalClientServices() []container_support.ClientService {
|
||||
services := m.List()
|
||||
|
||||
result := make([]container_support.ClientService, 0)
|
||||
|
||||
for _, service := range services {
|
||||
if _, ok := service.(*DockerClientService); ok {
|
||||
result = append(result, service)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -236,3 +236,17 @@ func (m *SwarmClientManager) String() string {
|
||||
func (m *SwarmClientManager) LocalClients() []container.Client {
|
||||
return []container.Client{m.localClient}
|
||||
}
|
||||
|
||||
func (m *SwarmClientManager) LocalClientServices() []container_support.ClientService {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
result := make([]container_support.ClientService, 0)
|
||||
for _, service := range m.clients {
|
||||
if _, ok := service.(*DockerClientService); ok {
|
||||
result = append(result, service)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/k8s"
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@@ -131,3 +133,43 @@ func (m *K8sClusterService) SubscribeAvailableHosts(ctx context.Context, hosts c
|
||||
func (m *K8sClusterService) LocalClients() []container.Client {
|
||||
return []container.Client{m.client.client}
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) LocalClientServices() []container_support.ClientService {
|
||||
return []container_support.ClientService{m.client}
|
||||
}
|
||||
|
||||
// Notification methods - not yet implemented for k8s
|
||||
func (m *K8sClusterService) AddSubscription(sub *notification.Subscription) error {
|
||||
|
||||
// TODO Implement notification subscription for k8s mode
|
||||
return fmt.Errorf("notifications not supported in k8s mode")
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) RemoveSubscription(id int) {
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) ReplaceSubscription(sub *notification.Subscription) error {
|
||||
return fmt.Errorf("notifications not supported in k8s mode")
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) UpdateSubscription(id int, updates map[string]any) error {
|
||||
return fmt.Errorf("notifications not supported in k8s mode")
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) Subscriptions() []*notification.Subscription {
|
||||
return []*notification.Subscription{}
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) AddDispatcher(d dispatcher.Dispatcher) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) UpdateDispatcher(id int, d dispatcher.Dispatcher) {
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) RemoveDispatcher(id int) {
|
||||
}
|
||||
|
||||
func (m *K8sClusterService) Dispatchers() []notification.DispatcherConfig {
|
||||
return []notification.DispatcherConfig{}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
gqlhandler "github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/amir20/dozzle/graph"
|
||||
)
|
||||
|
||||
func (h *handler) graphqlHandler() http.Handler {
|
||||
srv := gqlhandler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{
|
||||
Resolvers: &graph.Resolver{
|
||||
HostService: h.hostService,
|
||||
},
|
||||
}))
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
func (h *handler) graphqlPlaygroundHandler() http.Handler {
|
||||
endpoint := h.config.Base + "/api/graphql"
|
||||
// Avoid double slashes when base is "/"
|
||||
if h.config.Base == "/" {
|
||||
endpoint = "/api/graphql"
|
||||
}
|
||||
return playground.Handler("Dozzle GraphQL", endpoint)
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/internal/auth"
|
||||
"github.com/amir20/dozzle/internal/container"
|
||||
"github.com/amir20/dozzle/internal/notification"
|
||||
"github.com/amir20/dozzle/internal/notification/dispatcher"
|
||||
container_support "github.com/amir20/dozzle/internal/support/container"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -72,6 +74,17 @@ type HostService interface {
|
||||
LocalHost() (container.Host, error)
|
||||
SubscribeAvailableHosts(ctx context.Context, hosts chan<- container.Host)
|
||||
LocalClients() []container.Client
|
||||
LocalClientServices() []container_support.ClientService
|
||||
// Notification methods
|
||||
AddSubscription(sub *notification.Subscription) error
|
||||
RemoveSubscription(id int)
|
||||
ReplaceSubscription(sub *notification.Subscription) error
|
||||
UpdateSubscription(id int, updates map[string]any) error
|
||||
Subscriptions() []*notification.Subscription
|
||||
AddDispatcher(d dispatcher.Dispatcher) int
|
||||
UpdateDispatcher(id int, d dispatcher.Dispatcher)
|
||||
RemoveDispatcher(id int)
|
||||
Dispatchers() []notification.DispatcherConfig
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
@@ -116,6 +129,8 @@ func createRouter(h *handler) *chi.Mux {
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
r.Use(auth.RequireAuthentication)
|
||||
}
|
||||
|
||||
// Log streams
|
||||
r.Get("/hosts/{host}/containers/{id}/logs/stream", h.streamContainerLogs)
|
||||
r.Get("/hosts/{host}/logs/stream", h.streamHostLogs)
|
||||
r.Get("/hosts/{host}/containers/{id}/logs", h.fetchLogsBetweenDates)
|
||||
@@ -124,6 +139,8 @@ func createRouter(h *handler) *chi.Mux {
|
||||
r.Get("/labels/{labels}/logs/stream", h.streamLogsWithLabels)
|
||||
r.Get("/groups/{group}/logs/stream", h.streamGroupedLogs)
|
||||
r.Get("/events/stream", h.streamEvents)
|
||||
|
||||
// Action
|
||||
if h.config.EnableActions {
|
||||
r.Post("/hosts/{host}/containers/{id}/actions/{action}", h.containerActions)
|
||||
}
|
||||
@@ -131,6 +148,7 @@ func createRouter(h *handler) *chi.Mux {
|
||||
r.Get("/hosts/{host}/containers/{id}/attach", h.attach)
|
||||
r.Get("/hosts/{host}/containers/{id}/exec", h.exec)
|
||||
}
|
||||
|
||||
r.Get("/releases", h.releases)
|
||||
if !h.config.DisableAvatars {
|
||||
r.Get("/profile/avatar", h.avatar)
|
||||
@@ -140,6 +158,12 @@ func createRouter(h *handler) *chi.Mux {
|
||||
if log.Debug().Enabled() {
|
||||
r.Get("/debug/store", h.debugStore)
|
||||
}
|
||||
|
||||
// GraphQL endpoint
|
||||
r.Handle("/graphql", h.graphqlHandler())
|
||||
if h.config.Dev {
|
||||
r.Handle("/graphql/playground", h.graphqlPlaygroundHandler())
|
||||
}
|
||||
})
|
||||
|
||||
// Public API routes
|
||||
|
||||
@@ -80,6 +80,7 @@ title:
|
||||
login: Authentication Required
|
||||
dashboard: 1 container | {count} containers
|
||||
settings: Settings
|
||||
notifications: Notifications
|
||||
button:
|
||||
logout: Logout
|
||||
login: Login
|
||||
@@ -163,3 +164,67 @@ analytics:
|
||||
evaluating_query: Evaluating query...
|
||||
total_records: Total {count} records.
|
||||
showing_first: Showing first {count}.
|
||||
notifications:
|
||||
title: Notifications
|
||||
description: Configure where and when to receive alerts
|
||||
destinations: Destinations
|
||||
add-destination: Add destination
|
||||
alerts: Alerts
|
||||
add: Add
|
||||
filter:
|
||||
all: All ({count})
|
||||
enabled: Enabled ({count})
|
||||
paused: Paused ({count})
|
||||
no-alerts: No alerts configured yet. Create one to get started.
|
||||
alert:
|
||||
paused: Paused
|
||||
containers: Containers
|
||||
log-filter: Log filter
|
||||
destination: Destination
|
||||
dispatcher-deleted: Dispatcher deleted
|
||||
containers-count: "{count} containers"
|
||||
triggered-count: "{count} triggered"
|
||||
last-triggered: "Last: {time}"
|
||||
destination:
|
||||
http-webhook: HTTP Webhook
|
||||
dozzle-cloud: Dozzle Cloud
|
||||
edit: Edit
|
||||
delete: Delete
|
||||
alert-form:
|
||||
create-title: Create Alert
|
||||
edit-title: Edit Alert
|
||||
description: Subscribe to log events matching your criteria
|
||||
alert-name: Alert Name
|
||||
alert-name-placeholder: e.g., Test API Errors
|
||||
container-filter: Container Filter
|
||||
container-filter-placeholder: name contains "api"
|
||||
containers-match: "{count} containers match: {names}"
|
||||
no-containers-match: No containers match this filter
|
||||
log-filter: Log Filter
|
||||
log-filter-placeholder: level == "error" && message contains "timeout"
|
||||
logs-match: "{count} logs match"
|
||||
no-logs-match: No logs match this filter
|
||||
destination: Destination
|
||||
select-destination: Select a destination
|
||||
no-destinations: No destinations configured. Add one first.
|
||||
preview: Preview
|
||||
cancel: Cancel
|
||||
save: Save
|
||||
create: Create Alert
|
||||
destination-form:
|
||||
create-title: Add Destination
|
||||
edit-title: Edit Destination
|
||||
description: Where should notifications be sent?
|
||||
name: Name
|
||||
name-placeholder: e.g., Production Slack
|
||||
type: Type
|
||||
webhook-title: HTTP Webhook
|
||||
webhook-description: Slack, Discord, custom endpoint
|
||||
cloud-title: Dozzle Cloud
|
||||
cloud-description: Push, email, and dashboard
|
||||
webhook-url: Webhook URL
|
||||
webhook-url-placeholder: https://hooks.foo.com/services/...
|
||||
test: Test
|
||||
cancel: Cancel
|
||||
save: Save
|
||||
add: Add Destination
|
||||
|
||||
@@ -53,15 +53,20 @@ func main() {
|
||||
|
||||
log.Info().Msgf("Dozzle version %s", args.Version())
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
var hostService web.HostService
|
||||
if args.Mode == "server" {
|
||||
|
||||
multiHostService := cli.CreateMultiHostService(certs, args)
|
||||
if multiHostService.TotalClients() == 0 {
|
||||
log.Fatal().Msg("Could not connect to any Docker Engine")
|
||||
} else {
|
||||
log.Info().Int("clients", multiHostService.TotalClients()).Msg("Connected to Docker")
|
||||
}
|
||||
if err := multiHostService.StartNotificationManager(ctx); err != nil {
|
||||
log.Fatal().Err(err).Msg("Could not start notification manager")
|
||||
}
|
||||
hostService = multiHostService
|
||||
} else if args.Mode == "swarm" {
|
||||
localClient, err := docker.NewLocalClient("")
|
||||
@@ -74,7 +79,11 @@ func main() {
|
||||
}
|
||||
agentManager := docker_support.NewRetriableClientManager(args.RemoteAgent, args.Timeout, certs)
|
||||
manager := docker_support.NewSwarmClientManager(localClient, certs, args.Timeout, agentManager, args.Filter)
|
||||
hostService = docker_support.NewMultiHostService(manager, args.Timeout)
|
||||
multiHostService := docker_support.NewMultiHostService(manager, args.Timeout)
|
||||
if err := multiHostService.StartNotificationManager(ctx); err != nil {
|
||||
log.Fatal().Err(err).Msg("Could not start notification manager")
|
||||
}
|
||||
hostService = multiHostService
|
||||
log.Info().Msg("Starting in swarm mode")
|
||||
listener, err := net.Listen("tcp", ":7007")
|
||||
if err != nil {
|
||||
@@ -115,8 +124,6 @@ func main() {
|
||||
log.Fatal().Err(err).Msg("failed to listen")
|
||||
}
|
||||
}()
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
<-ctx.Done()
|
||||
stop()
|
||||
|
||||
@@ -24,11 +24,18 @@
|
||||
"release": "bumpp",
|
||||
"test": "TZ=UTC vitest",
|
||||
"typecheck": "vue-tsc --noEmit",
|
||||
"codegen": "graphql-codegen",
|
||||
"docs:dev": "vitepress dev docs --open",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.20.0",
|
||||
"@codemirror/lang-javascript": "^6.2.4",
|
||||
"@codemirror/language": "^6.12.1",
|
||||
"@codemirror/state": "^6.5.3",
|
||||
"@codemirror/theme-one-dark": "^6.1.3",
|
||||
"@codemirror/view": "^6.39.9",
|
||||
"@duckdb/duckdb-wasm": "1.33.1-dev18.0",
|
||||
"@iconify-json/carbon": "^1.2.18",
|
||||
"@iconify-json/cil": "^1.2.3",
|
||||
@@ -39,8 +46,10 @@
|
||||
"@iconify-json/octicon": "^1.2.20",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@intlify/unplugin-vue-i18n": "^11.0.3",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "4.1.18",
|
||||
"@urql/vue": "^2.0.0",
|
||||
"@vueuse/components": "^14.1.0",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"@vueuse/integrations": "^14.1.0",
|
||||
@@ -49,6 +58,7 @@
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"codemirror": "^6.0.2",
|
||||
"daisyui": "5.5.14",
|
||||
"entities": "^7.0.0",
|
||||
"fuse.js": "^7.1.0",
|
||||
@@ -63,6 +73,7 @@
|
||||
"unplugin-vue-components": "^31.0.0",
|
||||
"unplugin-vue-macros": "^2.14.5",
|
||||
"unplugin-vue-router": "^0.19.2",
|
||||
"urql": "^5.0.1",
|
||||
"vite": "7.3.1",
|
||||
"vite-plugin-vue-layouts": "^0.11.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
@@ -73,6 +84,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apache-arrow/esnext-esm": "^21.1.0",
|
||||
"@graphql-codegen/cli": "^6.1.1",
|
||||
"@graphql-codegen/typed-document-node": "^6.1.5",
|
||||
"@graphql-codegen/typescript": "^5.0.7",
|
||||
"@graphql-codegen/typescript-operations": "^5.0.7",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@iconify-json/ion": "^1.2.6",
|
||||
"@iconify-json/material-symbols-light": "^1.2.52",
|
||||
"@iconify-json/ri": "^1.2.7",
|
||||
@@ -87,6 +103,7 @@
|
||||
"c8": "^10.1.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"graphql": "^16.12.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"prettier": "^3.8.0",
|
||||
|
||||
Generated
+2623
-6
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -28,6 +28,7 @@ export default defineConfig(() => ({
|
||||
polyfill: false,
|
||||
},
|
||||
target: "esnext",
|
||||
chunkSizeWarningLimit: 600,
|
||||
},
|
||||
plugins: [
|
||||
VueRouter({
|
||||
@@ -35,7 +36,7 @@ export default defineConfig(() => ({
|
||||
src: "./assets/pages",
|
||||
},
|
||||
dts: "./assets/typed-router.d.ts",
|
||||
importMode: "sync",
|
||||
importMode: "sync", // this can't be async because it won't work with custom base
|
||||
}),
|
||||
VueMacros({
|
||||
plugins: {
|
||||
|
||||
Reference in New Issue
Block a user