mirror of
https://github.com/amir20/dozzle.git
synced 2026-06-23 04:10:12 +00:00
feat: supports go templates for webhooks (#4366)
This commit is contained in:
@@ -72,6 +72,38 @@
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<!-- Payload Format (only for webhook type) -->
|
||||
<fieldset v-if="type === 'webhook'" class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">{{ $t("notifications.destination-form.payload-format") }}</legend>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="format in ['slack', 'discord', 'ntfy', 'custom'] as const"
|
||||
:key="format"
|
||||
type="button"
|
||||
class="btn btn-sm"
|
||||
:class="payloadFormat === format ? 'btn-primary' : 'btn-ghost'"
|
||||
@click="selectPayloadFormat(format)"
|
||||
>
|
||||
{{ $t(`notifications.destination-form.format-${format}`) }}
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Template (only for webhook type) -->
|
||||
<fieldset v-if="type === 'webhook'" class="fieldset">
|
||||
<legend class="fieldset-legend text-lg">
|
||||
{{ $t("notifications.destination-form.template") }}
|
||||
<span class="text-base-content/60 ml-2 text-sm font-normal">{{
|
||||
$t("notifications.destination-form.template-hint")
|
||||
}}</span>
|
||||
</legend>
|
||||
<textarea
|
||||
v-model="template"
|
||||
class="textarea focus:textarea-primary min-h-48 w-full font-mono text-sm"
|
||||
:class="{ 'textarea-primary': template.trim().length > 0 }"
|
||||
></textarea>
|
||||
</fieldset>
|
||||
|
||||
<!-- Error -->
|
||||
<div v-if="error" class="alert alert-error">
|
||||
<span>{{ error }}</span>
|
||||
@@ -97,6 +129,55 @@
|
||||
import { useMutation } from "@urql/vue";
|
||||
import { CreateDispatcherDocument, UpdateDispatcherDocument, type Dispatcher } from "@/types/graphql";
|
||||
|
||||
type PayloadFormat = "slack" | "discord" | "ntfy" | "custom";
|
||||
|
||||
const PAYLOAD_TEMPLATES: Record<PayloadFormat, string> = {
|
||||
slack: `{
|
||||
"text": "{{ .Container.Name }}",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "*{{ .Container.Name }}*\\n{{ .Log.Message }}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "context",
|
||||
"elements": [
|
||||
{
|
||||
"type": "mrkdwn",
|
||||
"text": "Host: {{ .Container.Host }} | Image: {{ .Container.Image }}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
discord: `{
|
||||
"content": "{{ .Container.Name }}",
|
||||
"embeds": [
|
||||
{
|
||||
"title": "{{ .Container.Name }}",
|
||||
"description": "{{ .Log.Message }}",
|
||||
"fields": [
|
||||
{ "name": "Host", "value": "{{ .Container.Host }}", "inline": true },
|
||||
{ "name": "Image", "value": "{{ .Container.Image }}", "inline": true }
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
ntfy: `{
|
||||
"topic": "dozzle-{{ .Container.Host }}",
|
||||
"title": "{{ .Container.Name }}",
|
||||
"message": "{{ .Log.Message }}"
|
||||
}`,
|
||||
custom: `{
|
||||
"container": "{{ .Container.Name }}",
|
||||
"level": "{{ .Log.Level }}",
|
||||
"message": "{{ .Log.Message }}"
|
||||
}`,
|
||||
};
|
||||
|
||||
const { close, onCreated, destination } = defineProps<{
|
||||
close?: () => void;
|
||||
onCreated?: () => void;
|
||||
@@ -106,17 +187,24 @@ const { close, onCreated, destination } = defineProps<{
|
||||
const createMutation = useMutation(CreateDispatcherDocument);
|
||||
const updateMutation = useMutation(UpdateDispatcherDocument);
|
||||
|
||||
const isEditing = computed(() => !!destination);
|
||||
const isEditing = !!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 payloadFormat = ref<PayloadFormat>(isEditing ? "custom" : "slack");
|
||||
const template = ref(isEditing ? (destination?.template ?? "") : PAYLOAD_TEMPLATES[payloadFormat.value]);
|
||||
const isTesting = ref(false);
|
||||
const isSaving = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
function selectPayloadFormat(format: PayloadFormat) {
|
||||
payloadFormat.value = format;
|
||||
template.value = PAYLOAD_TEMPLATES[format];
|
||||
}
|
||||
|
||||
const canTest = computed(() => {
|
||||
if (type.value === "webhook") {
|
||||
return webhookUrl.value.trim().length > 0;
|
||||
@@ -158,9 +246,10 @@ async function saveDestination() {
|
||||
name: name.value.trim(),
|
||||
type: type.value,
|
||||
url: type.value === "webhook" ? webhookUrl.value.trim() : undefined,
|
||||
template: type.value === "webhook" && template.value.trim() ? template.value.trim() : undefined,
|
||||
};
|
||||
|
||||
const result = isEditing.value
|
||||
const result = isEditing
|
||||
? await updateMutation.executeMutation({ id: destination!.id, input })
|
||||
: await createMutation.executeMutation({ input });
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ query GetDispatchers {
|
||||
name
|
||||
type
|
||||
url
|
||||
template
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +91,7 @@ mutation UpdateDispatcher($id: Int!, $input: DispatcherInput!) {
|
||||
name
|
||||
type
|
||||
url
|
||||
template
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,12 +38,14 @@ export type Dispatcher = {
|
||||
__typename?: 'Dispatcher';
|
||||
id: Scalars['Int']['output'];
|
||||
name: Scalars['String']['output'];
|
||||
template?: Maybe<Scalars['String']['output']>;
|
||||
type: Scalars['String']['output'];
|
||||
url?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type DispatcherInput = {
|
||||
name: Scalars['String']['input'];
|
||||
template?: InputMaybe<Scalars['String']['input']>;
|
||||
type: Scalars['String']['input'];
|
||||
url?: InputMaybe<Scalars['String']['input']>;
|
||||
};
|
||||
@@ -198,7 +200,7 @@ export type GetNotificationRulesQuery = { __typename?: 'Query', notificationRule
|
||||
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 GetDispatchersQuery = { __typename?: 'Query', dispatchers: Array<{ __typename?: 'Dispatcher', id: number, name: string, type: string, url?: string | null, template?: string | null }> };
|
||||
|
||||
export type CreateNotificationRuleMutationVariables = Exact<{
|
||||
input: NotificationRuleInput;
|
||||
@@ -243,7 +245,7 @@ export type UpdateDispatcherMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateDispatcherMutation = { __typename?: 'Mutation', updateDispatcher: { __typename?: 'Dispatcher', id: number, name: string, type: string, url?: string | null } };
|
||||
export type UpdateDispatcherMutation = { __typename?: 'Mutation', updateDispatcher: { __typename?: 'Dispatcher', id: number, name: string, type: string, url?: string | null, template?: string | null } };
|
||||
|
||||
export type DeleteDispatcherMutationVariables = Exact<{
|
||||
id: Scalars['Int']['input'];
|
||||
@@ -266,13 +268,13 @@ export type GetReleasesQuery = { __typename?: 'Query', releases: Array<{ __typen
|
||||
|
||||
|
||||
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 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"}},{"kind":"Field","name":{"kind":"Name","value":"template"}}]}}]}}]} 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 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"}},{"kind":"Field","name":{"kind":"Name","value":"template"}}]}}]}}]} 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>;
|
||||
export const GetReleasesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetReleases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"releases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"mentionsCount"}},{"kind":"Field","name":{"kind":"Name","value":"tag"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"htmlUrl"}},{"kind":"Field","name":{"kind":"Name","value":"latest"}},{"kind":"Field","name":{"kind":"Name","value":"features"}},{"kind":"Field","name":{"kind":"Name","value":"bugFixes"}},{"kind":"Field","name":{"kind":"Name","value":"breaking"}}]}}]}}]} as unknown as DocumentNode<GetReleasesQuery, GetReleasesQueryVariables>;
|
||||
+60
-5
@@ -68,10 +68,11 @@ type ComplexityRoot struct {
|
||||
}
|
||||
|
||||
Dispatcher struct {
|
||||
ID func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
Type func(childComplexity int) int
|
||||
URL func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
Template func(childComplexity int) int
|
||||
Type func(childComplexity int) int
|
||||
URL func(childComplexity int) int
|
||||
}
|
||||
|
||||
LogEvent struct {
|
||||
@@ -267,6 +268,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
}
|
||||
|
||||
return e.complexity.Dispatcher.Name(childComplexity), true
|
||||
case "Dispatcher.template":
|
||||
if e.complexity.Dispatcher.Template == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Dispatcher.Template(childComplexity), true
|
||||
case "Dispatcher.type":
|
||||
if e.complexity.Dispatcher.Type == nil {
|
||||
break
|
||||
@@ -1351,6 +1358,35 @@ func (ec *executionContext) fieldContext_Dispatcher_url(_ context.Context, field
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Dispatcher_template(ctx context.Context, field graphql.CollectedField, obj *model.Dispatcher) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
ec.OperationContext,
|
||||
field,
|
||||
ec.fieldContext_Dispatcher_template,
|
||||
func(ctx context.Context) (any, error) {
|
||||
return obj.Template, nil
|
||||
},
|
||||
nil,
|
||||
ec.marshalOString2ᚖstring,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Dispatcher_template(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Dispatcher",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _LogEvent_id(ctx context.Context, field graphql.CollectedField, obj *container.LogEvent) (ret graphql.Marshaler) {
|
||||
return graphql.ResolveField(
|
||||
ctx,
|
||||
@@ -1811,6 +1847,8 @@ func (ec *executionContext) fieldContext_Mutation_createDispatcher(ctx context.C
|
||||
return ec.fieldContext_Dispatcher_type(ctx, field)
|
||||
case "url":
|
||||
return ec.fieldContext_Dispatcher_url(ctx, field)
|
||||
case "template":
|
||||
return ec.fieldContext_Dispatcher_template(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Dispatcher", field.Name)
|
||||
},
|
||||
@@ -1862,6 +1900,8 @@ func (ec *executionContext) fieldContext_Mutation_updateDispatcher(ctx context.C
|
||||
return ec.fieldContext_Dispatcher_type(ctx, field)
|
||||
case "url":
|
||||
return ec.fieldContext_Dispatcher_url(ctx, field)
|
||||
case "template":
|
||||
return ec.fieldContext_Dispatcher_template(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Dispatcher", field.Name)
|
||||
},
|
||||
@@ -2093,6 +2133,8 @@ func (ec *executionContext) fieldContext_NotificationRule_dispatcher(_ context.C
|
||||
return ec.fieldContext_Dispatcher_type(ctx, field)
|
||||
case "url":
|
||||
return ec.fieldContext_Dispatcher_url(ctx, field)
|
||||
case "template":
|
||||
return ec.fieldContext_Dispatcher_template(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Dispatcher", field.Name)
|
||||
},
|
||||
@@ -2572,6 +2614,8 @@ func (ec *executionContext) fieldContext_Query_dispatchers(_ context.Context, fi
|
||||
return ec.fieldContext_Dispatcher_type(ctx, field)
|
||||
case "url":
|
||||
return ec.fieldContext_Dispatcher_url(ctx, field)
|
||||
case "template":
|
||||
return ec.fieldContext_Dispatcher_template(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Dispatcher", field.Name)
|
||||
},
|
||||
@@ -2612,6 +2656,8 @@ func (ec *executionContext) fieldContext_Query_dispatcher(ctx context.Context, f
|
||||
return ec.fieldContext_Dispatcher_type(ctx, field)
|
||||
case "url":
|
||||
return ec.fieldContext_Dispatcher_url(ctx, field)
|
||||
case "template":
|
||||
return ec.fieldContext_Dispatcher_template(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Dispatcher", field.Name)
|
||||
},
|
||||
@@ -4532,7 +4578,7 @@ func (ec *executionContext) unmarshalInputDispatcherInput(ctx context.Context, o
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"name", "type", "url"}
|
||||
fieldsInOrder := [...]string{"name", "type", "url", "template"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
@@ -4560,6 +4606,13 @@ func (ec *executionContext) unmarshalInputDispatcherInput(ctx context.Context, o
|
||||
return it, err
|
||||
}
|
||||
it.URL = data
|
||||
case "template":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("template"))
|
||||
data, err := ec.unmarshalOString2ᚖstring(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.Template = data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4854,6 +4907,8 @@ func (ec *executionContext) _Dispatcher(ctx context.Context, sel ast.SelectionSe
|
||||
}
|
||||
case "url":
|
||||
out.Values[i] = ec._Dispatcher_url(ctx, field, obj)
|
||||
case "template":
|
||||
out.Values[i] = ec._Dispatcher_template(ctx, field, obj)
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
||||
+9
-4
@@ -40,11 +40,16 @@ func dispatcherConfigToDispatcher(d *notification.DispatcherConfig) *model.Dispa
|
||||
if d.URL != "" {
|
||||
url = &d.URL
|
||||
}
|
||||
var template *string
|
||||
if d.Template != "" {
|
||||
template = &d.Template
|
||||
}
|
||||
return &model.Dispatcher{
|
||||
ID: int32(d.ID),
|
||||
Name: d.Name,
|
||||
Type: d.Type,
|
||||
URL: url,
|
||||
ID: int32(d.ID),
|
||||
Name: d.Name,
|
||||
Type: d.Type,
|
||||
URL: url,
|
||||
Template: template,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,18 @@ import (
|
||||
)
|
||||
|
||||
type Dispatcher struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Template *string `json:"template,omitempty"`
|
||||
}
|
||||
|
||||
type DispatcherInput struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
URL *string `json:"url,omitempty"`
|
||||
Template *string `json:"template,omitempty"`
|
||||
}
|
||||
|
||||
type Mutation struct {
|
||||
|
||||
@@ -46,6 +46,7 @@ type Dispatcher {
|
||||
name: String!
|
||||
type: String!
|
||||
url: String
|
||||
template: String
|
||||
}
|
||||
|
||||
type PreviewResult {
|
||||
@@ -90,6 +91,7 @@ input DispatcherInput {
|
||||
name: String!
|
||||
type: String!
|
||||
url: String
|
||||
template: String
|
||||
}
|
||||
|
||||
input PreviewInput {
|
||||
|
||||
+28
-10
@@ -122,7 +122,15 @@ func (r *mutationResolver) CreateDispatcher(ctx context.Context, input model.Dis
|
||||
if input.URL != nil {
|
||||
url = *input.URL
|
||||
}
|
||||
d = dispatcher.NewWebhookDispatcher(input.Name, url)
|
||||
templateStr := ""
|
||||
if input.Template != nil {
|
||||
templateStr = *input.Template
|
||||
}
|
||||
webhook, err := dispatcher.NewWebhookDispatcher(input.Name, url, templateStr)
|
||||
if err != nil {
|
||||
return nil, &Error{Message: err.Error()}
|
||||
}
|
||||
d = webhook
|
||||
default:
|
||||
return nil, &Error{Message: "unknown dispatcher type"}
|
||||
}
|
||||
@@ -130,10 +138,11 @@ func (r *mutationResolver) CreateDispatcher(ctx context.Context, input model.Dis
|
||||
id := r.HostService.AddDispatcher(d)
|
||||
|
||||
return &model.Dispatcher{
|
||||
ID: int32(id),
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
URL: input.URL,
|
||||
ID: int32(id),
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
URL: input.URL,
|
||||
Template: input.Template,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -146,7 +155,15 @@ func (r *mutationResolver) UpdateDispatcher(ctx context.Context, id int32, input
|
||||
if input.URL != nil {
|
||||
url = *input.URL
|
||||
}
|
||||
d = dispatcher.NewWebhookDispatcher(input.Name, url)
|
||||
templateStr := ""
|
||||
if input.Template != nil {
|
||||
templateStr = *input.Template
|
||||
}
|
||||
webhook, err := dispatcher.NewWebhookDispatcher(input.Name, url, templateStr)
|
||||
if err != nil {
|
||||
return nil, &Error{Message: err.Error()}
|
||||
}
|
||||
d = webhook
|
||||
default:
|
||||
return nil, &Error{Message: "unknown dispatcher type"}
|
||||
}
|
||||
@@ -154,10 +171,11 @@ func (r *mutationResolver) UpdateDispatcher(ctx context.Context, id int32, input
|
||||
r.HostService.UpdateDispatcher(int(id), d)
|
||||
|
||||
return &model.Dispatcher{
|
||||
ID: id,
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
URL: input.URL,
|
||||
ID: id,
|
||||
Name: input.Name,
|
||||
Type: input.Type,
|
||||
URL: input.URL,
|
||||
Template: input.Template,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,33 +5,62 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// WebhookDispatcher sends notifications to a webhook URL
|
||||
type WebhookDispatcher struct {
|
||||
Name string
|
||||
URL string
|
||||
client *http.Client
|
||||
Name string
|
||||
URL string
|
||||
Template *template.Template
|
||||
TemplateText string // Original template string for serialization
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewWebhookDispatcher creates a new webhook dispatcher
|
||||
func NewWebhookDispatcher(name, url string) *WebhookDispatcher {
|
||||
return &WebhookDispatcher{
|
||||
Name: name,
|
||||
URL: url,
|
||||
// If templateStr is empty, the notification will be marshaled as JSON directly
|
||||
func NewWebhookDispatcher(name, url, templateStr string) (*WebhookDispatcher, error) {
|
||||
w := &WebhookDispatcher{
|
||||
Name: name,
|
||||
URL: url,
|
||||
TemplateText: templateStr,
|
||||
client: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
},
|
||||
}
|
||||
|
||||
if templateStr != "" {
|
||||
tmpl, err := template.New("webhook").Parse(templateStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse template: %w", err)
|
||||
}
|
||||
w.Template = tmpl
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
var payload []byte
|
||||
var err error
|
||||
|
||||
if w.Template != nil {
|
||||
var buf bytes.Buffer
|
||||
if err := w.Template.Execute(&buf, notification); err != nil {
|
||||
return fmt.Errorf("failed to execute template: %w", err)
|
||||
}
|
||||
payload = buf.Bytes()
|
||||
} else {
|
||||
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))
|
||||
@@ -48,6 +77,14 @@ func (w *WebhookDispatcher) Send(ctx context.Context, notification any) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
responseBody, _ := io.ReadAll(resp.Body)
|
||||
log.Debug().
|
||||
Str("webhook", w.Name).
|
||||
Str("url", w.URL).
|
||||
Int("status_code", resp.StatusCode).
|
||||
Str("payload", string(payload)).
|
||||
Str("response_body", string(responseBody)).
|
||||
Msg("webhook returned non-success status code")
|
||||
return fmt.Errorf("webhook returned non-success status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
|
||||
@@ -131,15 +131,9 @@ func (l *ContainerLogListener) startListening(c container.Container, client cont
|
||||
// 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
|
||||
}
|
||||
if found, err := client.FindContainer(l.ctx, c.ID, nil); err == nil {
|
||||
l.startListening(found, client)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.Warn().Str("containerID", c.ID).Msg("Could not find client for container")
|
||||
@@ -150,7 +144,7 @@ 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")
|
||||
log.Debug().Str("containerID", containerID).Msg("Stopped listening to container")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ func (m *Manager) AddSubscription(sub *Subscription) error {
|
||||
}
|
||||
|
||||
m.subscriptions.Store(sub.ID, sub)
|
||||
log.Info().Str("name", sub.Name).Int("id", sub.ID).Msg("Added subscription")
|
||||
log.Debug().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 {
|
||||
@@ -107,7 +107,7 @@ func (m *Manager) AddSubscription(sub *Subscription) error {
|
||||
// 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")
|
||||
log.Debug().Int("id", id).Str("name", sub.Name).Msg("Removed subscription")
|
||||
|
||||
// Update listener to stop streams that are no longer needed
|
||||
if m.listener != nil {
|
||||
@@ -146,7 +146,7 @@ func (m *Manager) ReplaceSubscription(sub *Subscription) error {
|
||||
}
|
||||
|
||||
m.subscriptions.Store(sub.ID, sub)
|
||||
log.Info().Str("name", sub.Name).Int("id", sub.ID).Msg("Replaced subscription")
|
||||
log.Debug().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 {
|
||||
@@ -246,20 +246,20 @@ func (m *Manager) UpdateSubscription(id int, updates map[string]any) error {
|
||||
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")
|
||||
log.Debug().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")
|
||||
log.Debug().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")
|
||||
log.Debug().Int("id", id).Msg("Removed dispatcher")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,10 +283,11 @@ func (m *Manager) Dispatchers() []DispatcherConfig {
|
||||
switch v := d.(type) {
|
||||
case *dispatcher.WebhookDispatcher:
|
||||
result = append(result, DispatcherConfig{
|
||||
ID: id,
|
||||
Name: v.Name,
|
||||
Type: "webhook",
|
||||
URL: v.URL,
|
||||
ID: id,
|
||||
Name: v.Name,
|
||||
Type: "webhook",
|
||||
URL: v.URL,
|
||||
Template: v.TemplateText,
|
||||
})
|
||||
}
|
||||
return true
|
||||
@@ -427,12 +428,16 @@ func (m *Manager) LoadConfig(r io.Reader) error {
|
||||
var d dispatcher.Dispatcher
|
||||
switch dispatcherConfig.Type {
|
||||
case "webhook":
|
||||
d = dispatcher.NewWebhookDispatcher(dispatcherConfig.Name, dispatcherConfig.URL)
|
||||
webhook, err := dispatcher.NewWebhookDispatcher(dispatcherConfig.Name, dispatcherConfig.URL, dispatcherConfig.Template)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create webhook dispatcher %s: %w", dispatcherConfig.Name, err)
|
||||
}
|
||||
d = webhook
|
||||
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")
|
||||
log.Debug().Int("id", dispatcherConfig.ID).Msg("Loaded dispatcher")
|
||||
}
|
||||
|
||||
// Update listener to start streams for loaded subscriptions
|
||||
@@ -466,6 +471,6 @@ func (m *Manager) loadSubscription(sub *Subscription) error {
|
||||
}
|
||||
|
||||
m.subscriptions.Store(sub.ID, sub)
|
||||
log.Info().Str("name", sub.Name).Int("id", sub.ID).Msg("Loaded subscription")
|
||||
log.Debug().Str("name", sub.Name).Int("id", sub.ID).Msg("Loaded subscription")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -124,10 +124,11 @@ func (s *Subscription) AddTriggeredContainer(id string) {
|
||||
|
||||
// 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"`
|
||||
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"`
|
||||
Template string `json:"template,omitempty" yaml:"template,omitempty"` // Go template for custom payload format
|
||||
}
|
||||
|
||||
// Config represents the persisted notification configuration
|
||||
|
||||
@@ -224,6 +224,13 @@ notifications:
|
||||
cloud-description: Push, email, and dashboard
|
||||
webhook-url: Webhook URL
|
||||
webhook-url-placeholder: https://hooks.foo.com/services/...
|
||||
payload-format: Payload Format
|
||||
format-slack: Slack
|
||||
format-discord: Discord
|
||||
format-ntfy: ntfy
|
||||
format-custom: Custom
|
||||
template: Template
|
||||
template-hint: Go template syntax
|
||||
test: Test
|
||||
cancel: Cancel
|
||||
save: Save
|
||||
|
||||
Reference in New Issue
Block a user