43 changed files with 1593 additions and 39 deletions
@ -0,0 +1,44 @@ |
|||
# Whether to open mock |
|||
VITE_USE_MOCK=false |
|||
|
|||
# public path |
|||
VITE_PUBLIC_PATH=/ |
|||
|
|||
# Delete console |
|||
VITE_DROP_CONSOLE=true |
|||
|
|||
# Whether to enable gzip or brotli compression |
|||
# Optional: gzip | brotli | none |
|||
# If you need multiple forms, you can use `,` to separate |
|||
VITE_BUILD_COMPRESS='none' |
|||
|
|||
# Whether to delete origin files when using compress, default false |
|||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE=false |
|||
|
|||
# Basic interface address SPA |
|||
VITE_GLOB_API_URL=/api |
|||
|
|||
# File upload address, optional |
|||
# It can be forwarded by nginx or write the actual address directly |
|||
VITE_GLOB_UPLOAD_URL=/upload |
|||
|
|||
# Interface prefix |
|||
VITE_GLOB_API_URL_PREFIX= |
|||
|
|||
# Whether to enable image compression |
|||
VITE_USE_IMAGEMIN=true |
|||
|
|||
# use pwa |
|||
VITE_USE_PWA=false |
|||
|
|||
# Is it compatible with older browsers |
|||
VITE_LEGACY=false |
|||
|
|||
# Multi-tenancy key |
|||
VITE_GLOB_MULTITENANCY_KEY='__tenant' |
|||
|
|||
# STS Connect |
|||
VITE_GLOB_AUTHORITY='http://127.0.0.1:44385' |
|||
VITE_GLOB_CLIENT_ID='vue-admin-element' |
|||
VITE_GLOB_CLIENT_SECRET='1q2w3e*' |
|||
|
|||
@ -0,0 +1,30 @@ |
|||
import { PagedAndSortedResultRequestDto } from '../../model/baseModel'; |
|||
import { HttpStatusCode } from '/@/enums/httpEnum'; |
|||
|
|||
export interface WebhookEvent { |
|||
tenantId?: string; |
|||
webhookName: string; |
|||
data: string; |
|||
creationTime: Date; |
|||
} |
|||
|
|||
export interface WebhookSendAttempt { |
|||
id: string; |
|||
tenantId?: string; |
|||
webhookEventId: string; |
|||
webhookSubscriptionId: string; |
|||
response: string; |
|||
responseStatusCode?: HttpStatusCode; |
|||
creationTime: Date; |
|||
lastModificationTime?: Date; |
|||
webhookEvent: WebhookEvent; |
|||
} |
|||
|
|||
export interface WebhookSendAttemptGetListInput extends PagedAndSortedResultRequestDto { |
|||
filter?: string; |
|||
webhookEventId?: string; |
|||
subscriptionId?: string; |
|||
responseStatusCode?: HttpStatusCode; |
|||
beginCreationTime?: Date; |
|||
endCreationTime?: Date; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
import { CreationAuditedEntityDto, PagedAndSortedResultRequestDto } from '../../model/baseModel'; |
|||
|
|||
export interface WebhookSubscription extends CreationAuditedEntityDto { |
|||
id: string; |
|||
tenantId?: string; |
|||
webhookUri: string; |
|||
secret: string; |
|||
isActive: boolean; |
|||
webhooks: string[]; |
|||
headers: { [key: string]: string }; |
|||
} |
|||
|
|||
export interface WebhookSubscriptionCreateOrUpdate { |
|||
webhookUri: string; |
|||
secret: string; |
|||
isActive: boolean; |
|||
webhooks: string[]; |
|||
headers: { [key: string]: string }; |
|||
} |
|||
|
|||
export type CreateWebhookSubscription = WebhookSubscriptionCreateOrUpdate; |
|||
|
|||
export type UpdateWebhookSubscription = WebhookSubscriptionCreateOrUpdate; |
|||
|
|||
export interface WebhookAvailable { |
|||
name: string; |
|||
displayName: string; |
|||
description: string; |
|||
} |
|||
|
|||
export interface WebhookAvailableGroup { |
|||
name: string; |
|||
displayName: string; |
|||
webhooks: WebhookAvailable[]; |
|||
} |
|||
|
|||
export interface WebhookSubscriptionGetListInput extends PagedAndSortedResultRequestDto { |
|||
filter?: string; |
|||
tenantId?: string; |
|||
webhookUri?: string; |
|||
secret?: string; |
|||
isActive?: boolean; |
|||
webhooks?: string; |
|||
beginCreationTime?: Date; |
|||
endCreationTime?: Date; |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
import { defAbpHttp } from '/@/utils/http/abp'; |
|||
import { PagedResultDto } from '../model/baseModel'; |
|||
import { WebhookSendAttempt, WebhookSendAttemptGetListInput } from './model/sendAttemptsModel'; |
|||
|
|||
const remoteServiceName = 'WebhooksManagement'; |
|||
const controllerName = 'WebhookSendRecord'; |
|||
|
|||
export const getById = (id: string) => { |
|||
return defAbpHttp.request<WebhookSendAttempt>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'GetAsync', |
|||
params: { |
|||
id: id, |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
export const deleteById = (id: string) => { |
|||
return defAbpHttp.request<WebhookSendAttempt>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'DeleteAsync', |
|||
params: { |
|||
id: id, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
export const getList = (input: WebhookSendAttemptGetListInput) => { |
|||
return defAbpHttp.request<PagedResultDto<WebhookSendAttempt>>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'GetListAsync', |
|||
params: input, |
|||
}); |
|||
}; |
|||
|
|||
export const resend = (id: string) => { |
|||
return defAbpHttp.request<void>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'ResendAsync', |
|||
params: { |
|||
id: id, |
|||
}, |
|||
}); |
|||
} |
|||
@ -0,0 +1,72 @@ |
|||
import { defAbpHttp } from '/@/utils/http/abp'; |
|||
import { |
|||
WebhookSubscription, |
|||
WebhookAvailableGroup, |
|||
CreateWebhookSubscription, |
|||
UpdateWebhookSubscription, |
|||
WebhookSubscriptionGetListInput, |
|||
} from './model/subscriptionsModel'; |
|||
import { ListResultDto, PagedResultDto } from '../model/baseModel'; |
|||
|
|||
const remoteServiceName = 'WebhooksManagement'; |
|||
const controllerName = 'WebhookSubscription'; |
|||
|
|||
export const create = (input: CreateWebhookSubscription) => { |
|||
return defAbpHttp.request<WebhookSubscription>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'CreateAsync', |
|||
data: input, |
|||
}); |
|||
}; |
|||
|
|||
export const update = (id: string, input: UpdateWebhookSubscription) => { |
|||
return defAbpHttp.request<WebhookSubscription>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'UpdateAsync', |
|||
data: input, |
|||
params: { |
|||
id: id, |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
export const getById = (id: string) => { |
|||
return defAbpHttp.request<WebhookSubscription>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'GetAsync', |
|||
params: { |
|||
id: id, |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
export const deleteById = (id: string) => { |
|||
return defAbpHttp.request<WebhookSubscription>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'DeleteAsync', |
|||
params: { |
|||
id: id, |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
export const getList = (input: WebhookSubscriptionGetListInput) => { |
|||
return defAbpHttp.request<PagedResultDto<WebhookSubscription>>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'GetListAsync', |
|||
params: input, |
|||
}); |
|||
}; |
|||
|
|||
export const getAllAvailableWebhooks = () => { |
|||
return defAbpHttp.request<ListResultDto<WebhookAvailableGroup>>({ |
|||
service: remoteServiceName, |
|||
controller: controllerName, |
|||
action: 'GetAllAvailableWebhooksAsync', |
|||
}); |
|||
}; |
|||
@ -0,0 +1,163 @@ |
|||
<template> |
|||
<BasicModal |
|||
@register="registerModal" |
|||
:width="900" |
|||
:height="500" |
|||
:title="L('SendAttempts')" |
|||
:mask-closable="false" |
|||
> |
|||
<Form |
|||
ref="formElRef" |
|||
:colon="true" |
|||
label-align="left" |
|||
:label-col="{ span: 6 }" |
|||
:wrapper-col="{ span: 18 }" |
|||
:model="modelRef" |
|||
> |
|||
<Tabs v-model:activeKey="activeKey"> |
|||
<TabPane key="basic" :tab="L('BasicInfo')"> |
|||
<FormItem :label="L('DisplayName:CreationTime')"> |
|||
<Input readonly :value="getDateTime(modelRef.creationTime)" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:ResponseStatusCode')"> |
|||
<Tag v-if="modelRef.responseStatusCode" :color="getHttpStatusColor(modelRef.responseStatusCode)">{{ httpStatusCodeMap[modelRef.responseStatusCode] }}</Tag> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:Response')"> |
|||
<CodeEditor readonly style="height: 300px;" :mode="MODE.HTML" v-model:value="modelRef.response" /> |
|||
</FormItem> |
|||
</TabPane> |
|||
|
|||
<TabPane v-if="modelRef.webhookEvent" key="event" :tab="L('WebhookEvent')"> |
|||
<FormItem :label="L('DisplayName:WebhookEventId')"> |
|||
<Input readonly :value="modelRef.webhookEventId" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:WebhookName')"> |
|||
<Input readonly :value="modelRef.webhookEvent.webhookName" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:CreationTime')"> |
|||
<Input readonly :value="getDateTime(modelRef.webhookEvent.creationTime)" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:Data')"> |
|||
<CodeEditor readonly style="height: 300px;" :mode="MODE.JSON" v-model:value="modelRef.webhookEvent.data" /> |
|||
</FormItem> |
|||
</TabPane> |
|||
|
|||
<TabPane v-if="subscriptionRef.id" key="subscription" :tab="L('Subscriptions')"> |
|||
<FormItem :label="L('DisplayName:SubscriptionId')"> |
|||
<Input readonly :value="modelRef.webhookSubscriptionId" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:IsActive')"> |
|||
<Checkbox disabled v-model:checked="subscriptionRef.isActive">{{ L('DisplayName:IsActive') }}</Checkbox> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:WebhookUri')"> |
|||
<Input readonly :value="subscriptionRef.webhookUri" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:Secret')"> |
|||
<Input readonly :value="subscriptionRef.secret" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:CreationTime')"> |
|||
<Input readonly :value="getDateTime(subscriptionRef.creationTime)" /> |
|||
</FormItem> |
|||
<FormItem :label="L('DisplayName:Webhooks')"> |
|||
<TextArea readonly :value="getWebhooks(subscriptionRef.webhooks)" :auto-size="{ minRows: 5, maxRows: 10 }" /> |
|||
</FormItem> |
|||
<FormItem name="headers" :label="L('DisplayName:Headers')"> |
|||
<CodeEditor readonly style="height: 300px;" :mode="MODE.JSON" v-model:value="subscriptionRef.headers" /> |
|||
</FormItem> |
|||
</TabPane> |
|||
</Tabs> |
|||
</Form> |
|||
</BasicModal> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref, computed } from 'vue'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { |
|||
Checkbox, |
|||
Form, |
|||
Tabs, |
|||
Tag, |
|||
Input, |
|||
} from 'ant-design-vue'; |
|||
import { CodeEditor, MODE } from '/@/components/CodeEditor'; |
|||
import { BasicModal, useModalInner } from '/@/components/Modal'; |
|||
import { getById } from '/@/api/webhooks/send-attempts'; |
|||
import { getById as getSubscription } from '/@/api/webhooks/subscriptions'; |
|||
import { WebhookSendAttempt } from '/@/api/webhooks/model/sendAttemptsModel'; |
|||
import { WebhookSubscription } from '/@/api/webhooks/model/subscriptionsModel'; |
|||
import { httpStatusCodeMap, getHttpStatusColor } from '../../typing'; |
|||
import { formatToDateTime } from '/@/utils/dateUtil'; |
|||
|
|||
const FormItem = Form.Item; |
|||
const TabPane = Tabs.TabPane; |
|||
const TextArea = Input.TextArea; |
|||
|
|||
const { L } = useLocalization('WebhooksManagement'); |
|||
const formElRef = ref<any>(); |
|||
const activeKey = ref('basic'); |
|||
const modelRef = ref<WebhookSendAttempt>(getDefaultModel()); |
|||
const subscriptionRef = ref<WebhookSubscription>(getDefaultSubscription()); |
|||
const [registerModal] = useModalInner((model) => { |
|||
activeKey.value = 'basic'; |
|||
fetchModel(model.id); |
|||
}); |
|||
const getDateTime = computed(() => { |
|||
return (date?: Date) => { |
|||
return date ? formatToDateTime(date) : ''; |
|||
} |
|||
}); |
|||
const getWebhooks = computed(() => { |
|||
return (webhooks: string[]) => { |
|||
return webhooks.reduce((hook, p) => hook + p + '\n', ''); |
|||
} |
|||
}) |
|||
|
|||
function fetchModel(id: string) { |
|||
if (!id) { |
|||
modelRef.value = getDefaultModel(); |
|||
return; |
|||
} |
|||
getById(id).then((res) => { |
|||
modelRef.value = res; |
|||
fetchSubscription(res.webhookSubscriptionId); |
|||
}); |
|||
} |
|||
|
|||
function fetchSubscription(id: string) { |
|||
getSubscription(id).then((res) => { |
|||
subscriptionRef.value = res; |
|||
}); |
|||
} |
|||
|
|||
function getDefaultModel() : WebhookSendAttempt { |
|||
return { |
|||
id: '', |
|||
webhookEventId: '', |
|||
webhookSubscriptionId: '', |
|||
webhookEvent: { |
|||
tenantId: undefined, |
|||
webhookName: '', |
|||
data: '{}', |
|||
creationTime: new Date(), |
|||
}, |
|||
response: '', |
|||
responseStatusCode: undefined, |
|||
creationTime: new Date(), |
|||
lastModificationTime: undefined, |
|||
} |
|||
} |
|||
|
|||
function getDefaultSubscription() : WebhookSubscription { |
|||
return { |
|||
id: '', |
|||
webhooks: [], |
|||
webhookUri: '', |
|||
headers: {}, |
|||
secret: '', |
|||
isActive: true, |
|||
creatorId: '', |
|||
creationTime: new Date(), |
|||
}; |
|||
} |
|||
</script> |
|||
@ -0,0 +1,97 @@ |
|||
<template> |
|||
<div class="content"> |
|||
<BasicTable @register="registerTable"> |
|||
<template #code="{ record }"> |
|||
<Tag :color="getHttpStatusColor(record.responseStatusCode)">{{ httpStatusCodeMap[record.responseStatusCode] }}</Tag> |
|||
</template> |
|||
<template #action="{ record }"> |
|||
<TableAction |
|||
:stop-button-propagation="true" |
|||
:actions="[ |
|||
{ |
|||
auth: 'AbpWebhooks.SendAttempts', |
|||
label: L('Edit'), |
|||
icon: 'ant-design:edit-outlined', |
|||
onClick: handleEdit.bind(null, record), |
|||
}, |
|||
{ |
|||
auth: 'AbpWebhooks.SendAttempts.Delete', |
|||
color: 'error', |
|||
label: L('Delete'), |
|||
icon: 'ant-design:delete-outlined', |
|||
onClick: handleDelete.bind(null, record), |
|||
}, |
|||
]" |
|||
:dropDownActions="[ |
|||
{ |
|||
auth: 'AbpWebhooks.SendAttempts.Resend', |
|||
label: L('Resend'), |
|||
ifShow: [JobStatus.Running, JobStatus.FailedRetry].includes(record.status), |
|||
onClick: handlePause.bind(null, record), |
|||
}, |
|||
]" |
|||
/> |
|||
</template> |
|||
</BasicTable> |
|||
<SendAttemptModal @register="registerModal" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { Switch, Tag } from 'ant-design-vue'; |
|||
import { useMessage } from '/@/hooks/web/useMessage'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { useModal } from '/@/components/Modal'; |
|||
import { BasicTable, TableAction, useTable } from '/@/components/Table'; |
|||
import { formatPagedRequest } from '/@/utils/http/abp/helper'; |
|||
import { getDataColumns } from '../datas/TableData'; |
|||
import { getSearchFormSchemas } from '../datas/ModalData'; |
|||
import { httpStatusCodeMap, getHttpStatusColor } from '../../typing'; |
|||
import { getList } from '/@/api/webhooks/send-attempts'; |
|||
import SendAttemptModal from './SendAttemptModal.vue'; |
|||
|
|||
const { createConfirm } = useMessage(); |
|||
const { L } = useLocalization('WebhooksManagement'); |
|||
const [registerModal, { openModal }] = useModal(); |
|||
const [registerTable, { reload }] = useTable({ |
|||
rowKey: 'id', |
|||
title: L('SendAttempts'), |
|||
columns: getDataColumns(), |
|||
api: getList, |
|||
beforeFetch: formatPagedRequest, |
|||
pagination: true, |
|||
striped: false, |
|||
useSearchForm: true, |
|||
showTableSetting: true, |
|||
bordered: true, |
|||
showIndexColumn: false, |
|||
canResize: false, |
|||
immediate: true, |
|||
clickToRowSelect: false, |
|||
formConfig: getSearchFormSchemas(), |
|||
actionColumn: { |
|||
width: 220, |
|||
title: L('Actions'), |
|||
dataIndex: 'action', |
|||
slots: { customRender: 'action' }, |
|||
}, |
|||
}); |
|||
|
|||
function handleEdit(record) { |
|||
openModal(true, record); |
|||
} |
|||
|
|||
function handleDelete(record) { |
|||
createConfirm({ |
|||
iconType: 'warning', |
|||
title: L('AreYouSure'), |
|||
content: L('ItemWillBeDeletedMessage'), |
|||
okCancel: true, |
|||
onOk: () => { |
|||
deleteById(record.id).then(() => { |
|||
reload(); |
|||
}); |
|||
}, |
|||
}); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,84 @@ |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { FormProps } from '/@/components/Form'; |
|||
import { getList as getTenants } from '/@/api/saas/tenant'; |
|||
import { getList as getSubscriptions } from '/@/api/webhooks/subscriptions'; |
|||
import { httpStatusOptions } from '../../typing'; |
|||
|
|||
const { L } = useLocalization('WebhooksManagement', 'AbpUi'); |
|||
|
|||
export function getSearchFormSchemas(): Partial<FormProps> { |
|||
return { |
|||
labelWidth: 100, |
|||
schemas: [ |
|||
{ |
|||
field: 'tenantId', |
|||
component: 'ApiSelect', |
|||
label: L('DisplayName:TenantId'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
api: getTenants, |
|||
params: { |
|||
skipCount: 0, |
|||
maxResultCount: 100, |
|||
}, |
|||
resultField: 'items', |
|||
labelField: 'name', |
|||
valueField: 'id', |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'subscriptionId', |
|||
component: 'ApiSelect', |
|||
label: L('DisplayName:Subscription'), |
|||
colProps: { span: 12 }, |
|||
componentProps: { |
|||
api: getSubscriptions, |
|||
params: { |
|||
skipCount: 0, |
|||
maxResultCount: 100, |
|||
}, |
|||
resultField: 'items', |
|||
labelField: 'webhookUri', |
|||
valueField: 'id', |
|||
} |
|||
}, |
|||
{ |
|||
field: 'responseStatusCode', |
|||
component: 'Select', |
|||
label: L('DisplayName:ResponseStatusCode'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
options: httpStatusOptions, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'beginCreationTime', |
|||
component: 'DatePicker', |
|||
label: L('DisplayName:BeginCreationTime'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
style: { |
|||
width: '100%', |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'endCreationTime', |
|||
component: 'DatePicker', |
|||
label: L('DisplayName:EndCreationTime'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
style: { |
|||
width: '100%', |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'filter', |
|||
component: 'Input', |
|||
label: L('Search'), |
|||
colProps: { span: 12 }, |
|||
}, |
|||
], |
|||
}; |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { BasicColumn } from '/@/components/Table'; |
|||
import { formatToDateTime } from '/@/utils/dateUtil'; |
|||
|
|||
const { L } = useLocalization('WebhooksManagement'); |
|||
|
|||
export function getDataColumns(): BasicColumn[] { |
|||
return [ |
|||
{ |
|||
title: 'id', |
|||
dataIndex: 'id', |
|||
width: 1, |
|||
ifShow: false, |
|||
}, |
|||
{ |
|||
title: L('DisplayName:TenantId'), |
|||
dataIndex: 'tenantId', |
|||
align: 'left', |
|||
width: 150, |
|||
sorter: true, |
|||
fixed: 'left', |
|||
}, |
|||
{ |
|||
title: L('DisplayName:ResponseStatusCode'), |
|||
dataIndex: 'responseStatusCode', |
|||
align: 'left', |
|||
width: 180, |
|||
sorter: true, |
|||
slots: { |
|||
customRender: 'code', |
|||
} |
|||
}, |
|||
{ |
|||
title: L('DisplayName:CreationTime'), |
|||
dataIndex: 'creationTime', |
|||
align: 'left', |
|||
width: 150, |
|||
sorter: true, |
|||
format: (text) => { |
|||
return formatToDateTime(text); |
|||
}, |
|||
}, |
|||
{ |
|||
title: L('DisplayName:Response'), |
|||
dataIndex: 'response', |
|||
align: 'left', |
|||
width: 'auto', |
|||
sorter: true, |
|||
}, |
|||
]; |
|||
} |
|||
|
|||
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<SendAttemptTable /> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import SendAttemptTable from './components/SendAttemptTable.vue'; |
|||
export default defineComponent({ |
|||
name: 'SendAttempts', |
|||
components: { |
|||
SendAttemptTable, |
|||
}, |
|||
setup() {}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,179 @@ |
|||
<template> |
|||
<BasicModal |
|||
@register="registerModal" |
|||
:width="800" |
|||
:height="400" |
|||
:title="modalTitle" |
|||
:mask-closable="false" |
|||
@ok="handleSubmit" |
|||
> |
|||
<Form |
|||
ref="formElRef" |
|||
label-align="left" |
|||
layout="horizontal" |
|||
:label-col="{ span: 6 }" |
|||
:wrapper-col="{ span: 18 }" |
|||
:model="modelRef" |
|||
:rules="modelRules" |
|||
> |
|||
<FormItem name="tenantId" :label="L('DisplayName:TenantId')"> |
|||
<Select v-model:value="modelRef.tenantId"> |
|||
<SelectOption |
|||
v-for="tenant in tenantsRef" |
|||
:key="tenant.id" |
|||
:value="tenant.id" |
|||
>{{ tenant.name }}</SelectOption> |
|||
</Select> |
|||
</FormItem> |
|||
<FormItem name="isActive" :label="L('DisplayName:IsActive')"> |
|||
<Checkbox v-model:checked="modelRef.isActive">{{ L('DisplayName:IsActive') }}</Checkbox> |
|||
</FormItem> |
|||
<FormItem name="webhookUri" required :label="L('DisplayName:WebhookUri')"> |
|||
<Input v-model:value="modelRef.webhookUri" autocomplete="off" /> |
|||
</FormItem> |
|||
<FormItem name="secret" required :label="L('DisplayName:Secret')"> |
|||
<Input v-model:value="modelRef.secret" autocomplete="off" /> |
|||
</FormItem> |
|||
<FormItem name="webhooks" :label="L('DisplayName:Webhooks')"> |
|||
<Select v-model:value="modelRef.webhooks" mode="multiple"> |
|||
<SelectGroup v-for="group in webhooksGroupRef" :key="group.name" :label="group.displayName"> |
|||
<SelectOption |
|||
v-for="option in group.webhooks" |
|||
:key="option.name" |
|||
:value="option.name" |
|||
>{{ option.displayName }}</SelectOption> |
|||
</SelectGroup> |
|||
</Select> |
|||
</FormItem> |
|||
<FormItem name="headers" :label="L('DisplayName:Headers')"> |
|||
<CodeEditor style="height: 300px;" :mode="MODE.JSON" v-model:value="modelRef.headers" /> |
|||
</FormItem> |
|||
</Form> |
|||
</BasicModal> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed, ref, reactive, unref, onMounted, nextTick } from 'vue'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { useValidation } from '/@/hooks/abp/useValidation'; |
|||
import { useMessage } from '/@/hooks/web/useMessage'; |
|||
import { |
|||
Checkbox, |
|||
Form, |
|||
Select, |
|||
Input, |
|||
} from 'ant-design-vue'; |
|||
import { CodeEditor, MODE } from '/@/components/CodeEditor'; |
|||
import { BasicModal, useModalInner } from '/@/components/Modal'; |
|||
import { Tenant } from '/@/api/saas/model/tenantModel'; |
|||
import { getList as getTenants } from '/@/api/saas/tenant'; |
|||
import { getById, create, update, getAllAvailableWebhooks } from '/@/api/webhooks/subscriptions'; |
|||
import { WebhookSubscription, WebhookAvailableGroup } from '/@/api/webhooks/model/subscriptionsModel'; |
|||
|
|||
const FormItem = Form.Item; |
|||
const SelectGroup = Select.OptGroup; |
|||
const SelectOption = Select.Option; |
|||
|
|||
const emit = defineEmits(['change', 'register']); |
|||
const { L } = useLocalization('WebhooksManagement'); |
|||
const { ruleCreator } = useValidation(); |
|||
const { createMessage } = useMessage(); |
|||
const formElRef = ref<any>(); |
|||
const tenantsRef = ref<Tenant[]>([]); |
|||
const webhooksGroupRef = ref<WebhookAvailableGroup[]>([]); |
|||
const modelRef = ref<WebhookSubscription>(getDefaultModel()); |
|||
const [registerModal, { closeModal, changeOkLoading }] = useModalInner((model) => { |
|||
fetchModel(model.id); |
|||
nextTick(() => { |
|||
const formEl = unref(formElRef); |
|||
formEl?.clearValidate(); |
|||
}); |
|||
}); |
|||
const isEditModal = computed(() => { |
|||
if (modelRef.value.id) { |
|||
return true; |
|||
} |
|||
return false; |
|||
}); |
|||
const modalTitle = computed(() => { |
|||
if (!isEditModal.value) { |
|||
return L('Subscriptions:AddNew'); |
|||
} |
|||
return L('Subscriptions:Edit'); |
|||
}); |
|||
const modelRules = reactive({ |
|||
webhookUri: ruleCreator.fieldRequired({ |
|||
name: 'WebhookUri', |
|||
resourceName: 'WebhooksManagement', |
|||
prefix: 'DisplayName', |
|||
}), |
|||
secret: ruleCreator.fieldRequired({ |
|||
name: 'Secret', |
|||
resourceName: 'WebhooksManagement', |
|||
prefix: 'DisplayName', |
|||
}), |
|||
}); |
|||
|
|||
onMounted(() => { |
|||
fetchTenants(); |
|||
fetchAvailableWebhooks(); |
|||
}); |
|||
|
|||
function fetchAvailableWebhooks() { |
|||
getAllAvailableWebhooks().then((res) => { |
|||
webhooksGroupRef.value = res.items; |
|||
}); |
|||
} |
|||
|
|||
function fetchTenants() { |
|||
getTenants({ |
|||
skipCount: 0, |
|||
maxResultCount: 100, |
|||
sorting: undefined, |
|||
}).then((res) => { |
|||
tenantsRef.value = res.items; |
|||
}) |
|||
} |
|||
|
|||
function fetchModel(id: string) { |
|||
if (!id) { |
|||
modelRef.value = getDefaultModel(); |
|||
return; |
|||
} |
|||
getById(id).then((res) => { |
|||
modelRef.value = res; |
|||
}); |
|||
} |
|||
|
|||
function handleSubmit() { |
|||
const formEl = unref(formElRef); |
|||
formEl?.validate().then(() => { |
|||
changeOkLoading(true); |
|||
const model = unref(modelRef); |
|||
const api = isEditModal.value |
|||
? update(model.id, Object.assign(model)) |
|||
: create(Object.assign(model)); |
|||
api.then(() => { |
|||
createMessage.success(L('Successful')); |
|||
formEl?.resetFields(); |
|||
closeModal(); |
|||
emit('change'); |
|||
}).finally(() => { |
|||
changeOkLoading(false); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function getDefaultModel() : WebhookSubscription { |
|||
return { |
|||
id: '', |
|||
webhooks: [], |
|||
webhookUri: '', |
|||
headers: {}, |
|||
secret: '', |
|||
isActive: true, |
|||
creatorId: '', |
|||
creationTime: new Date(), |
|||
}; |
|||
} |
|||
</script> |
|||
@ -0,0 +1,103 @@ |
|||
<template> |
|||
<div class="content"> |
|||
<BasicTable @register="registerTable"> |
|||
<template #toolbar> |
|||
<a-button |
|||
v-auth="['AbpWebhooks.Subscriptions.Create']" |
|||
type="primary" |
|||
@click="handleAddNew" |
|||
>{{ L('Subscriptions:AddNew') }}</a-button |
|||
> |
|||
</template> |
|||
<template #active="{ record }"> |
|||
<Switch :checked="record.isActive" disabled /> |
|||
</template> |
|||
<template #webhooks="{ record }"> |
|||
<Tag v-for="hook in record.webhooks" color="blue">{{ hook }}</Tag> |
|||
</template> |
|||
<template #action="{ record }"> |
|||
<TableAction |
|||
:stop-button-propagation="true" |
|||
:actions="[ |
|||
{ |
|||
auth: 'AbpWebhooks.Subscriptions.Update', |
|||
label: L('Edit'), |
|||
icon: 'ant-design:edit-outlined', |
|||
onClick: handleEdit.bind(null, record), |
|||
}, |
|||
{ |
|||
auth: 'AbpWebhooks.Subscriptions.Delete', |
|||
color: 'error', |
|||
label: L('Delete'), |
|||
icon: 'ant-design:delete-outlined', |
|||
onClick: handleDelete.bind(null, record), |
|||
}, |
|||
]" |
|||
/> |
|||
</template> |
|||
</BasicTable> |
|||
<SubscriptionModal @register="registerModal" @change="reload" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { Switch, Tag } from 'ant-design-vue'; |
|||
import { useMessage } from '/@/hooks/web/useMessage'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { useModal } from '/@/components/Modal'; |
|||
import { BasicTable, TableAction, useTable } from '/@/components/Table'; |
|||
import { formatPagedRequest } from '/@/utils/http/abp/helper'; |
|||
import { getDataColumns } from '../datas/TableData'; |
|||
import { getSearchFormSchemas } from '../datas/ModalData'; |
|||
import { deleteById, getList } from '/@/api/webhooks/subscriptions'; |
|||
import SubscriptionModal from './SubscriptionModal.vue'; |
|||
|
|||
const { createConfirm } = useMessage(); |
|||
const { L } = useLocalization('WebhooksManagement'); |
|||
const [registerModal, { openModal }] = useModal(); |
|||
const [registerTable, { reload }] = useTable({ |
|||
rowKey: 'id', |
|||
title: L('Subscriptions'), |
|||
columns: getDataColumns(), |
|||
api: getList, |
|||
beforeFetch: formatPagedRequest, |
|||
pagination: true, |
|||
striped: false, |
|||
useSearchForm: true, |
|||
showTableSetting: true, |
|||
bordered: true, |
|||
showIndexColumn: false, |
|||
canResize: false, |
|||
immediate: true, |
|||
clickToRowSelect: false, |
|||
formConfig: getSearchFormSchemas(), |
|||
actionColumn: { |
|||
width: 220, |
|||
title: L('Actions'), |
|||
dataIndex: 'action', |
|||
slots: { customRender: 'action' }, |
|||
}, |
|||
}); |
|||
|
|||
function handleAddNew() { |
|||
openModal(true, { id: null }); |
|||
} |
|||
|
|||
function handleEdit(record) { |
|||
openModal(true, record); |
|||
} |
|||
|
|||
function handleDelete(record) { |
|||
createConfirm({ |
|||
iconType: 'warning', |
|||
title: L('AreYouSure'), |
|||
content: L('ItemWillBeDeletedMessage'), |
|||
okCancel: true, |
|||
onOk: () => { |
|||
deleteById(record.id).then(() => { |
|||
reload(); |
|||
}); |
|||
}, |
|||
}); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,108 @@ |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { FormProps } from '/@/components/Form'; |
|||
import { getList } from '/@/api/saas/tenant'; |
|||
import { getAllAvailableWebhooks } from '/@/api/webhooks/subscriptions'; |
|||
|
|||
const { L } = useLocalization('WebhooksManagement', 'AbpUi'); |
|||
|
|||
function getAllAvailables(): Promise<any> { |
|||
return getAllAvailableWebhooks().then((res) => { |
|||
return res.items.map((group) => { |
|||
return { |
|||
label: group.displayName, |
|||
value: group.name, |
|||
options: group.webhooks.map((p) => { |
|||
return { |
|||
label: p.displayName, |
|||
value: p.name, |
|||
} |
|||
}), |
|||
} |
|||
}) |
|||
}); |
|||
} |
|||
|
|||
export function getSearchFormSchemas(): Partial<FormProps> { |
|||
return { |
|||
labelWidth: 100, |
|||
schemas: [ |
|||
{ |
|||
field: 'tenantId', |
|||
component: 'ApiSelect', |
|||
label: L('DisplayName:TenantId'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
api: getList, |
|||
params: { |
|||
skipCount: 0, |
|||
maxResultCount: 1000, |
|||
}, |
|||
resultField: 'items', |
|||
labelField: 'name', |
|||
valueField: 'id', |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'webhookUri', |
|||
component: 'Input', |
|||
label: L('DisplayName:WebhookUri'), |
|||
colProps: { span: 12 }, |
|||
}, |
|||
{ |
|||
field: 'secret', |
|||
component: 'Input', |
|||
label: L('DisplayName:Secret'), |
|||
colProps: { span: 6 }, |
|||
}, |
|||
{ |
|||
field: 'isActive', |
|||
component: 'Checkbox', |
|||
label: L('DisplayName:IsActive'), |
|||
colProps: { span: 6 }, |
|||
defaultValue: true, |
|||
renderComponentContent: L('DisplayName:IsActive'), |
|||
}, |
|||
{ |
|||
field: 'webhooks', |
|||
component: 'ApiSelect', |
|||
label: L('DisplayName:Webhooks'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
api: () => getAllAvailables(), |
|||
showSearch: true, |
|||
filterOption: (onputValue: string, option: any) => { |
|||
return option.label.includes(onputValue); |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'beginCreationTime', |
|||
component: 'DatePicker', |
|||
label: L('DisplayName:BeginCreationTime'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
style: { |
|||
width: '100%', |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'endCreationTime', |
|||
component: 'DatePicker', |
|||
label: L('DisplayName:EndCreationTime'), |
|||
colProps: { span: 6 }, |
|||
componentProps: { |
|||
style: { |
|||
width: '100%', |
|||
}, |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'filter', |
|||
component: 'Input', |
|||
label: L('Search'), |
|||
colProps: { span: 24 }, |
|||
}, |
|||
], |
|||
}; |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { BasicColumn } from '/@/components/Table'; |
|||
import { formatToDateTime } from '/@/utils/dateUtil'; |
|||
|
|||
const { L } = useLocalization('WebhooksManagement'); |
|||
|
|||
export function getDataColumns(): BasicColumn[] { |
|||
return [ |
|||
{ |
|||
title: 'id', |
|||
dataIndex: 'id', |
|||
width: 1, |
|||
ifShow: false, |
|||
}, |
|||
{ |
|||
title: L('DisplayName:TenantId'), |
|||
dataIndex: 'tenantId', |
|||
align: 'left', |
|||
width: 150, |
|||
sorter: true, |
|||
fixed: 'left', |
|||
}, |
|||
{ |
|||
title: L('DisplayName:WebhookUri'), |
|||
dataIndex: 'webhookUri', |
|||
align: 'left', |
|||
width: 300, |
|||
sorter: true, |
|||
fixed: 'left', |
|||
slots: { |
|||
customRender: 'name', |
|||
} |
|||
}, |
|||
{ |
|||
title: L('DisplayName:IsActive'), |
|||
dataIndex: 'isActive', |
|||
align: 'left', |
|||
width: 180, |
|||
sorter: true, |
|||
slots: { |
|||
customRender: 'active', |
|||
} |
|||
}, |
|||
{ |
|||
title: L('DisplayName:CreationTime'), |
|||
dataIndex: 'creationTime', |
|||
align: 'left', |
|||
width: 150, |
|||
sorter: true, |
|||
format: (text) => { |
|||
return formatToDateTime(text); |
|||
}, |
|||
}, |
|||
{ |
|||
title: L('DisplayName:Webhooks'), |
|||
dataIndex: 'webhooks', |
|||
align: 'left', |
|||
width: 200, |
|||
sorter: true, |
|||
slots: { |
|||
customRender: 'webhooks', |
|||
}, |
|||
}, |
|||
]; |
|||
} |
|||
|
|||
@ -0,0 +1,16 @@ |
|||
<template> |
|||
<SubscriptionTable /> |
|||
</template> |
|||
|
|||
<script lang="ts"> |
|||
import { defineComponent } from 'vue'; |
|||
|
|||
import SubscriptionTable from './components/SubscriptionTable.vue'; |
|||
export default defineComponent({ |
|||
name: 'Subscriptions', |
|||
components: { |
|||
SubscriptionTable, |
|||
}, |
|||
setup() {}, |
|||
}); |
|||
</script> |
|||
@ -0,0 +1,76 @@ |
|||
import { HttpStatusCode } from '/@/enums/httpEnum'; |
|||
|
|||
export const httpStatusCodeMap = { |
|||
[HttpStatusCode.Continue]: '100 - Continue', |
|||
[HttpStatusCode.SwitchingProtocols]: '101 - Switching Protocols', |
|||
[HttpStatusCode.OK]: '200 - OK', |
|||
[HttpStatusCode.Created]: '201 - Created', |
|||
[HttpStatusCode.Accepted]: '202 - Accepted', |
|||
[HttpStatusCode.NonAuthoritativeInformation]: '203 - Non Authoritative Information', |
|||
[HttpStatusCode.NoContent]: '204 - No Content', |
|||
[HttpStatusCode.ResetContent]: '205 - Reset Content', |
|||
[HttpStatusCode.PartialContent]: '206 - Partial Content', |
|||
[HttpStatusCode.Ambiguous]: '300 - Ambiguous', |
|||
[HttpStatusCode.MultipleChoices]: '300 - Multiple Choices', |
|||
[HttpStatusCode.Moved]: '301 - Moved', |
|||
[HttpStatusCode.MovedPermanently]: '301 - Moved Permanently', |
|||
[HttpStatusCode.Found]: '302 - Found', |
|||
[HttpStatusCode.Redirect]: '302 - Redirect', |
|||
[HttpStatusCode.RedirectMethod]: '303 - Redirect Method', |
|||
[HttpStatusCode.SeeOther]: '303 - See Other', |
|||
[HttpStatusCode.NotModified]: '304 - Not Modified', |
|||
[HttpStatusCode.UseProxy]: '305 - Use Proxy', |
|||
[HttpStatusCode.Unused]: '306 - Unused', |
|||
[HttpStatusCode.RedirectKeepVerb]: '307 - Redirect Keep Verb', |
|||
[HttpStatusCode.TemporaryRedirect]: '307 - Temporary Redirect', |
|||
[HttpStatusCode.BadRequest]: '400 - Bad Request', |
|||
[HttpStatusCode.Unauthorized]: '401 - Unauthorized', |
|||
[HttpStatusCode.PaymentRequired]: '402 - Payment Required', |
|||
[HttpStatusCode.Forbidden]: '403 - Forbidden', |
|||
[HttpStatusCode.NotFound]: '404 - Not Found', |
|||
[HttpStatusCode.MethodNotAllowed]: '405 - Method Not Allowed', |
|||
[HttpStatusCode.NotAcceptable]: '406 - Not Acceptable', |
|||
[HttpStatusCode.ProxyAuthenticationRequired]: '407 - Proxy Authentication Required', |
|||
[HttpStatusCode.RequestTimeout]: '408 - Request Timeout', |
|||
[HttpStatusCode.Conflict]: '409 - Conflict', |
|||
[HttpStatusCode.Gone]: '410 - Gone', |
|||
[HttpStatusCode.LengthRequired]: '411 - Length Required', |
|||
[HttpStatusCode.PreconditionFailed]: '412 - Precondition Failed', |
|||
[HttpStatusCode.RequestEntityTooLarge]: '413 - Request Entity Too Large', |
|||
[HttpStatusCode.RequestUriTooLong]: '414 - Request Uri Too Long', |
|||
[HttpStatusCode.UnsupportedMediaType]: '415 - Unsupported Media Type', |
|||
[HttpStatusCode.RequestedRangeNotSatisfiable]: '416 - Requested Range Not Satisfiable', |
|||
[HttpStatusCode.ExpectationFailed]: '417 - Expectation Failed', |
|||
[HttpStatusCode.UpgradeRequired]: '426 - Upgrade Required', |
|||
[HttpStatusCode.InternalServerError]: '500 - Internal Server Error', |
|||
[HttpStatusCode.NotImplemented]: '501 - Not Implemented', |
|||
[HttpStatusCode.BadGateway]: '502 - Bad Gateway', |
|||
[HttpStatusCode.ServiceUnavailable]: '503 - Service Unavailable', |
|||
[HttpStatusCode.GatewayTimeout]: '504 - Gateway Timeout', |
|||
[HttpStatusCode.HttpVersionNotSupported]: '505 - Http Version Not Supported', |
|||
} |
|||
|
|||
export const httpStatusOptions = Object.keys(httpStatusCodeMap).map((key) => { |
|||
return { |
|||
label: httpStatusCodeMap[key], |
|||
value: key, |
|||
}; |
|||
}) |
|||
|
|||
export function getHttpStatusColor(statusCode: HttpStatusCode) { |
|||
if (statusCode < 200) { |
|||
return 'default'; |
|||
} |
|||
if (statusCode >= 200 && statusCode < 300) { |
|||
return 'success'; |
|||
} |
|||
if (statusCode >= 300 && statusCode < 400) { |
|||
return 'processing'; |
|||
} |
|||
if (statusCode >= 400 && statusCode < 500) { |
|||
return 'warning'; |
|||
} |
|||
if (statusCode >= 500) { |
|||
return 'error'; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
using LINGYUN.Abp.MultiTenancy; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace LINGYUN.Abp.Saas.Tenants; |
|||
public class ConnectionStringInvalidator : |
|||
IDistributedEventHandler<ConnectionStringCreatedEventData>, |
|||
IDistributedEventHandler<ConnectionStringDeletedEventData>, |
|||
ITransientDependency |
|||
{ |
|||
protected IDistributedCache<TenantCacheItem> Cache { get; } |
|||
|
|||
public ConnectionStringInvalidator(IDistributedCache<TenantCacheItem> cache) |
|||
{ |
|||
Cache = cache; |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(ConnectionStringCreatedEventData eventData) |
|||
{ |
|||
await Cache.RemoveAsync( |
|||
TenantCacheItem.CalculateCacheKey( |
|||
eventData.TenantId, |
|||
eventData.TenantName), |
|||
considerUow: true); |
|||
} |
|||
|
|||
public virtual async Task HandleEventAsync(ConnectionStringDeletedEventData eventData) |
|||
{ |
|||
await Cache.RemoveAsync( |
|||
TenantCacheItem.CalculateCacheKey( |
|||
eventData.TenantId, |
|||
eventData.TenantName), |
|||
considerUow: true); |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
{ |
|||
"Routes": [ |
|||
{ |
|||
"DownstreamPathTemplate": "/api/abp/api-definition", |
|||
"DownstreamScheme": "http", |
|||
"DownstreamHostAndPorts": [ |
|||
{ |
|||
"Host": "127.0.0.1", |
|||
"Port": 30045 |
|||
} |
|||
], |
|||
"UpstreamPathTemplate": "/api/abp/webhook/api-definition", |
|||
"UpstreamHttpMethod": [ "GET" ], |
|||
"LoadBalancerOptions": { |
|||
"Type": "RoundRobin" |
|||
}, |
|||
"RateLimitOptions": {}, |
|||
"QoSOptions": { |
|||
"ExceptionsAllowedBeforeBreaking": 10, |
|||
"DurationOfBreak": 1000, |
|||
"TimeoutValue": 10000 |
|||
}, |
|||
"HttpHandlerOptions": { |
|||
"UseTracing": true |
|||
}, |
|||
"Key": "webhook-api-definition" |
|||
}, |
|||
{ |
|||
"DownstreamPathTemplate": "/api/webhooks/{everything}", |
|||
"DownstreamScheme": "http", |
|||
"DownstreamHostAndPorts": [ |
|||
{ |
|||
"Host": "127.0.0.1", |
|||
"Port": 30045 |
|||
} |
|||
], |
|||
"UpstreamPathTemplate": "/api/webhooks/{everything}", |
|||
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ], |
|||
"LoadBalancerOptions": { |
|||
"Type": "RoundRobin" |
|||
}, |
|||
"RateLimitOptions": { |
|||
"ClientWhitelist": [], |
|||
"EnableRateLimiting": true, |
|||
"Period": "1s", |
|||
"PeriodTimespan": 1, |
|||
"Limit": 5 |
|||
}, |
|||
"QoSOptions": { |
|||
"ExceptionsAllowedBeforeBreaking": 10, |
|||
"DurationOfBreak": 1000, |
|||
"TimeoutValue": 10000 |
|||
}, |
|||
"HttpHandlerOptions": { |
|||
"UseTracing": true |
|||
} |
|||
} |
|||
] |
|||
} |
|||
Loading…
Reference in new issue