feat: Update the design for integration page (#9825)
Combine integrations and applications page into one page. <img width="1182" alt="Screenshot 2024-07-23 at 3 30 51 PM" src="https://github.com/user-attachments/assets/50920a6f-606f-44b3-b1e4-641046a14444"> Major changes: - The app enabled?, active? checks are all moved to backend. - The dashboard_apps integration is also now part of the apps.yml file. - Updated the header design for the new settings pages. - Merged the folders integrationapps and integrations. - Updated the copy to match the size of the card and provide clear instruction. - Only the list page is updated in this PR, rest of the pages are yet to be migrated. | Integration | Verified | | -- | -- | | Dashboard Apps | ✅ | | Dyte | ✅ | | Slack | ✅ | | Webhooks | ✅ | | Dialogflow | ✅ | | Google Translate | ✅ | | OpenAI | ✅ | | Linear | ✅ | --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
@import 'shared/assets/fonts/plus-jakarta';
|
||||
@import 'shared/assets/fonts/InterDisplay/inter-display';
|
||||
@import 'shared/assets/fonts/inter';
|
||||
|
||||
@import 'shared/assets/stylesheets/animations';
|
||||
@import 'shared/assets/stylesheets/colors';
|
||||
@import 'shared/assets/stylesheets/spacing';
|
||||
@@ -42,6 +45,7 @@
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
// scss-lint:disable PropertySortOrder
|
||||
:root {
|
||||
--color-amber-25: 254 253 251;
|
||||
@@ -213,6 +217,7 @@
|
||||
--color-orange-800: 204 78 0;
|
||||
--color-orange-900: 88 45 29;
|
||||
}
|
||||
|
||||
// scss-lint:disable QualifyingElement
|
||||
body.dark {
|
||||
--color-amber-25: 31 19 0;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup>
|
||||
import { useStoreGetters } from 'dashboard/composables/store';
|
||||
import { computed } from 'vue';
|
||||
const props = defineProps({
|
||||
showOnCustomBrandedInstance: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const getters = useStoreGetters();
|
||||
|
||||
const isACustomBrandedInstance =
|
||||
getters['globalConfig/isACustomBrandedInstance'];
|
||||
|
||||
const shouldShowContent = computed(
|
||||
() => props.showOnCustomBrandedInstance || !isACustomBrandedInstance.value
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="shouldShowContent">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -163,17 +163,6 @@ const settings = accountId => ({
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
toState: frontendURL(`accounts/${accountId}/settings/integrations`),
|
||||
toStateName: 'settings_integrations',
|
||||
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
|
||||
},
|
||||
{
|
||||
icon: 'star-emphasis',
|
||||
label: 'APPLICATIONS',
|
||||
hasSubMenu: false,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
toState: frontendURL(`accounts/${accountId}/settings/applications`),
|
||||
toStateName: 'settings_applications',
|
||||
featureFlag: FEATURE_FLAGS.INTEGRATIONS,
|
||||
},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"INTEGRATION_APPS": {
|
||||
"FETCHING": "Fetching Integrations",
|
||||
"NO_HOOK_CONFIGURED": "There are no %{integrationId} integrations configured in this account.",
|
||||
"HEADER": "Applications",
|
||||
"STATUS": {
|
||||
"ENABLED": "Enabled",
|
||||
"DISABLED": "Disabled"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"INTEGRATION_SETTINGS": {
|
||||
"HEADER": "Integrations",
|
||||
"LOADING": "Fetching integrations",
|
||||
"WEBHOOK": {
|
||||
"SUBSCRIBED_EVENTS": "Subscribed Events",
|
||||
"FORM": {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"INTEGRATION_SETTINGS": {
|
||||
"HEADER": "Integrations",
|
||||
"DESCRIPTION": "Chatwoot integrates with multiple tools and services to improve your team's efficiency. Explore the list below to configure your favorite apps.",
|
||||
"LEARN_MORE": "Learn more about integrations",
|
||||
"LOADING": "Fetching integrations",
|
||||
"WEBHOOK": {
|
||||
"SUBSCRIBED_EVENTS": "Subscribed Events",
|
||||
"FORM": {
|
||||
@@ -37,7 +40,10 @@
|
||||
"LIST": {
|
||||
"404": "There are no webhooks configured for this account.",
|
||||
"TITLE": "Manage webhooks",
|
||||
"TABLE_HEADER": ["Webhook endpoint", "Actions"]
|
||||
"TABLE_HEADER": [
|
||||
"Webhook endpoint",
|
||||
"Actions"
|
||||
]
|
||||
},
|
||||
"EDIT": {
|
||||
"BUTTON_TEXT": "Edit",
|
||||
@@ -169,7 +175,10 @@
|
||||
"LIST": {
|
||||
"404": "There are no dashboard apps configured on this account yet",
|
||||
"LOADING": "Fetching dashboard apps...",
|
||||
"TABLE_HEADER": ["Name", "Endpoint"],
|
||||
"TABLE_HEADER": [
|
||||
"Name",
|
||||
"Endpoint"
|
||||
],
|
||||
"EDIT_TOOLTIP": "Edit app",
|
||||
"DELETE_TOOLTIP": "Delete app"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"SLA": {
|
||||
"HEADER": "SLA",
|
||||
"HEADER": "Service Level Agreements",
|
||||
"ADD_ACTION": "Add SLA",
|
||||
"ADD_ACTION_LONG": "Create a new SLA Policy",
|
||||
"DESCRIPTION": "Service Level Agreements (SLAs) are contracts that define clear expectations between your team and customers. They establish standards for response and resolution times, creating a framework for accountability and ensures a consistent, high-quality experience.",
|
||||
@@ -105,4 +105,4 @@
|
||||
"HIDE": "Hide {count} rows"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col w-full h-full px-5 pt-8 pb-3 m-0 overflow-auto bg-white sm:px-16 sm:pt-16 dark:bg-slate-900"
|
||||
class="flex flex-col w-full h-full m-0 px-8 lg:px-16 py-8 overflow-auto bg-white dark:bg-slate-900"
|
||||
>
|
||||
<div class="flex items-start max-w-[900px] w-full">
|
||||
<div class="flex items-start w-full max-w-6xl mx-auto">
|
||||
<keep-alive v-if="keepAlive">
|
||||
<router-view />
|
||||
</keep-alive>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
import CustomBrandPolicyWrapper from 'dashboard/components/CustomBrandPolicyWrapper.vue';
|
||||
import { getHelpUrlForFeature } from '../../../../helper/featureHelper';
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
@@ -9,10 +11,6 @@ defineProps({
|
||||
required: true,
|
||||
},
|
||||
iconName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
@@ -20,8 +18,14 @@ defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
featureName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const helpURL = getHelpUrlForFeature(props.featureName);
|
||||
|
||||
const openInNewTab = url => {
|
||||
if (!url) return;
|
||||
window.open(url, '_blank', 'noopener noreferrer');
|
||||
@@ -29,12 +33,11 @@ const openInNewTab = url => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col items-start w-full gap-4">
|
||||
<!-- Header section with icon, title and action button -->
|
||||
<div class="flex flex-col items-start w-full gap-3 pt-4">
|
||||
<div class="flex items-center justify-between w-full gap-4">
|
||||
<!-- Icon and title container -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
v-if="iconName"
|
||||
class="flex items-center w-10 h-10 p-1 rounded-full bg-woot-25/60 dark:bg-woot-900/60"
|
||||
>
|
||||
<div
|
||||
@@ -49,7 +52,7 @@ const openInNewTab = url => {
|
||||
</div>
|
||||
</div>
|
||||
<h1
|
||||
class="text-2xl font-medium tracking-[-1.5%] text-slate-900 dark:text-slate-25"
|
||||
class="text-2xl font-semibold font-interDisplay tracking-[0.3px] text-slate-900 dark:text-slate-25"
|
||||
>
|
||||
{{ title }}
|
||||
</h1>
|
||||
@@ -59,44 +62,43 @@ const openInNewTab = url => {
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Description and optional link -->
|
||||
<div
|
||||
class="flex flex-col gap-2 text-slate-600 dark:text-slate-300 max-w-[721px] w-full"
|
||||
>
|
||||
<div class="flex flex-col gap-3 text-slate-600 dark:text-slate-300 w-full">
|
||||
<p
|
||||
class="mb-0 text-sm font-normal tracking-[0.5%] line-clamp-5 sm:line-clamp-none"
|
||||
class="mb-0 text-base font-normal line-clamp-5 sm:line-clamp-none max-w-3xl"
|
||||
>
|
||||
<slot name="description">{{ description }}</slot>
|
||||
</p>
|
||||
<!-- Conditional link -->
|
||||
<a
|
||||
v-if="href && linkText"
|
||||
:href="href"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="sm:inline-flex hidden tracking-[-0.6%] gap-1 w-fit items-center text-woot-500 dark:text-woot-500 text-sm font-medium tracking=[-0.6%] hover:underline"
|
||||
>
|
||||
{{ linkText }}
|
||||
<fluent-icon
|
||||
size="16"
|
||||
icon="chevron-right"
|
||||
type="outline"
|
||||
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
|
||||
/>
|
||||
</a>
|
||||
<CustomBrandPolicyWrapper :show-on-custom-branded-instance="false">
|
||||
<a
|
||||
v-if="helpURL && linkText"
|
||||
:href="helpURL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="sm:inline-flex hidden tracking-[-0.6%] gap-1 w-fit items-center text-woot-500 dark:text-woot-500 text-sm font-medium tracking=[-0.6%] hover:underline"
|
||||
>
|
||||
{{ linkText }}
|
||||
<fluent-icon
|
||||
size="16"
|
||||
icon="chevron-right"
|
||||
type="outline"
|
||||
class="flex-shrink-0 text-woot-500 dark:text-woot-500"
|
||||
/>
|
||||
</a>
|
||||
</CustomBrandPolicyWrapper>
|
||||
</div>
|
||||
<!-- Mobile view for actions and link -->
|
||||
<div class="flex items-start justify-start w-full gap-3 sm:hidden">
|
||||
<slot name="actions" />
|
||||
<woot-button
|
||||
v-if="href && linkText"
|
||||
color-scheme="secondary"
|
||||
icon="arrow-outwards"
|
||||
class="flex-row-reverse rounded-xl min-w-0 !bg-slate-50 !text-slate-900 dark:!text-white dark:!bg-slate-800"
|
||||
@click="openInNewTab(href)"
|
||||
>
|
||||
{{ linkText }}
|
||||
</woot-button>
|
||||
<CustomBrandPolicyWrapper :show-on-custom-branded-instance="false">
|
||||
<woot-button
|
||||
v-if="helpURL && linkText"
|
||||
color-scheme="secondary"
|
||||
icon="arrow-outwards"
|
||||
class="flex-row-reverse rounded-xl min-w-0 !bg-slate-50 !text-slate-900 dark:!text-white dark:!bg-slate-800"
|
||||
@click="openInNewTab(helpURL)"
|
||||
>
|
||||
{{ linkText }}
|
||||
</woot-button>
|
||||
</CustomBrandPolicyWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -12,16 +12,15 @@ defineProps({
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="flex relative flex-col sm:flex-row p-4 gap-4 sm:p-6 justify-between shadow-sm group bg-white border border-solid rounded-xl dark:bg-slate-800 border-slate-75 dark:border-slate-700/50 max-w-[900px] w-full"
|
||||
class="flex relative flex-col sm:flex-row p-4 gap-4 sm:p-6 justify-between shadow-sm group bg-white border border-solid rounded-xl dark:bg-slate-800 border-slate-75 dark:border-slate-700/50 w-full"
|
||||
>
|
||||
<!-- left side section -->
|
||||
<slot name="leftSection">
|
||||
<div class="flex flex-col min-w-0 items-start gap-3 max-w-[480px] w-full">
|
||||
<div
|
||||
class="flex items-center justify-between w-full gap-3 sm:justify-normal whitespace-nowrap"
|
||||
>
|
||||
<h3
|
||||
class="justify-between text-sm tracking-[-0.6%] font-medium truncate w-fit sm:justify-normal text-slate-900 dark:text-slate-25"
|
||||
class="justify-between text-sm font-medium truncate w-fit sm:justify-normal text-slate-900 dark:text-slate-25"
|
||||
>
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
@@ -30,7 +29,7 @@ defineProps({
|
||||
<slot name="label" />
|
||||
</div>
|
||||
<p
|
||||
class="text-sm text-slate-600 tracking-[0.5%] dark:text-slate-300 max-w-[400px] w-full line-clamp-2"
|
||||
class="text-base text-slate-600 dark:text-slate-300 max-w-[400px] w-full line-clamp-2"
|
||||
>
|
||||
<slot name="description">
|
||||
{{ description }}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<template>
|
||||
<div class="flex-grow flex-shrink p-4 overflow-auto">
|
||||
<div class="flex flex-col">
|
||||
<div v-if="uiFlags.isFetching" class="mx-auto my-0">
|
||||
<woot-loading-state :message="$t('INTEGRATION_APPS.FETCHING')" />
|
||||
</div>
|
||||
|
||||
<div v-else class="w-full">
|
||||
<div>
|
||||
<div
|
||||
v-for="item in enabledIntegrations"
|
||||
:key="item.id"
|
||||
class="p-4 mb-4 bg-white border border-solid rounded-sm dark:bg-slate-800 border-slate-75 dark:border-slate-700/50"
|
||||
>
|
||||
<integration-item
|
||||
:integration-id="item.id"
|
||||
:integration-logo="item.logo"
|
||||
:integration-name="item.name"
|
||||
:integration-description="item.description"
|
||||
:integration-enabled="item.hooks.length"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import IntegrationItem from './IntegrationItem.vue';
|
||||
const store = useStore();
|
||||
const getters = useStoreGetters();
|
||||
|
||||
const uiFlags = getters['integrations/getUIFlags'];
|
||||
|
||||
const accountId = getters.getCurrentAccountId;
|
||||
|
||||
const integrationList = computed(() => {
|
||||
return getters['integrations/getAppIntegrations'].value;
|
||||
});
|
||||
|
||||
const isLinearIntegrationEnabled = computed(() => {
|
||||
return getters['accounts/isFeatureEnabledonAccount'].value(
|
||||
accountId.value,
|
||||
'linear_integration'
|
||||
);
|
||||
});
|
||||
const enabledIntegrations = computed(() => {
|
||||
if (!isLinearIntegrationEnabled.value) {
|
||||
return integrationList.value.filter(
|
||||
integration => integration.id !== 'linear'
|
||||
);
|
||||
}
|
||||
return integrationList.value;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('integrations/get');
|
||||
});
|
||||
</script>
|
||||
@@ -1,95 +0,0 @@
|
||||
<template>
|
||||
<div class="flex">
|
||||
<div class="flex h-[6.25rem] w-[6.25rem]">
|
||||
<img
|
||||
:src="`/dashboard/images/integrations/${integrationId}.png`"
|
||||
class="max-w-full p-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center m-0 mx-4 flex-1">
|
||||
<h3 class="text-xl font-medium mb-1 text-slate-800 dark:text-slate-100">
|
||||
{{ integrationName }}
|
||||
</h3>
|
||||
<p class="text-slate-700 dark:text-slate-200">
|
||||
{{
|
||||
useInstallationName(
|
||||
integrationDescription,
|
||||
globalConfig.installationName
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex justify-center items-center mb-0 w-[15%]">
|
||||
<woot-label
|
||||
:title="labelText"
|
||||
:color-scheme="labelColor"
|
||||
class="text-xs rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center items-center mb-0 w-[15%]">
|
||||
<router-link
|
||||
:to="
|
||||
frontendURL(
|
||||
`accounts/${accountId}/settings/applications/` + integrationId
|
||||
)
|
||||
"
|
||||
>
|
||||
<woot-button icon="settings">
|
||||
{{ $t('INTEGRATION_APPS.CONFIGURE') }}
|
||||
</woot-button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
import WootLabel from 'dashboard/components/ui/Label.vue';
|
||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootLabel,
|
||||
},
|
||||
mixins: [globalConfigMixin],
|
||||
props: {
|
||||
integrationId: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
integrationLogo: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
integrationName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
integrationDescription: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
integrationEnabled: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
globalConfig: 'globalConfig/get',
|
||||
}),
|
||||
labelText() {
|
||||
return this.integrationEnabled
|
||||
? this.$t('INTEGRATION_APPS.STATUS.ENABLED')
|
||||
: this.$t('INTEGRATION_APPS.STATUS.DISABLED');
|
||||
},
|
||||
labelColor() {
|
||||
return this.integrationEnabled ? 'success' : 'secondary';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
frontendURL,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,47 +0,0 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const IntegrationHooks = () => import('./IntegrationHooks.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/settings/applications'),
|
||||
component: SettingsContent,
|
||||
props: params => {
|
||||
const showBackButton = params.name !== 'settings_applications';
|
||||
const backUrl =
|
||||
params.name === 'settings_applications_integration'
|
||||
? { name: 'settings_applications' }
|
||||
: '';
|
||||
return {
|
||||
headerTitle: 'INTEGRATION_APPS.HEADER',
|
||||
icon: 'star-emphasis',
|
||||
showBackButton,
|
||||
backUrl,
|
||||
};
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'settings_applications',
|
||||
component: Index,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':integration_id',
|
||||
name: 'settings_applications_integration',
|
||||
component: IntegrationHooks,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
props: route => ({
|
||||
integrationId: route.params.integration_id,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,57 +1,51 @@
|
||||
<script setup>
|
||||
import { useStoreGetters, useStore } from 'dashboard/composables/store';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import IntegrationItem from './IntegrationItem.vue';
|
||||
import SettingsLayout from '../SettingsLayout.vue';
|
||||
import BaseSettingsHeader from '../components/BaseSettingsHeader.vue';
|
||||
|
||||
const store = useStore();
|
||||
const getters = useStoreGetters();
|
||||
|
||||
const uiFlags = getters['integrations/getUIFlags'];
|
||||
|
||||
const integrationList = computed(
|
||||
() => getters['integrations/getAppIntegrations'].value
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('integrations/get');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-shrink flex-grow overflow-auto p-4">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col">
|
||||
<div>
|
||||
<div
|
||||
v-for="item in integrationsList"
|
||||
<SettingsLayout
|
||||
:is-loading="uiFlags.isFetching"
|
||||
:loading-message="$t('INTEGRATION_SETTINGS.LOADING')"
|
||||
>
|
||||
<template #header>
|
||||
<BaseSettingsHeader
|
||||
:title="$t('INTEGRATION_SETTINGS.HEADER')"
|
||||
:description="$t('INTEGRATION_SETTINGS.DESCRIPTION')"
|
||||
:link-text="$t('INTEGRATION_SETTINGS.LEARN_MORE')"
|
||||
feature-name="integrations"
|
||||
/>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="flex-grow flex-shrink overflow-auto font-inter">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||
<integration-item
|
||||
v-for="item in integrationList"
|
||||
:id="item.id"
|
||||
:key="item.id"
|
||||
class="bg-white dark:bg-slate-800 border border-solid border-slate-75 dark:border-slate-700/50 rounded-sm mb-4 p-4"
|
||||
>
|
||||
<integration
|
||||
:integration-id="item.id"
|
||||
:integration-logo="item.logo"
|
||||
:integration-name="item.name"
|
||||
:integration-description="item.description"
|
||||
:integration-enabled="item.enabled"
|
||||
:integration-action="item.action"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800 border border-solid border-slate-75 dark:border-slate-700/50 rounded-sm mb-4 p-4"
|
||||
>
|
||||
<integration
|
||||
integration-id="dashboard_apps"
|
||||
:integration-name="
|
||||
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.TITLE')
|
||||
"
|
||||
:integration-description="
|
||||
$t('INTEGRATION_SETTINGS.DASHBOARD_APPS.DESCRIPTION')
|
||||
"
|
||||
integration-enabled
|
||||
integration-action="/dashboard-apps"
|
||||
/>
|
||||
</div>
|
||||
:logo="item.logo"
|
||||
:name="item.name"
|
||||
:description="item.description"
|
||||
:enabled="item.enabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</SettingsLayout>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Integration from './Integration.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Integration,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
integrationsList: 'integrations/getIntegrations',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('integrations/get');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<!-- to be removed non using component -->
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useStoreGetters } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
import { useInstallationName } from 'shared/mixins/globalConfigMixin';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const getters = useStoreGetters();
|
||||
const accountId = getters.getCurrentAccountId;
|
||||
const globalConfig = getters['globalConfig/get'];
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const integrationStatus = computed(() =>
|
||||
props.enabled
|
||||
? t('INTEGRATION_APPS.STATUS.ENABLED')
|
||||
: t('INTEGRATION_APPS.STATUS.DISABLED')
|
||||
);
|
||||
|
||||
const integrationStatusColor = computed(() =>
|
||||
props.enabled ? 'bg-green-500' : 'bg-slate-200'
|
||||
);
|
||||
|
||||
const actionURL = computed(() =>
|
||||
frontendURL(`accounts/${accountId.value}/settings/integrations/${props.id}`)
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-1 p-6 bg-white border border-solid rounded-md dark:bg-slate-800 border-slate-50 dark:border-slate-700/50"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex h-12 w-12 mb-4">
|
||||
<img
|
||||
:src="`/dashboard/images/integrations/${id}.png`"
|
||||
class="max-w-full rounded-md border border-slate-50 dark:border-slate-700/50 shadow-sm block dark:hidden bg-white dark:bg-slate-900"
|
||||
/>
|
||||
<img
|
||||
:src="`/dashboard/images/integrations/${id}-dark.png`"
|
||||
class="max-w-full rounded-md border border-slate-50 dark:border-slate-700/50 shadow-sm hidden dark:block bg-white dark:bg-slate-900"
|
||||
/>
|
||||
</div>
|
||||
<fluent-icon
|
||||
v-tooltip="integrationStatus"
|
||||
size="20"
|
||||
class="text-white p-0.5 rounded-full"
|
||||
:class="integrationStatusColor"
|
||||
icon="checkmark"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col m-0 flex-1">
|
||||
<div
|
||||
class="font-medium mb-2 text-slate-800 dark:text-slate-100 flex justify-between items-center"
|
||||
>
|
||||
<span class="text-base font-semibold">{{ name }}</span>
|
||||
<router-link :to="actionURL">
|
||||
<woot-button class="clear link">
|
||||
{{ $t('INTEGRATION_APPS.CONFIGURE') }}
|
||||
</woot-button>
|
||||
</router-link>
|
||||
</div>
|
||||
<p class="text-slate-700 dark:text-slate-200">
|
||||
{{ useInstallationName(description, globalConfig.installationName) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,14 +1,30 @@
|
||||
import { frontendURL } from '../../../../helper/URLHelper';
|
||||
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
const SettingsWrapper = () => import('../SettingsWrapper.vue');
|
||||
const IntegrationHooks = () => import('./IntegrationHooks.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
const Webhook = () => import('./Webhooks/Index.vue');
|
||||
const DashboardApps = () => import('./DashboardApps/Index.vue');
|
||||
const ShowIntegration = () => import('./ShowIntegration.vue');
|
||||
const Slack = () => import('./Slack.vue');
|
||||
const Index = () => import('./Index.vue');
|
||||
const SettingsContent = () => import('../Wrapper.vue');
|
||||
|
||||
export default {
|
||||
routes: [
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/settings/integrations'),
|
||||
component: SettingsWrapper,
|
||||
props: {},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'settings_applications',
|
||||
component: Index,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: frontendURL('accounts/:accountId/settings/integrations'),
|
||||
component: SettingsContent,
|
||||
@@ -26,14 +42,6 @@ export default {
|
||||
};
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'settings_integrations',
|
||||
component: Index,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'webhook',
|
||||
component: Webhook,
|
||||
@@ -61,17 +69,14 @@ export default {
|
||||
},
|
||||
{
|
||||
path: ':integration_id',
|
||||
name: 'settings_integrations_integration',
|
||||
component: ShowIntegration,
|
||||
name: 'settings_applications_integration',
|
||||
component: IntegrationHooks,
|
||||
meta: {
|
||||
permissions: ['administrator'],
|
||||
},
|
||||
props: route => {
|
||||
return {
|
||||
integrationId: route.params.integration_id,
|
||||
code: route.query.code,
|
||||
};
|
||||
},
|
||||
props: route => ({
|
||||
integrationId: route.params.integration_id,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -9,7 +9,6 @@ import billing from './billing/billing.routes';
|
||||
import campaigns from './campaigns/campaigns.routes';
|
||||
import canned from './canned/canned.routes';
|
||||
import inbox from './inbox/inbox.routes';
|
||||
import integrationapps from './integrationapps/integrations.routes';
|
||||
import integrations from './integrations/integrations.routes';
|
||||
import labels from './labels/labels.routes';
|
||||
import macros from './macros/macros.routes';
|
||||
@@ -44,7 +43,6 @@ export default {
|
||||
...campaigns.routes,
|
||||
...canned.routes,
|
||||
...inbox.routes,
|
||||
...integrationapps.routes,
|
||||
...integrations.routes,
|
||||
...labels.routes,
|
||||
...macros.routes,
|
||||
|
||||
@@ -7,14 +7,15 @@ defineProps({
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
defineEmits(['click']);
|
||||
</script>
|
||||
<template>
|
||||
<base-settings-header
|
||||
<BaseSettingsHeader
|
||||
:title="$t('SLA.HEADER')"
|
||||
:description="$t('SLA.DESCRIPTION')"
|
||||
:link-text="$t('SLA.LEARN_MORE')"
|
||||
href="/"
|
||||
icon-name="document-list-clock"
|
||||
feature-name="sla"
|
||||
>
|
||||
<template v-if="showActions" #actions>
|
||||
<woot-button
|
||||
@@ -26,5 +27,5 @@ defineProps({
|
||||
{{ $t('SLA.ADD_ACTION') }}
|
||||
</woot-button>
|
||||
</template>
|
||||
</base-settings-header>
|
||||
</BaseSettingsHeader>
|
||||
</template>
|
||||
|
||||
@@ -20,21 +20,9 @@ const state = {
|
||||
},
|
||||
};
|
||||
|
||||
const isAValidAppIntegration = integration => {
|
||||
return [
|
||||
'dialogflow',
|
||||
'dyte',
|
||||
'google_translate',
|
||||
'openai',
|
||||
'linear',
|
||||
].includes(integration.id);
|
||||
};
|
||||
export const getters = {
|
||||
getIntegrations($state) {
|
||||
return $state.records.filter(item => !isAValidAppIntegration(item));
|
||||
},
|
||||
getAppIntegrations($state) {
|
||||
return $state.records.filter(item => isAValidAppIntegration(item));
|
||||
return $state.records;
|
||||
},
|
||||
getIntegration: $state => integrationId => {
|
||||
const [integration] = $state.records.filter(
|
||||
|
||||
@@ -1,60 +1,9 @@
|
||||
import { getters } from '../../integrations';
|
||||
|
||||
describe('#getters', () => {
|
||||
it('getIntegrations', () => {
|
||||
const state = {
|
||||
records: [
|
||||
{
|
||||
id: 'test1',
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
name: 'test2',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'dyte',
|
||||
name: 'dyte',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'dialogflow',
|
||||
name: 'dialogflow',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(getters.getIntegrations(state)).toEqual([
|
||||
{
|
||||
id: 'test1',
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
name: 'test2',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('getAppIntegrations', () => {
|
||||
const state = {
|
||||
records: [
|
||||
{
|
||||
id: 'test1',
|
||||
name: 'test1',
|
||||
logo: 'test',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'dyte',
|
||||
name: 'dyte',
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
export const useInstallationName = (str, installationName) => {
|
||||
if (str && installationName) {
|
||||
return str.replace(/Chatwoot/g, installationName);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
// eslint-disable-next-line default-param-last
|
||||
useInstallationName(str = '', installationName) {
|
||||
return str.replace(/Chatwoot/g, installationName);
|
||||
},
|
||||
useInstallationName,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -49,6 +49,8 @@ class Integrations::App
|
||||
case params[:id]
|
||||
when 'webhook'
|
||||
account.webhooks.exists?
|
||||
when 'dashboard_apps'
|
||||
account.dashboard_apps.exists?
|
||||
else
|
||||
account.hooks.exists?(app_id: id)
|
||||
end
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
help_url: https://chwt.app/hc/canned
|
||||
- name: integrations
|
||||
enabled: true
|
||||
help_url: https://chwt.app/hc/integrations
|
||||
- name: voice_recorder
|
||||
enabled: true
|
||||
- name: mobile_v2
|
||||
@@ -80,6 +81,7 @@
|
||||
- name: sla
|
||||
enabled: false
|
||||
premium: true
|
||||
help_url: https://chwt.app/hc/sla
|
||||
- name: help_center_embedding_search
|
||||
enabled: false
|
||||
premium: true
|
||||
|
||||
@@ -16,6 +16,12 @@ webhooks:
|
||||
action: /webhook
|
||||
hook_type: account
|
||||
allow_multiple_hooks: true
|
||||
dashboard_apps:
|
||||
id: dashboard_apps
|
||||
logo: dashboard_apps.png
|
||||
i18n_key: dashboard_apps
|
||||
hook_type: account
|
||||
allow_multiple_hooks: true
|
||||
openai:
|
||||
id: openai
|
||||
logo: openai.png
|
||||
|
||||
@@ -202,31 +202,31 @@ en:
|
||||
online:
|
||||
delete: "%{contact_name} is Online, please try again later"
|
||||
integration_apps:
|
||||
dashboard_apps:
|
||||
name: "Dashboard Apps"
|
||||
description: "Dashboard Apps allow you to create and embed applications that display user information, orders, or payment history, providing more context to your customer support agents."
|
||||
dyte:
|
||||
name: "Dyte"
|
||||
description: "Dyte is tool that helps you to add live audio & video to your application with just a few lines of code. This integration allows you to give an option to your agents to have a video or voice call with your customers from without leaving Chatwoot."
|
||||
description: "Dyte is a product that integrates audio and video functionalities into your application. With this integration, your agents can start video/voice calls with your customers directly from Chatwoot."
|
||||
meeting_name: "%{agent_name} has started a meeting"
|
||||
slack:
|
||||
name: "Slack"
|
||||
description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack."
|
||||
description: "Integrate Chatwoot with Slack to keep your team in sync. This integration allows you to receive notifications for new conversations and respond to them directly within Slack's interface."
|
||||
webhooks:
|
||||
name: "Webhooks"
|
||||
description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks."
|
||||
description: "Webhook events provide real-time updates about activities in your Chatwoot account. You can subscribe to your preferred events, and Chatwoot will send you HTTP callbacks with the updates."
|
||||
dialogflow:
|
||||
name: "Dialogflow"
|
||||
description: "Build chatbots using Dialogflow and connect them to your inbox quickly. Let the bots handle the queries before handing them off to a customer service agent."
|
||||
fullcontact:
|
||||
name: "Fullcontact"
|
||||
description: "FullContact integration helps to enrich visitor profiles. Identify the users as soon as they share their email address and offer them tailored customer service. Connect your FullContact to your account by sharing the FullContact API Key."
|
||||
description: "Build chatbots with Dialogflow and easily integrate them into your inbox. These bots can handle initial queries before transferring them to a customer service agent."
|
||||
google_translate:
|
||||
name: "Google Translate"
|
||||
description: "Make it easier for agents to translate messages by adding a Google Translate Integration. Google translate helps to identify the language automatically and convert it to the language chosen by the agent/account admin."
|
||||
description: "Integrate Google Translate to help agents easily translate customer messages. This integration automatically detects the language and converts it to the agent's or admin's preferred language."
|
||||
openai:
|
||||
name: "OpenAI"
|
||||
description: "Integrate powerful AI features into Chatwoot by leveraging the GPT models from OpenAI."
|
||||
description: "Leverage the power of large language models from OpenAI with the features such as reply suggestions, summarization, message rephrasing, spell-checking, and label classification."
|
||||
linear:
|
||||
name: "Linear"
|
||||
description: "Create Linear issues from conversations, or link existing ones for seamless tracking."
|
||||
description: "Create issues in Linear directly from your conversation window. Alternatively, link existing Linear issues for a more streamlined and efficient issue tracking process."
|
||||
public_portal:
|
||||
search:
|
||||
search_placeholder: Search for article by title or body...
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
"eslint-plugin-jsx-a11y": "6.7.1",
|
||||
"eslint-plugin-prettier": "5.0.0",
|
||||
"eslint-plugin-storybook": "^0.6.14",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"fake-indexeddb": "^6.0.0",
|
||||
"husky": "^7.0.0",
|
||||
"jsdom": "^24.1.0",
|
||||
|
||||
@@ -17,6 +17,7 @@ module.exports = {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
inter: ['Inter', ...defaultTheme.fontFamily.sans],
|
||||
interDisplay: ['Inter Display', ...defaultTheme.fontFamily.sans],
|
||||
},
|
||||
},
|
||||
fontSize: {
|
||||
|
||||
47
yarn.lock
47
yarn.lock
@@ -10418,17 +10418,18 @@ eslint-plugin-storybook@^0.6.14:
|
||||
requireindex "^1.1.0"
|
||||
ts-dedent "^2.2.0"
|
||||
|
||||
eslint-plugin-vue@^9.17.0:
|
||||
version "9.17.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz#4501547373f246547083482838b4c8f4b28e5932"
|
||||
integrity sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==
|
||||
eslint-plugin-vue@^9.27.0:
|
||||
version "9.27.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz#c22dae704a03d9ecefa81364ff89f60ce0481f94"
|
||||
integrity sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
globals "^13.24.0"
|
||||
natural-compare "^1.4.0"
|
||||
nth-check "^2.1.1"
|
||||
postcss-selector-parser "^6.0.13"
|
||||
semver "^7.5.4"
|
||||
vue-eslint-parser "^9.3.1"
|
||||
postcss-selector-parser "^6.0.15"
|
||||
semver "^7.6.0"
|
||||
vue-eslint-parser "^9.4.3"
|
||||
xml-name-validator "^4.0.0"
|
||||
|
||||
eslint-rule-composer@^0.3.0:
|
||||
@@ -11561,6 +11562,13 @@ globals@^13.20.0:
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
|
||||
globals@^13.24.0:
|
||||
version "13.24.0"
|
||||
resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
|
||||
integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
|
||||
dependencies:
|
||||
type-fest "^0.20.2"
|
||||
|
||||
globalthis@^1.0.0, globalthis@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
|
||||
@@ -16223,7 +16231,7 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.4:
|
||||
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.4:
|
||||
version "6.0.13"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
|
||||
integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==
|
||||
@@ -16231,6 +16239,14 @@ postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-select
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-selector-parser@^6.0.15:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38"
|
||||
integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-svgo@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e"
|
||||
@@ -17830,13 +17846,18 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.3.7:
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.3.6, semver@^7.5.3, semver@^7.5.4:
|
||||
semver@^7.3.6, semver@^7.5.3:
|
||||
version "7.5.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
semver@^7.6.0:
|
||||
version "7.6.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
||||
|
||||
send@0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
||||
@@ -19976,10 +19997,10 @@ vue-easytable@2.5.5:
|
||||
vue "^2.6.12"
|
||||
vue-template-compiler "^2.6.11"
|
||||
|
||||
vue-eslint-parser@^9.3.1:
|
||||
version "9.3.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz#429955e041ae5371df5f9e37ebc29ba046496182"
|
||||
integrity sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==
|
||||
vue-eslint-parser@^9.4.3:
|
||||
version "9.4.3"
|
||||
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz#9b04b22c71401f1e8bca9be7c3e3416a4bde76a8"
|
||||
integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
|
||||
dependencies:
|
||||
debug "^4.3.4"
|
||||
eslint-scope "^7.1.1"
|
||||
|
||||
Reference in New Issue
Block a user