mirror of https://github.com/nocodb/nocodb
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
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 |
|
}
|
|
|