mirror of https://github.com/nocodb/nocodb
Pranav C
3 years ago
7 changed files with 738 additions and 9 deletions
@ -0,0 +1,524 @@
|
||||
<template> |
||||
<v-form v-if="hook" ref="form" v-model="valid" class="mx-auto" lazy-validation> |
||||
<v-card-title> |
||||
Webhook / {{ hook.id ? hook.title : 'New' }} |
||||
<v-spacer /> |
||||
<v-btn |
||||
outlined |
||||
tooltip="Save" |
||||
small |
||||
:disabled="loading || !valid || !hook.event" |
||||
@click.prevent="$refs.webhookTest.testWebhook()" |
||||
> |
||||
Test webhook |
||||
</v-btn> |
||||
<v-btn |
||||
outlined |
||||
tooltip="Save" |
||||
color="primary" |
||||
small |
||||
:disabled="loading || !valid || !hook.event" |
||||
@click.prevent="saveHooks" |
||||
> |
||||
<v-icon small left> |
||||
save |
||||
</v-icon> |
||||
<!-- Save --> |
||||
{{ $t("general.save") }} |
||||
</v-btn> |
||||
</v-card-title> |
||||
<v-card-text> |
||||
<v-text-field |
||||
v-model="hook.title" |
||||
class="caption" |
||||
outlined |
||||
dense |
||||
:label="$t('general.title')" |
||||
required |
||||
:rules="[(v) => !!v || `${$t('general.required')}`]" |
||||
/> |
||||
|
||||
<webhook-event |
||||
:event.sync="hook.event" |
||||
:operation.sync="hook.operation" |
||||
/> |
||||
|
||||
<v-card class="mb-8"> |
||||
<v-card-text> |
||||
<v-checkbox |
||||
v-model="hook.condition" |
||||
dense |
||||
hide-details |
||||
class="mt-1" |
||||
label="On Condition" |
||||
@change="checkConditionAvail" |
||||
/> |
||||
|
||||
<column-filter |
||||
v-if="hook.condition" |
||||
:key="key" |
||||
ref="filter" |
||||
v-model="filters" |
||||
:meta="meta" |
||||
:field-list="fieldList" |
||||
dense |
||||
style="max-width: 100%" |
||||
:hook-id="hook.id" |
||||
web-hook |
||||
/> |
||||
</v-card-text> |
||||
</v-card> |
||||
|
||||
<v-select |
||||
v-model="hook.notification.type" |
||||
outlined |
||||
dense |
||||
:label="$t('general.notification')" |
||||
required |
||||
:items="notificationList" |
||||
:rules="[(v) => !!v || `${$t('general.required')}`]" |
||||
class="caption" |
||||
:prepend-inner-icon="notificationIcon[hook.notification.type]" |
||||
@change="onNotTypeChange" |
||||
> |
||||
<template #item="{ item }"> |
||||
<v-list-item-icon> |
||||
<v-icon small> |
||||
{{ notificationIcon[item] }} |
||||
</v-icon> |
||||
</v-list-item-icon> |
||||
<v-list-item-title> |
||||
{{ item }} |
||||
</v-list-item-title> |
||||
</template> |
||||
</v-select> |
||||
|
||||
<template v-if="hook.notification.type === 'URL'"> |
||||
<http-webhook v-model="notification" /> |
||||
</template> |
||||
|
||||
<template v-if="hook.notification.type === 'Slack'"> |
||||
<v-combobox |
||||
v-if="slackChannels" |
||||
v-model="notification.channels" |
||||
:rules="[(v) => !!v || `${$t('general.required')}`]" |
||||
:items="slackChannels" |
||||
item-text="channel" |
||||
label="Select Slack channels" |
||||
multiple |
||||
outlined |
||||
dense |
||||
class="caption" |
||||
/> |
||||
</template> |
||||
<template v-if="hook.notification.type === 'Microsoft Teams'"> |
||||
<v-combobox |
||||
v-if="teamsChannels" |
||||
v-model="notification.channels" |
||||
:rules="[(v) => !!v || `${$t('general.required')}`]" |
||||
:items="teamsChannels" |
||||
item-text="channel" |
||||
label="Select Teams channels" |
||||
multiple |
||||
outlined |
||||
dense |
||||
class="caption" |
||||
/> |
||||
</template> |
||||
<template v-if="hook.notification.type === 'Discord'"> |
||||
<v-combobox |
||||
v-if="discordChannels" |
||||
v-model="notification.channels" |
||||
:rules="[(v) => !!v || `${$t('general.required')}`]" |
||||
:items="discordChannels" |
||||
item-text="channel" |
||||
label="Select Discord channels" |
||||
multiple |
||||
outlined |
||||
dense |
||||
class="caption" |
||||
/> |
||||
</template> |
||||
<template v-if="hook.notification.type === 'Mattermost'"> |
||||
<v-combobox |
||||
v-if="mattermostChannels" |
||||
v-model="notification.channels" |
||||
:rules="[(v) => !!v || `${$t('general.required')}`]" |
||||
:items="mattermostChannels" |
||||
item-text="channel" |
||||
label="Select Mattermost channels" |
||||
multiple |
||||
outlined |
||||
dense |
||||
class="caption" |
||||
/> |
||||
</template> |
||||
|
||||
<template v-if="inputs[hook.notification.type] && notification"> |
||||
<template v-for="input in inputs[hook.notification.type]"> |
||||
<v-textarea |
||||
v-if="input.type === 'LongText'" |
||||
:key="input.key" |
||||
v-model="notification[input.key]" |
||||
class="caption" |
||||
dense |
||||
outlined |
||||
:label="input.label" |
||||
:rules="[ |
||||
(v) => |
||||
!input.required || !!v || `${$t('general.required')}`, |
||||
]" |
||||
/> |
||||
<v-text-field |
||||
v-else |
||||
:key="input.key" |
||||
v-model="notification[input.key]" |
||||
class="caption" |
||||
dense |
||||
outlined |
||||
:label="input.label" |
||||
:rules="[ |
||||
(v) => |
||||
!input.required || !!v || `${$t('general.required')}`, |
||||
]" |
||||
/> |
||||
</template> |
||||
</template> |
||||
</v-card-text> |
||||
|
||||
<v-card-text> |
||||
<span class="caption grey--text"> |
||||
<em>Available context variables are |
||||
<strong>data and user</strong></em> |
||||
<v-tooltip top> |
||||
<template #activator="{ on }"> |
||||
<v-icon |
||||
small |
||||
color="grey" |
||||
class="ml-2" |
||||
v-on="on" |
||||
>mdi-information</v-icon> |
||||
</template> |
||||
<span class="caption"> |
||||
<strong>data</strong> : Row data <br> |
||||
<strong>user</strong> : User information<br> |
||||
</span> |
||||
</v-tooltip> |
||||
<br> |
||||
<a |
||||
href="https://docs.nocodb.com/developer-resources/webhooks/" |
||||
> |
||||
<!--Document Reference--> |
||||
{{ $t("labels.docReference") }} |
||||
</a> |
||||
</span> |
||||
|
||||
<webhooks-test |
||||
ref="webhookTest" |
||||
class="mt-3" |
||||
:model-id="meta.id" |
||||
hide-test-btn |
||||
:hook="{ |
||||
...hook, |
||||
filters, |
||||
notification: { |
||||
...hook.notification, |
||||
payload: notification, |
||||
}, |
||||
}" |
||||
/> |
||||
</v-card-text> |
||||
</v-form> |
||||
<span v-else /> |
||||
</template> |
||||
|
||||
<script> |
||||
import WebhooksTest from '~/components/project/tableTabs/webhook/webhooksTest' |
||||
import WebhookEvent from '~/components/project/tableTabs/webhook/webhookEvent' |
||||
import HttpWebhook from '~/components/project/tableTabs/webhook/httpWebhook' |
||||
export default { |
||||
name: 'WebhookEditor', |
||||
components: { HttpWebhook, WebhookEvent, WebhooksTest }, |
||||
props: { |
||||
meta: Object |
||||
}, |
||||
data: () => ({ |
||||
hook: { |
||||
notification: { |
||||
} |
||||
}, |
||||
valid: false, |
||||
apps: {}, |
||||
slackChannels: null, |
||||
teamsChannels: null, |
||||
discordChannels: null, |
||||
mattermostChannels: null, |
||||
enableCondition: false, |
||||
notificationList: [ |
||||
'URL', |
||||
'Email', |
||||
'Slack', |
||||
'Microsoft Teams', |
||||
'Discord', |
||||
'Mattermost', |
||||
'Twilio', |
||||
'Whatsapp Twilio' |
||||
], |
||||
filters: [], |
||||
notificationIcon: { |
||||
URL: 'mdi-link', |
||||
Email: 'mdi-email', |
||||
Slack: 'mdi-slack', |
||||
'Microsoft Teams': 'mdi-microsoft-teams', |
||||
Discord: 'mdi-discord', |
||||
Mattermost: 'mdi-chat', |
||||
'Whatsapp Twilio': 'mdi-whatsapp', |
||||
Twilio: 'mdi-cellphone-message' |
||||
}, |
||||
inputs: { |
||||
Email: [ |
||||
{ |
||||
key: 'to', |
||||
label: 'To Address', |
||||
placeholder: 'To Address', |
||||
type: 'SingleLineText', |
||||
required: true |
||||
}, |
||||
{ |
||||
key: 'subject', |
||||
label: 'Subject', |
||||
placeholder: 'Subject', |
||||
type: 'SingleLineText', |
||||
required: true |
||||
}, |
||||
{ |
||||
key: 'body', |
||||
label: 'Body', |
||||
placeholder: 'Body', |
||||
type: 'LongText', |
||||
required: true |
||||
} |
||||
], |
||||
Slack: [ |
||||
{ |
||||
key: 'body', |
||||
label: 'Body', |
||||
placeholder: 'Body', |
||||
type: 'LongText', |
||||
required: true |
||||
} |
||||
], |
||||
'Microsoft Teams': [ |
||||
{ |
||||
key: 'body', |
||||
label: 'Body', |
||||
placeholder: 'Body', |
||||
type: 'LongText', |
||||
required: true |
||||
} |
||||
], |
||||
Discord: [ |
||||
{ |
||||
key: 'body', |
||||
label: 'Body', |
||||
placeholder: 'Body', |
||||
type: 'LongText', |
||||
required: true |
||||
} |
||||
], |
||||
Mattermost: [ |
||||
{ |
||||
key: 'body', |
||||
label: 'Body', |
||||
placeholder: 'Body', |
||||
type: 'LongText', |
||||
required: true |
||||
} |
||||
], |
||||
Twilio: [ |
||||
{ |
||||
key: 'body', |
||||
label: 'Body', |
||||
placeholder: 'Body', |
||||
type: 'LongText', |
||||
required: true |
||||
}, |
||||
{ |
||||
key: 'to', |
||||
label: 'Comma separated Mobile #', |
||||
placeholder: 'Comma separated Mobile #', |
||||
type: 'LongText', |
||||
required: true |
||||
} |
||||
], |
||||
'Whatsapp Twilio': [ |
||||
{ |
||||
key: 'body', |
||||
label: 'Body', |
||||
placeholder: 'Body', |
||||
type: 'LongText', |
||||
required: true |
||||
}, |
||||
{ |
||||
key: 'to', |
||||
label: 'Comma separated Mobile #', |
||||
placeholder: 'Comma separated Mobile #', |
||||
type: 'LongText', |
||||
required: true |
||||
} |
||||
] |
||||
} |
||||
|
||||
}), |
||||
created() { |
||||
this.loadPluginList() |
||||
}, |
||||
methods: { |
||||
async loadPluginList() { |
||||
try { |
||||
// const plugins = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'xcPluginList']) |
||||
const plugins = (await this.$api.plugin.list()).list |
||||
// plugins.push(...plugins.splice(0, 3)) |
||||
this.apps = plugins.reduce((o, p) => { |
||||
p.tags = p.tags ? p.tags.split(',') : [] |
||||
p.parsedInput = p.input && JSON.parse(p.input) |
||||
o[p.title] = p |
||||
return o |
||||
}, {}) |
||||
} catch (e) {} |
||||
}, |
||||
addNewHook() { |
||||
this.onEventChange() |
||||
this.$refs.form.resetValidation() |
||||
}, |
||||
async onNotTypeChange() { |
||||
this.notification = {} |
||||
if (this.hook.notification.type === 'Slack') { |
||||
this.slackChannels = |
||||
(this.apps && this.apps.Slack && this.apps.Slack.parsedInput) || [] |
||||
} |
||||
if (this.hook.notification.type === 'Microsoft Teams') { |
||||
this.teamsChannels = |
||||
(this.apps && |
||||
this.apps['Microsoft Teams'] && |
||||
this.apps['Microsoft Teams'].parsedInput) || |
||||
[] |
||||
} |
||||
if (this.hook.notification.type === 'Discord') { |
||||
this.discordChannels = |
||||
(this.apps && this.apps.Discord && this.apps.Discord.parsedInput) || |
||||
[] |
||||
} |
||||
if (this.hook.notification.type === 'Mattermost') { |
||||
this.mattermostChannels = |
||||
(this.apps && |
||||
this.apps.Mattermost && |
||||
this.apps.Mattermost.parsedInput) || |
||||
[] |
||||
} |
||||
this.$nextTick(() => this.$refs.form.validate()) |
||||
}, |
||||
async onEventChange() { |
||||
this.key++ |
||||
if (!this.hooks || !this.hooks.length) { |
||||
return |
||||
} |
||||
const { notification: { payload, type } = {}, ...hook } = |
||||
this.hooks[this.selectedHook] || {} |
||||
|
||||
this.hook = { |
||||
...hook, |
||||
notification: { |
||||
type |
||||
} |
||||
} |
||||
// this.enableCondition = !!(this.hook && this.hook.condition && Object.keys(this.hook.condition).length) |
||||
await this.onNotTypeChange() |
||||
this.notification = payload |
||||
if (this.hook.notification.type === 'Slack') { |
||||
this.notification.webhook_url = |
||||
this.notification.webhook_url && |
||||
this.notification.webhook_url.map(v => |
||||
this.slackChannels.find(s => v.webhook_url === s.webhook_url) |
||||
) |
||||
} |
||||
if (this.hook.notification.type === 'Microsoft Teams') { |
||||
this.notification.webhook_url = |
||||
this.notification.webhook_url && |
||||
this.notification.webhook_url.map(v => |
||||
this.teamsChannels.find(s => v.webhook_url === s.webhook_url) |
||||
) |
||||
} |
||||
if (this.hook.notification.type === 'Discord') { |
||||
this.notification.webhook_url = |
||||
this.notification.webhook_url && |
||||
this.notification.webhook_url.map(v => |
||||
this.discordChannels.find(s => v.webhook_url === s.webhook_url) |
||||
) |
||||
} |
||||
if (this.hook.notification.type === 'Mattermost') { |
||||
this.notification.webhook_url = |
||||
this.notification.webhook_url && |
||||
this.notification.webhook_url.map(v => |
||||
this.mattermostChannels.find(s => v.webhook_url === s.webhook_url) |
||||
) |
||||
} |
||||
if (this.hook.notification.type === 'URL') { |
||||
// |
||||
} |
||||
}, |
||||
async saveHooks() { |
||||
if (!this.$refs.form.validate() || !this.valid || !this.hook.event) { |
||||
return |
||||
} |
||||
this.loading = true |
||||
try { |
||||
let res |
||||
if (this.hook.id) { |
||||
res = await this.$api.dbTableWebhook.update(this.hook.id, { |
||||
...this.hook, |
||||
notification: { |
||||
...this.hook.notification, |
||||
payload: this.notification |
||||
} |
||||
}) |
||||
} else { |
||||
res = await this.$api.dbTableWebhook.create(this.meta.id, { |
||||
...this.hook, |
||||
notification: { |
||||
...this.hook.notification, |
||||
payload: this.notification |
||||
} |
||||
}) |
||||
} |
||||
|
||||
if (!this.hook.id && res) { |
||||
this.hook.id = res.id |
||||
} |
||||
if (this.$refs.filter) { |
||||
await this.$refs.filter.applyChanges(false, { |
||||
hookId: this.hook.id |
||||
}) |
||||
} |
||||
|
||||
this.$toast |
||||
.success('Webhook details updated successfully') |
||||
.goAway(3000) |
||||
} catch (e) { |
||||
this.$toast.error(e.message).goAway(3000) |
||||
} |
||||
this.loading = false |
||||
|
||||
this.$e('a:webhook:add', { |
||||
operation: this.hook.operation, |
||||
condition: this.hook.condition, |
||||
notification: this.hook.notification.type |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,140 @@
|
||||
<template> |
||||
<div> |
||||
<v-card-title> |
||||
Webhook |
||||
<v-spacer /> |
||||
<v-btn |
||||
outlined |
||||
tooltip="Save" |
||||
small |
||||
> |
||||
Create webhook |
||||
</v-btn> |
||||
</v-card-title> |
||||
|
||||
<div v-if="hooks" class="pa-4"> |
||||
<v-card v-for="hook in hooks" v-ripple class="elevation-0 backgroundColor"> |
||||
<div class="pa-4 "> |
||||
<h4 class="nc-text"> |
||||
{{ hook.title }} |
||||
</h4> |
||||
<div class="d-flex"> |
||||
<!--Title--> |
||||
<span class="caption textColor1--text">{{ $t("general.event") }} : {{ hook.event }} {{ hook.operation }}</span> |
||||
<v-spacer /> |
||||
<!--Notify Via--> |
||||
<span class="caption textColor1--text">{{ $t("labels.notifyVia") }} : {{ hook.notification && hook.notification.type }} |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</v-card> |
||||
</div> |
||||
|
||||
<!-- <v-simple-table dense> |
||||
<template #default> |
||||
<thead> |
||||
<tr> |
||||
<th> |
||||
<!–Title–> |
||||
{{ $t("general.title") }} |
||||
</th> |
||||
<th> |
||||
<!–Event–> |
||||
{{ $t("general.event") }} |
||||
</th> |
||||
<th> |
||||
<!–Condition–> |
||||
{{ $t("general.condition") }} |
||||
</th> |
||||
<th> |
||||
<!–Notify Via–> |
||||
{{ $t("labels.notifyVia") }} |
||||
</th> |
||||
<th> |
||||
<!–Action–> |
||||
{{ $t("labels.action") }} |
||||
</th> |
||||
</tr> |
||||
</thead> |
||||
|
||||
<tbody> |
||||
<template v-if="hooks && hooks.length"> |
||||
<tr v-for="(item, i) in hooks" :key="i"> |
||||
<td>{{ item.title }}</td> |
||||
<td>{{ item.event }} {{ item.operation }}</td> |
||||
<td> |
||||
<v-icon v-if="item.condition" color="success" small> |
||||
mdi-check-bold |
||||
</v-icon> |
||||
</td> |
||||
<td> |
||||
{{ item.notification && item.notification.type }} |
||||
</td> |
||||
<td> |
||||
<x-icon |
||||
small |
||||
color="error" |
||||
@click.stop="deleteHook(item, i)" |
||||
> |
||||
mdi-delete |
||||
</x-icon> |
||||
<!– <x-icon small :color="loading || !valid || !hook.event ? 'grey' : 'primary'" |
||||
@click.stop="(!loading && valid && hook.event) && saveHooks()">save |
||||
</x-icon>–> |
||||
</td> |
||||
</tr> |
||||
</template> |
||||
<tr> |
||||
<td colspan="6" class="text-center py-5"> |
||||
<!–:tooltip="$t('tooltip.saveChanges')"–> |
||||
<x-btn |
||||
v-ge="['hooks', 'add new']" |
||||
outlined |
||||
color="primary" |
||||
small |
||||
@click.prevent="$emit('add')" |
||||
> |
||||
<v-icon small left> |
||||
mdi-plus |
||||
</v-icon> |
||||
<!–Add New Webhook–> |
||||
{{ $t("activity.addWebhook") }} |
||||
</x-btn> |
||||
</td> |
||||
</tr> |
||||
</tbody> |
||||
</template> |
||||
</v-simple-table>--> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'WebhookList', |
||||
props: { meta: Object }, |
||||
data: () => ({ |
||||
hooks: null, loading: false |
||||
}), |
||||
mounted() { |
||||
this.loadHooksList() |
||||
}, |
||||
methods: { |
||||
async loadHooksList() { |
||||
this.key++ |
||||
this.loading = true |
||||
|
||||
const hooks = await this.$api.dbTableWebhook.list(this.meta.id) |
||||
|
||||
this.hooks = hooks.list.map((h) => { |
||||
h.notification = h.notification && JSON.parse(h.notification) |
||||
return h |
||||
}) |
||||
this.loading = false |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
|
||||
</style> |
@ -0,0 +1,44 @@
|
||||
<template> |
||||
<v-dialog v-model="webhookModal" width="min(700px,90%)" overlay-opacity=".9"> |
||||
<v-card |
||||
v-if="webhookModal" |
||||
width="100%" |
||||
min-height="350px" |
||||
class="pa-4" |
||||
> |
||||
<webhook-editor v-if="editOrAdd" :meta="meta" /> |
||||
<webhook-list v-else :meta="meta" @edit="editOrAdd=true" @add="editOrAdd=true" /> |
||||
</v-card> |
||||
</v-dialog> |
||||
</template> |
||||
|
||||
<script> |
||||
|
||||
import WebhookList from '~/components/project/tableTabs/webhook/webhookList' |
||||
import WebhookEditor from '~/components/project/tableTabs/webhook/webhookEditor' |
||||
export default { |
||||
name: 'WebhookModal', |
||||
components: { WebhookEditor, WebhookList }, |
||||
props: { |
||||
meta: Object, |
||||
value: Boolean |
||||
}, |
||||
data: () => ({ |
||||
editOrAdd: false, |
||||
activePage: 'role' |
||||
}), |
||||
computed: { |
||||
webhookModal: { |
||||
get() { |
||||
return this.value |
||||
}, |
||||
set(v) { |
||||
this.$emit('input', v) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped lang="scss"> |
||||
</style> |
Loading…
Reference in new issue