diff --git a/.env.example b/.env.example index b7ba0920d..a8fdab64a 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # https://www.chatwoot.com/docs/self-hosted/configuration/environment-variables/#rails-production-variables # Used to verify the integrity of signed cookies. so ensure a secure value is set -# SECRET_KEY_BASE should be alphanumeric. Avoid special characters or symbols. +# SECRET_KEY_BASE should be alphanumeric. Avoid special characters or symbols. # Use `rake secret` to generate this variable SECRET_KEY_BASE=replace_with_lengthy_secure_hex @@ -258,3 +258,7 @@ AZURE_APP_SECRET= # contact_inboxes with no conversation older than 90 days will be removed # REMOVE_STALE_CONTACT_INBOX_JOB_STATUS=false +# Baileys API Whatsapp provider +BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME=Chatwoot +BAILEYS_PROVIDER_DEFAULT_URL=http://localhost:3025 +BAILEYS_PROVIDER_DEFAULT_API_KEY= diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index 011faaf28..2d390bba9 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -62,6 +62,28 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController head :ok end + def setup_channel_provider + channel = @inbox.channel + + unless channel.respond_to?(:setup_channel_provider) + render json: { error: 'Channel does not support setup' }, status: :unprocessable_entity and return + end + + channel.setup_channel_provider + head :ok + end + + def disconnect_channel_provider + channel = @inbox.channel + + unless channel.respond_to?(:disconnect_channel_provider) + render json: { error: 'Channel does not support disconnect' }, status: :unprocessable_entity and return + end + + channel.disconnect_channel_provider + head :ok + end + def destroy ::DeleteObjectJob.perform_later(@inbox, Current.user, request.ip) if @inbox.present? render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') } diff --git a/app/helpers/cache_keys_helper.rb b/app/helpers/cache_keys_helper.rb index aab33e44c..366c87383 100644 --- a/app/helpers/cache_keys_helper.rb +++ b/app/helpers/cache_keys_helper.rb @@ -10,6 +10,6 @@ module CacheKeysHelper return value_from_cache if value_from_cache.present? # zero epoch time: 1970-01-01 00:00:00 UTC - '0000000000' + '0000000000000' end end diff --git a/app/javascript/dashboard/api/inboxes.js b/app/javascript/dashboard/api/inboxes.js index 8c09791c8..7af82a720 100644 --- a/app/javascript/dashboard/api/inboxes.js +++ b/app/javascript/dashboard/api/inboxes.js @@ -28,6 +28,14 @@ class Inboxes extends CacheEnabledApiClient { agent_bot: botId, }); } + + setupChannelProvider(inboxId) { + return axios.post(`${this.url}/${inboxId}/setup_channel_provider`); + } + + disconnectChannelProvider(inboxId) { + return axios.post(`${this.url}/${inboxId}/disconnect_channel_provider`); + } } export default new Inboxes(); diff --git a/app/javascript/dashboard/components/widgets/InboxName.vue b/app/javascript/dashboard/components/widgets/InboxName.vue index 693162f95..a5840caaa 100644 --- a/app/javascript/dashboard/components/widgets/InboxName.vue +++ b/app/javascript/dashboard/components/widgets/InboxName.vue @@ -7,6 +7,14 @@ export default { type: Object, default: () => {}, }, + withPhoneNumber: { + type: Boolean, + default: false, + }, + withProviderConnectionStatus: { + type: Boolean, + default: false, + }, }, computed: { computedInboxClass() { @@ -14,6 +22,9 @@ export default { const classByType = getInboxClassByType(type, phoneNumber); return classByType; }, + providerConnection() { + return this.inbox.provider_connection?.connection; + }, }, }; @@ -28,5 +39,17 @@ export default { size="12" /> {{ inbox.name }} + {{ + inbox.phone_number + }} + + + diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index ae798d8d9..e1ee9ce2d 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -5,6 +5,7 @@ import { useConfig } from 'dashboard/composables/useConfig'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useAI } from 'dashboard/composables/useAI'; import { useMapGetter } from 'dashboard/composables/store'; +import { useAdmin } from 'dashboard/composables/useAdmin'; // components import ReplyBox from './ReplyBox.vue'; @@ -36,6 +37,7 @@ import { REPLY_POLICY } from 'shared/constants/links'; import wootConstants from 'dashboard/constants/globals'; import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage'; import { FEATURE_FLAGS } from '../../../featureFlags'; +import WhatsappBaileysLinkDeviceModal from '../../../routes/dashboard/settings/inbox/components/WhatsappBaileysLinkDeviceModal.vue'; import NextButton from 'dashboard/components-next/button/Button.vue'; @@ -47,6 +49,7 @@ export default { Banner, ConversationLabelSuggestion, NextButton, + WhatsappBaileysLinkDeviceModal, }, mixins: [inboxMixin], props: { @@ -61,6 +64,7 @@ export default { }, emits: ['contactPanelToggle'], setup() { + const { isAdmin } = useAdmin(); const isPopOutReplyBox = ref(false); const { isEnterprise } = useConfig(); @@ -107,6 +111,7 @@ export default { fetchIntegrationsIfRequired, fetchLabelSuggestions, showNextBubbles, + isAdmin, }; }, data() { @@ -118,6 +123,7 @@ export default { isProgrammaticScroll: false, messageSentSinceOpened: false, labelSuggestions: [], + showBaileysLinkDeviceModal: false, }; }, @@ -128,6 +134,9 @@ export default { listLoadingStatus: 'getAllMessagesLoaded', currentAccountId: 'getCurrentAccountId', }), + currentInbox() { + return this.$store.getters['inboxes/getInbox'](this.currentChat.inbox_id); + }, isOpen() { return this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN; }, @@ -266,6 +275,9 @@ export default { return { incoming, outgoing }; }, + inboxProviderConnection() { + return this.currentInbox.provider_connection?.connection; + }, }, watch: { @@ -476,12 +488,48 @@ export default { return false; }); }, + onOpenBaileysLinkDeviceModal() { + this.showBaileysLinkDeviceModal = true; + }, + onCloseBaileysLinkDeviceModal() { + this.showBaileysLinkDeviceModal = false; + }, }, }; diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index 845206d1c..974dd4b6d 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -88,6 +88,9 @@ export default { if (this.isATwilioWhatsAppChannel) { return this.$t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.TWILIO'); } + if (this.isAWhatsAppBaileysChannel) { + return this.$t('INBOX_MGMT.ADD.WHATSAPP.PROVIDERS.BAILEYS'); + } return ''; }, tabs() { diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/BaileysWhatsapp.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/BaileysWhatsapp.vue new file mode 100644 index 000000000..dbfa2d739 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/BaileysWhatsapp.vue @@ -0,0 +1,171 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue index 68a9d8bcd..f07d266af 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue @@ -3,6 +3,7 @@ import PageHeader from '../../SettingsSubPageHeader.vue'; import Twilio from './Twilio.vue'; import ThreeSixtyDialogWhatsapp from './360DialogWhatsapp.vue'; import CloudWhatsapp from './CloudWhatsapp.vue'; +import BaileysWhatsapp from './BaileysWhatsapp.vue'; export default { components: { @@ -10,6 +11,7 @@ export default { Twilio, ThreeSixtyDialogWhatsapp, CloudWhatsapp, + BaileysWhatsapp, }, data() { return { @@ -37,12 +39,16 @@ export default { + - + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WhatsappBaileysLinkDeviceModal.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WhatsappBaileysLinkDeviceModal.vue new file mode 100644 index 000000000..bf12a8982 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/components/WhatsappBaileysLinkDeviceModal.vue @@ -0,0 +1,165 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue index bc5355c3c..792a15299 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue @@ -5,8 +5,11 @@ import SettingsSection from '../../../../../components/SettingsSection.vue'; import ImapSettings from '../ImapSettings.vue'; import SmtpSettings from '../SmtpSettings.vue'; import { useVuelidate } from '@vuelidate/core'; -import { required } from '@vuelidate/validators'; import NextButton from 'dashboard/components-next/button/Button.vue'; +import { requiredIf } from '@vuelidate/validators'; +import { isValidURL } from '../../../../../helper/URLHelper'; +import WhatsappBaileysLinkDeviceModal from '../components/WhatsappBaileysLinkDeviceModal.vue'; +import InboxName from '../../../../../components/widgets/InboxName.vue'; export default { components: { @@ -14,6 +17,8 @@ export default { ImapSettings, SmtpSettings, NextButton, + WhatsappBaileysLinkDeviceModal, + InboxName, }, mixins: [inboxMixin], props: { @@ -29,10 +34,17 @@ export default { return { hmacMandatory: false, whatsAppInboxAPIKey: '', + whatsAppProviderUrl: '', + showBaileysLinkDeviceModal: false, }; }, - validations: { - whatsAppInboxAPIKey: { required }, + validations() { + return { + whatsAppInboxAPIKey: { + requiredIf: requiredIf(!this.isAWhatsAppBaileysChannel), + }, + whatsAppProviderUrl: { isValidURL: value => !value || isValidURL(value) }, + }; }, watch: { inbox() { @@ -83,6 +95,31 @@ export default { useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE')); } }, + async updateWhatsAppProviderUrl() { + try { + const payload = { + id: this.inbox.id, + formData: false, + channel: { + provider_config: { + ...this.inbox.provider_config, + provider_url: this.whatsAppProviderUrl, + }, + }, + }; + + await this.$store.dispatch('inboxes/updateInbox', payload); + useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE')); + } catch (error) { + useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE')); + } + }, + onOpenBaileysLinkDeviceModal() { + this.showBaileysLinkDeviceModal = true; + }, + onCloseBaileysLinkDeviceModal() { + this.showBaileysLinkDeviceModal = false; + }, }, }; @@ -194,8 +231,8 @@ export default { -
-
+
+
+
+ +
+ +
+ + + {{ + $t( + 'INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_MANAGE_PROVIDER_CONNECTION_BUTTON' + ) + }} + +
+
+ +
+ + + {{ $t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_SECTION_UPDATE_BUTTON') }} + +
+ + {{ $t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_PROVIDER_URL_ERROR') }} + +
+ + +
+ + + {{ $t('INBOX_MGMT.SETTINGS_POPUP.WHATSAPP_SECTION_UPDATE_BUTTON') }} + +
+
+
+