多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

553 lines
15 KiB

import type { FunctionalComponent, SVGAttributes } from 'vue'
import type { FormDefinition, IntegrationType, PaginatedType } from 'nocodb-sdk'
import { ClientType, IntegrationsType, SyncDataType } from 'nocodb-sdk'
import GeneralBaseLogo from '~/components/general/BaseLogo.vue'
import type { IntegrationStoreEvents as IntegrationStoreEventsTypes } from '#imports'
enum IntegrationsPageMode {
LIST,
ADD,
EDIT,
}
const integrationType: Record<'PostgreSQL' | 'MySQL' | 'SQLITE' | 'OpenAI', ClientType | SyncDataType> = {
PostgreSQL: ClientType.PG,
MySQL: ClientType.MYSQL,
SQLITE: ClientType.SQLITE,
OpenAI: SyncDataType.OPENAI,
}
type IntegrationsSubType = (typeof integrationType)[keyof typeof integrationType]
function getStaticInitializor(type: IntegrationsSubType) {
const genericValues = {
payload: {},
}
switch (type) {
case integrationType.PostgreSQL:
return {
...genericValues,
type: integrationType.PostgreSQL,
title: 'PostgreSQL',
logo: h(GeneralBaseLogo, {
'source-type': 'pg',
'class': 'logo',
}),
}
case integrationType.MySQL:
return {
...genericValues,
type: integrationType.MySQL,
title: 'MySQL',
logo: h(GeneralBaseLogo, {
'source-type': 'mysql2',
'class': 'logo',
}),
}
case integrationType.SQLITE:
return {
...genericValues,
type: integrationType.SQLITE,
title: 'SQLite',
logo: h(GeneralBaseLogo, {
'source-type': 'sqlite3',
'class': 'logo',
}),
}
}
}
const integrationForms: Record<string, FormDefinition> = {}
const [useProvideIntegrationViewStore, _useIntegrationStore] = useInjectionState(() => {
const router = useRouter()
const route = router.currentRoute
const { api } = useApi()
const pageMode = ref<IntegrationsPageMode | null>(null)
const activeIntegration = ref<IntegrationType | null>(null)
const activeIntegrationItem = ref<IntegrationItemType | null>(null)
const workspaceStore = useWorkspace()
const { activeWorkspaceId } = storeToRefs(workspaceStore)
const integrations = ref<IntegrationType[]>([])
const searchQuery = ref('')
const integrationsCategoryFilter = ref<string[]>([])
const integrationPaginationData = ref<PaginatedType>({ page: 1, pageSize: 25, totalRows: 0 })
const deleteConfirmText = ref<string | null>()
const isLoadingIntegrations = ref(false)
const eventBus = useEventBus<IntegrationStoreEventsTypes>(Symbol('integrationStore'))
const { $api, $e } = useNuxtApp()
const { t } = useI18n()
const { aiIntegrations } = useNocoAi()
const isFromIntegrationPage = ref(false)
const integrationsRefreshKey = ref(0)
const requestIntegration = ref({
isOpen: false,
msg: '',
isLoading: false,
})
const successConfirmModal = ref({
isOpen: false,
title: t('msg.success.connectionAdded'),
connectionTitle: '',
description: t('msg.success.connectionAddedDesc'),
})
const activeViewTab = computed({
get() {
return (route.value.query?.tab as string) ?? 'integrations'
},
set(tab: string) {
if (requestIntegration.value.isOpen) {
requestIntegration.value.isOpen = false
}
router.push({ query: { ...route.value.query, tab } })
},
})
const loadIntegrations = async (
databaseOnly = false,
baseId = undefined,
page: number = integrationPaginationData.value.page!,
limit: number = integrationPaginationData.value.pageSize!,
) => {
try {
if (!activeWorkspaceId.value) return
isLoadingIntegrations.value = true
if (!databaseOnly && limit * (page - 1) > integrationPaginationData.value.totalRows!) {
integrationPaginationData.value.page = 1
page = 1
}
const { list, pageInfo } = await api.integration.list(
databaseOnly
? {
type: IntegrationsType.Database,
includeDatabaseInfo: true,
baseId,
}
: {
offset: limit * (page - 1),
limit,
...(searchQuery.value.trim() ? { query: searchQuery.value } : {}),
},
)
integrations.value = list
if (!databaseOnly) {
integrationPaginationData.value.totalRows = pageInfo.totalRows ?? 0
}
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
integrations.value = []
integrationPaginationData.value.totalRows = 0
integrationPaginationData.value.page = 1
} finally {
isLoadingIntegrations.value = false
}
}
const addIntegration = async (integration: IntegrationItemType) => {
activeIntegration.value = integration.dynamic ? integration : getStaticInitializor(integration.subType)
activeIntegrationItem.value = integration
if (integration.dynamic === true && !(integration.subType in integrationForms)) {
const integrationInfo = await $api.integrations.info(integration.type, integration.subType)
if (integrationInfo?.form) {
integrationForms[integration.subType] = integrationInfo.form
activeIntegrationItem.value.form = integrationInfo.form
}
} else if (integration.dynamic === true) {
activeIntegrationItem.value.form = integrationForms[integration.subType]
}
pageMode.value = IntegrationsPageMode.ADD
$e('c:integration:add')
}
const deleteIntegration = async (integration: IntegrationType, force = false) => {
if (!integration?.id) return
$e('a:integration:delete')
try {
await api.integration.delete(integration.id, {
query: force ? { force: 'true' } : {},
})
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.filter((i) => i.id !== integration.id)
}
await loadIntegrations()
// await message.success(`Connection ${integration.title} deleted successfully`)
return true
} catch (e) {
const error = await extractSdkResponseErrorMsgv2(e)
if (error.error === NcErrorType.INTEGRATION_NOT_FOUND) {
await message.error(error.message?.replace(integration.id, integration.title!))
window.location.reload()
return
}
await message.error(await extractSdkResponseErrorMsg(e))
}
deleteConfirmText.value = null
}
const updateIntegration = async (integration: IntegrationType) => {
if (!integration.id) return
$e('a:integration:update')
try {
await api.integration.update(integration.id, integration)
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
i.title = integration.title
}
return i
})
}
await loadIntegrations()
pageMode.value = null
activeIntegration.value = null
await message.success(`Connection "${integration.title}" updated successfully`)
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const setDefaultIntegration = async (integration: IntegrationType) => {
if (!integration.id) return
$e('a:integration:set-default')
try {
await api.integration.setDefault(integration.id)
if (integration.type === IntegrationsType.Ai) {
aiIntegrations.value = aiIntegrations.value.map((i) => {
if (i.id === integration.id) {
i.is_default = true
} else {
i.is_default = false
}
return i
})
}
await loadIntegrations()
pageMode.value = null
activeIntegration.value = null
await message.success(`Connection "${integration.title}" set as default successfully`)
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const saveIntegration = async (
integration: IntegrationType,
mode: 'create' | 'duplicate' = 'create',
loadDatasourceInfo = false,
baseId: string | undefined = undefined,
) => {
if (mode === 'create') {
$e('a:integration:create')
} else {
$e('a:integration:duplicate')
}
try {
const response = await api.integration.create(integration)
if (response && response?.id) {
if (!loadDatasourceInfo) {
integrations.value.push(response)
}
if (response.type === IntegrationsType.Ai) {
aiIntegrations.value.push({
id: response.id,
title: response.title,
is_default: response.is_default,
type: response.type,
sub_type: response.sub_type,
})
}
}
await loadIntegrations(loadDatasourceInfo, baseId)
if (mode === 'create') {
eventBus.emit(IntegrationStoreEvents.INTEGRATION_ADD, response)
}
pageMode.value = null
activeIntegration.value = null
if (response?.title && mode === 'create') {
if (isFromIntegrationPage.value) {
activeViewTab.value = 'connections'
if (response.type === IntegrationsType.Database) {
successConfirmModal.value.connectionTitle = response.title ?? ''
successConfirmModal.value.isOpen = true
}
} else {
await message.success(`Connection "${response.title}" created successfully`)
}
}
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const duplicateIntegration = async (integration: IntegrationType) => {
if (!integration?.id) return
try {
isLoadingIntegrations.value = true
saveIntegration(
{
title: integration.title,
config: {},
type: integration.type,
copy_from_id: integration.id,
},
'duplicate',
)
} catch (e) {
await message.error(await extractSdkResponseErrorMsg(e))
} finally {
isLoadingIntegrations.value = false
}
}
const getIntegration = async (
integration: IntegrationType,
options?: {
includeConfig?: boolean
includeSources?: boolean
},
) => {
if (!integration?.id) return
try {
const integrationWithConfig = await api.integration.read(integration.id, {
...(options || {}),
})
return integrationWithConfig
} catch (e) {
const error = await extractSdkResponseErrorMsgv2(e)
if (error.error === NcErrorType.INTEGRATION_NOT_FOUND) {
await message.error(error.message?.replace(integration.id!, integration.title!))
window.location.reload()
return
}
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const editIntegration = async (integration: IntegrationType) => {
if (!integration?.id) return
try {
const integrationWithConfig = await getIntegration(integration, { includeConfig: true })
activeIntegration.value = integrationWithConfig
const integrationItem = allIntegrations.find(
(item) => item.type === integration.type && item.subType === integration.sub_type,
)!
activeIntegrationItem.value = integrationItem
if (integrationItem.dynamic === true && !(integrationItem.subType in integrationForms)) {
const integrationInfo = await $api.integrations.info(integrationItem.type, integrationItem.subType)
if (integrationInfo?.form) {
integrationForms[integrationItem.subType] = integrationInfo.form
activeIntegrationItem.value.form = integrationInfo.form
}
} else if (integrationItem.dynamic === true) {
activeIntegrationItem.value.form = integrationForms[integrationItem.subType]
}
pageMode.value = IntegrationsPageMode.EDIT
$e('c:integration:edit')
} catch {}
}
const saveIntegrationRequest = async (msg: string) => {
if (!msg?.trim()) return
requestIntegration.value.isLoading = true
try {
$e('a:integration:new-request', {
value: requestIntegration.value.msg,
})
requestIntegration.value.isLoading = false
requestIntegration.value.isOpen = false
requestIntegration.value.msg = ''
await message.success('Your request has been successfully submitted')
} catch (e) {
requestIntegration.value.isLoading = false
await message.error(await extractSdkResponseErrorMsg(e))
}
}
const listIntegrationByType = async (type: IntegrationsType) => {
if (!activeWorkspaceId.value) return
const { list } = await api.integration.list({
type,
})
return list
}
const loadDynamicIntegrations = async () => {
if (integrationsInitialized.value) return
integrationsInitialized.value = true
const dynamicIntegrations = (await $api.integrations.list()) as {
type: IntegrationsType
subType: string
meta: {
title?: string
icon?: string
description?: string
order?: number
}
}[]
dynamicIntegrations.sort((a, b) => (a.meta.order ?? Infinity) - (b.meta.order ?? Infinity))
for (const di of dynamicIntegrations) {
let icon: FunctionalComponent<SVGAttributes, {}, any, {}> | VNode
if (di.meta.icon) {
if (di.meta.icon in iconMap) {
icon = iconMap[di.meta.icon as keyof typeof iconMap]
} else {
if (isValidURL(di.meta.icon)) {
icon = h('img', {
src: di.meta.icon,
alt: di.meta.title || di.subType,
})
}
}
} else {
icon = iconMap.puzzle
}
const integration: IntegrationItemType = {
title: di.meta.title || di.subType,
subType: di.subType,
icon,
type: di.type,
isAvailable: true,
dynamic: true,
}
allIntegrations.push(integration)
integrationsRefreshKey.value++
}
}
const integrationsIconMap = computed(() => {
// eslint-disable-next-line no-unused-expressions
integrationsRefreshKey.value
const map: Record<string, any> = {}
for (const integration of allIntegrations) {
map[integration.subType] = integration.icon
}
return map
})
return {
IntegrationsPageMode,
integrationType,
pageMode,
activeIntegration,
activeIntegrationItem,
integrationsRefreshKey,
integrations,
isLoadingIntegrations,
deleteConfirmText,
eventBus,
requestIntegration,
integrationPaginationData,
activeViewTab,
isFromIntegrationPage,
successConfirmModal,
searchQuery,
integrationsCategoryFilter,
addIntegration,
loadIntegrations,
deleteIntegration,
updateIntegration,
saveIntegration,
editIntegration,
duplicateIntegration,
saveIntegrationRequest,
getIntegration,
setDefaultIntegration,
integrationsIconMap,
listIntegrationByType,
loadDynamicIntegrations,
}
}, 'integrations-store')
export { useProvideIntegrationViewStore }
export function useIntegrationStore() {
const integrationStore = _useIntegrationStore()
if (integrationStore == null) return useProvideIntegrationViewStore()
return integrationStore
}