多维表格
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.

804 lines
27 KiB

Nc feat/integrations (#8903) * feat: integrations backend (WIP) * feat: migration - source table * feat: updated migration * feat: integration APIs - WIP * feat: integration - crud, acl, api tests * feat: integration - crud, acl, api tests * feat: integration - GUI integration * feat: private integration config * feat: integration GUI * feat: delete api and source creation * feat: add hint for input fields * fix: source creation bugs * refactor: placeholder text correction * refactor: include context * feat: integration delete with transaction * refactor: permission scope correction and move ee logic * refactor: migration correction and improvements * feat: confirm dialog * refactor: review comments * refactor: meta service changes * feat: add oss support - WIP * feat: add oss support * refactor: coderabbt suggestions * refactor: exclude config from api response * refactor: coderabbit review comments * refactor: rename migration names * fix: method name correction * fix(nc-gui): integration ui changes * fix(nc-gui): add edit integration ui changes * fix(nc-gui): add shared badge in integrations list * feat(nc-gui): duplicate integration * fix(nocodb): add copy from id integration support in create integration api * fix(nc-gui): update useIntegration store * fix(nc-gui): test connection btn style update * fix(nc-gui): update new integration modal * feat(nc-gui): add sort integration list support * fix(nc-gui): integration table to be center aligned * fix(nc-gui): move form item required mark to right side * fix: remove divider * fix(nc-gui): add input shadow * fix(nc-gui): base name validator error message * fix(nc-gui): add border if search connection input has some value * fix(nc-gui): add close btn in integration modal * chore(nc-gui): lint * fix(nc-gui): pr review changes * chore(nc-gui): cleanup unused code * chore(nc-gui): lint * fix(nc-gui): integrationsType not found issue * fix(nc-gui): update data source table * fix(nc-gui): populate integration name only on input value change * fix(nc-gui): create data source form update * fix: type correction * fix: label correction * fix: font corrections * fix: remove help text * fix: grammar in help text * fix(nc-gui): edit source ui changes * fix(nc-gui): base settings modal changes & datasource search feat * fix(nc-gui): update data source table * fix(nc-gui): move integrations outside team & settings * fix(nc-gui): make connections table full width * fix(nc-gui): modal height issue in small screen * fix(nc-gui): disable editing selected connection in edit data source * fix(nc-gui): add data sources in base settings tab * fix(nc-gui): ant design multiple warnings issue * fix(nc-gui): create source page scrollbar issue * feat(nc-gui): create connection from create source page * chore(nc-gui): lint * fix(nc-gui): update project members tab content margin * chore: label text change * fix: font changes * chore: font corrections * chore: integration => connection * fix(nc-gui): disable auto editing database name on changing connection name * fix(nc-gui): table header overflow issue * fix(nc-gui): show connection crud operation messages in toast * feat(nc-gui): request new integration ui * fix(nc-gui): text area height adjust issue * fix(nc-gui): add connection from source create issue * fix(nc-gui): show data source details in modal * fix(nc-gui): hide private connection option * fix(nc-gui): user should able to edit & save connection without test connection if only title updated * fix(nc-gui): add integration page in oss * fix(nc-gui): typo currection * fix(nc-gui): oss create base ui changes * misc: minor formatting changes * misc: formatting corrections * fix(nc-gui): overlay close btn issue * fix(nc-gui): some review changes * fix(nc-gui): remove link beetween connection name & database name * fix(nc-gui): update edit base/source modal oss * fix(nc-gui): add db type icon in select connection * chore(nc-gui): lint * fix: integration list - allow access based on base level role * fix(nc-gui): load integrations on creating integration from source create issue * fix(nc-gui): add connection count in tab * fix: correction in soft delete logic * fix(nc-gui): reset use ssl on panel collapse * fix(nc-gui): reduce select input font weight * fix(nc-gui): update connection edit access control * fix: integration read api correction * fix(nc-gui): some review changes * fix(nc-gui): labels update * fix(nc-gui): udpate text in delete modal integration -> connection * fix: remove permission from wrong scope * refactor: swagger description correction * fix(nc-gui): remove connection between source name & database name * fix(nc-gui): test connection is not needed form source name. inflection field changes * refactor: include integration title with source * feat: integration pagination * fix: remove unused prop * fix(nc-gui): update all tables tab btns tooltip * feat: new integration request * refactor: replace delete statement and use assigning undefined for better performance * feat(nc-gui): sync data support in project page * fix(nc-gui): all sync data type list * fix(nc-gui): close sync data modal issue * fix(nc-gui): add bg gray color on db icon of tooltip * fix(nc-gui): make connection as required field * fix(nc-gui): show connection name if not found and reload page * fix(nc-gui): show connection name in ds list * fix(nc-gui): ssl related changes * fix: oss permission * fix(nc-gui): active tab issue on clicking source * feat: include source count and sources in api response * fix(nc-gui): add getIntegration fun in useIntegrationStore * fix(nc-gui): source list udpate issue on updating source details * fix(nc-gui): fix external source icon alignment * feat: include base name and source count * fix: query correction * fix(nc-gui): show liked sources list in delete connection modal * fix(nc-gui): display connection usage information in list * fix(nc-gui): add sync data types icons * fix(nc-gui): add pagination support in connection list * fix(nc-gui): connection pagination issue * fix(nc-gui): connection tab count update issue * test(nc-gui): some of test cases updated * fix(nc-gui): some minor review changes * fix(nc-gui): minor ui changes * fix(nc-gui): Cannot read properties of undefined (reading 'sub_type') * fix(nc-gui): udpate all tables btn text * fix(nc-gui): ui changes * fix(nc-gui): overflow issue * fix(nc-gui): add connection icon & back btn in modal * fix(nc-gui): some minor ui changes * test(nc-gui): update source restriction test cases * chore(test): remove only from test * fix(nc-gui): update style of delete connection modal * test(nc-gui): update acl pw test cases * fix(test): ws collaboration role accss test fail issue * fix(nc-gui): add connection successfully added modal * fix(nc-gui): update connection added modal * fix(nc-gui): trigger sync request event on upvote * chore(nc-gui): lint * fix(nc-gui): add learn more btn in connection successfull modal * fix(nc-gui): add integration docs link support * fix(nc-gui): integration table name field text truncate issue * fix: misc corrections * misc: button width change * fix(nc-gui): update icons * fix(nc-gui): update test connection btn icons * fix(nc-gui): all tables btn gap issue * feat(nc-gui): search option in sync data modal * feat(nc-gui): search connection through api * fix(nc-gui): add base and source icon in delete connection modal * fix: update sync request event * fix(nc-gui): rebase conflict issue * fix: connections text length * fix(nc-gui): enable integration/create source supported docs option * fix(nc-gui): update advanced option header style --------- Co-authored-by: mertmit <mertmit99@gmail.com> Co-authored-by: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
4 months ago
<script lang="ts" setup>
import type { IntegrationType, UserType, WorkspaceUserType } from 'nocodb-sdk'
import dayjs from 'dayjs'
type SortFields = 'title' | 'sub_type' | 'created_at' | 'created_by' | 'source_count'
const {
integrations,
isLoadingIntegrations,
deleteConfirmText,
integrationPaginationData,
successConfirmModal,
searchQuery,
loadIntegrations,
deleteIntegration,
editIntegration,
duplicateIntegration,
getIntegration,
} = useIntegrationStore()
const { $api, $e } = useNuxtApp()
const { collaborators } = storeToRefs(useWorkspace())
const isDeleteIntegrationModalOpen = ref(false)
const toBeDeletedIntegration = ref<
| (IntegrationType & {
sources?: {
id: string
alias: string
project_title: string
base_id: string
}[]
})
| null
>(null)
const isLoadingGetLinkedSources = ref(false)
const tableWrapper = ref<HTMLDivElement>()
const titleHeaderCellRef = ref<HTMLDivElement>()
const orderBy = ref<Partial<Record<SortFields, 'asc' | 'desc' | undefined>>>({})
const localCollaborators = ref<User[] | UserType[]>([])
const { width } = useElementBounding(titleHeaderCellRef)
const collaboratorsMap = computed<Map<string, (WorkspaceUserType & { id: string }) | User | UserType>>(() => {
const map = new Map()
;(isEeUI ? collaborators.value : localCollaborators.value)?.forEach((coll) => {
if (coll?.id) {
map.set(coll.id, coll)
}
})
return map
})
const filteredIntegrations = computed(() =>
(integrations.value || []).sort((a, b) => {
if (orderBy.value.title) {
if (a.title && b.title) {
return orderBy.value.title === 'asc' ? (a.title < b.title ? -1 : 1) : a.title > b.title ? -1 : 1
}
} else if (orderBy.value.sub_type) {
if (a.sub_type && b.sub_type) {
return orderBy.value.sub_type === 'asc' ? (a.sub_type < b.sub_type ? -1 : 1) : a.sub_type > b.sub_type ? -1 : 1
}
} else if (orderBy.value.created_at) {
if (a?.created_at && b?.created_at) {
return orderBy.value.created_at === 'asc'
? dayjs(a.created_at).local().isBefore(dayjs(b.created_at).local())
? -1
: 1
: dayjs(a.created_at).local().isAfter(dayjs(b.created_at).local())
? -1
: 1
}
} else if (orderBy.value.created_by) {
if (a.created_by && b.created_by && collaboratorsMap.value.get(a.created_by) && collaboratorsMap.value.get(b.created_by)) {
return orderBy.value.created_by === 'asc'
? collaboratorsMap.value.get(a.created_by)?.email < collaboratorsMap.value.get(b.created_by)?.email
? -1
: 1
: collaboratorsMap.value.get(a.created_by)?.email > collaboratorsMap.value.get(b.created_by)?.email
? -1
: 1
}
} else if (orderBy.value.source_count) {
if (a.source_count !== undefined && b.source_count !== undefined) {
return orderBy.value.source_count === 'asc' ? a.source_count - b.source_count : b.source_count - a.source_count
}
}
return 0
}),
)
async function loadConnections(
page = integrationPaginationData.value.page,
limit = integrationPaginationData.value.pageSize,
updateCurrentPage = true,
) {
try {
if (updateCurrentPage) {
integrationPaginationData.value.page = 1
}
await loadIntegrations(undefined, undefined, updateCurrentPage ? 1 : page, limit)
} catch {}
}
const handleChangePage = async (page: number) => {
integrationPaginationData.value.page = page
await loadConnections(undefined, undefined, false)
}
const { onLeft, onRight, onUp, onDown } = usePaginationShortcuts({
paginationDataRef: integrationPaginationData,
changePage: handleChangePage,
isViewDataLoading: isLoadingIntegrations,
})
const openDeleteIntegration = async (source: IntegrationType) => {
isLoadingGetLinkedSources.value = true
$e('c:integration:delete')
deleteConfirmText.value = null
isDeleteIntegrationModalOpen.value = true
toBeDeletedIntegration.value = source
const connectionDetails = await getIntegration(source, {
includeSources: true,
})
toBeDeletedIntegration.value.sources = connectionDetails?.sources || []
isLoadingGetLinkedSources.value = false
}
const onDeleteConfirm = async () => {
await deleteIntegration(toBeDeletedIntegration.value, true)
}
const loadOrgUsers = async () => {
try {
const response: any = await $api.orgUsers.list()
if (!response?.list) return
localCollaborators.value = response.list as UserType[]
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const updateOrderBy = (field: SortFields) => {
if (!integrations.value?.length) return
// Only single field sort supported, other old field sort config will reset
if (orderBy.value?.[field] === 'asc') {
orderBy.value = {
[field]: 'desc',
}
} else if (orderBy.value?.[field] === 'desc') {
orderBy.value = {
[field]: undefined,
}
} else {
orderBy.value = {
[field]: 'asc',
}
}
}
const handleSearchConnection = useDebounceFn(() => {
loadConnections()
}, 250)
const renderAltOrOptlKey = () => {
return isMac() ? '⌥' : 'ALT'
}
useEventListener(tableWrapper, 'scroll', () => {
const stickyHeaderCell = tableWrapper.value?.querySelector('th.cell-title')
const nonStickyHeaderFirstCell = tableWrapper.value?.querySelector('th.cell-type')
if (!stickyHeaderCell?.getBoundingClientRect().right || !nonStickyHeaderFirstCell?.getBoundingClientRect().left) {
return
}
if (nonStickyHeaderFirstCell?.getBoundingClientRect().left < stickyHeaderCell?.getBoundingClientRect().right) {
tableWrapper.value?.classList.add('sticky-shadow')
} else {
tableWrapper.value?.classList.remove('sticky-shadow')
}
})
onMounted(async () => {
if (!isEeUI) {
await Promise.allSettled([!integrations.value.length && loadIntegrations(), loadOrgUsers()])
} else if (!integrations.value.length) {
await loadIntegrations()
}
})
// Keyboard shortcuts for pagination
onKeyStroke('ArrowLeft', onLeft)
onKeyStroke('ArrowRight', onRight)
onKeyStroke('ArrowUp', onUp)
onKeyStroke('ArrowDown', onDown)
</script>
<template>
<div class="h-full flex flex-col gap-6 nc-workspace-connections">
<div class="flex justify-between gap-12">
<div class="text-sm font-normal text-gray-600">
<div>
Connections simplify managing stored configurations for different integrations.
<a target="_blank" rel="noopener noreferrer"> Learn more </a>
</div>
</div>
<div class="flex items-center justify-end gap-3 mx-1">
<a-input
v-model:value="searchQuery"
type="text"
class="nc-search-integration-input !min-w-[300px] nc-input-sm flex-none"
:placeholder="`${$t('general.search')} ${$t('general.connections').toLowerCase()}`"
allow-clear
@input="handleSearchConnection"
>
<template #prefix>
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500" />
</template>
</a-input>
</div>
</div>
<div class="table-container relative flex-1">
<div
ref="tableWrapper"
class="nc-workspace-integration-table relative nc-scrollbar-thin !overflow-auto max-h-[calc(100%_-_40px)]"
:class="{
'h-full': filteredIntegrations?.length,
}"
>
<table class="!sticky top-0 z-5 w-full">
<thead>
<tr>
<th
class="cell-title !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.title,
}"
@click="updateOrderBy('title')"
>
<div ref="titleHeaderCellRef" class="flex items-center gap-3">
<div>Name</div>
<GeneralIcon
v-if="orderBy.title"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy?.title === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-type !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.sub_type,
}"
@click="updateOrderBy('sub_type')"
>
<div class="flex items-center gap-3">
<div>Type</div>
<GeneralIcon
v-if="orderBy.sub_type"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy.sub_type === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-created-date !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.created_at,
}"
@click="updateOrderBy('created_at')"
>
<div class="flex items-center gap-3">
<div>Date added</div>
<GeneralIcon
v-if="orderBy.created_at"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy.created_at === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-added-by !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
'!text-gray-700': orderBy.created_by,
}"
@click="updateOrderBy('created_by')"
>
<div class="flex items-center gap-3">
<div>Added by</div>
<GeneralIcon
v-if="orderBy.created_by"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy.created_by === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th
class="cell-usage !hover:bg-gray-100 select-none cursor-pointer"
:class="{
'cursor-not-allowed': !filteredIntegrations?.length,
}"
@click="updateOrderBy('source_count')"
>
<div class="flex items-center gap-3">
<div>Usage</div>
<GeneralIcon
v-if="orderBy?.source_count"
icon="chevronDown"
class="flex-none"
:class="{
'transform rotate-180': orderBy?.source_count === 'asc',
}"
/>
<GeneralIcon v-else icon="chevronUpDown" class="flex-none" />
</div>
</th>
<th class="cell-actions">
<div>Actions</div>
</th>
</tr>
</thead>
</table>
<template v-if="filteredIntegrations?.length">
<table class="h-full max-h-[calc(100%_-_55px)] w-full">
<tbody>
<tr v-for="integration of filteredIntegrations" :key="integration.id" @click="editIntegration(integration)">
<td class="cell-title">
<div
class="gap-3"
:style="{
maxWidth: `${width}px`,
}"
>
<NcTooltip placement="bottom" class="truncate" show-on-truncate-only>
<template #title> {{ integration.title }}</template>
{{ integration.title }}
</NcTooltip>
<span v-if="integration.is_private">
<NcBadge :border="false" class="text-primary !h-4.5 bg-brand-50 text-xs">{{
$t('general.private')
}}</NcBadge>
</span>
</div>
</td>
<td class="cell-type">
<div>
<NcBadge rounded="lg" class="flex items-center gap-2 px-2 py-1 !h-7 truncate">
<WorkspaceIntegrationsIcon
v-if="integration.sub_type"
:integration-type="integration.sub_type"
size="xs"
class="!p-0 !bg-transparent"
/>
<NcTooltip placement="bottom" show-on-truncate-only class="text-sm truncate">
<template #title> {{ clientTypesMap[integration?.sub_type]?.text || integration?.sub_type }}</template>
{{
integration.sub_type && clientTypesMap[integration.sub_type]
? clientTypesMap[integration.sub_type]?.text
: integration.sub_type
}}
</NcTooltip>
</NcBadge>
</div>
</td>
<td class="cell-created-date">
<div>
<NcTooltip placement="bottom" show-on-truncate-only>
<template #title> {{ dayjs(integration.created_at).local().format('DD MMM YYYY') }}</template>
{{ dayjs(integration.created_at).local().format('DD MMM YYYY') }}
</NcTooltip>
</div>
</td>
<td class="cell-added-by">
<div>
<div
v-if="integration.created_by && collaboratorsMap.get(integration.created_by)?.email"
class="w-full flex gap-3 items-center"
>
<GeneralUserIcon
:email="collaboratorsMap.get(integration.created_by)?.email"
size="base"
class="flex-none"
/>
<div class="flex-1 flex flex-col max-w-[calc(100%_-_44px)]">
<div class="w-full flex gap-3">
<NcTooltip
class="text-sm !leading-5 text-gray-800 capitalize font-semibold truncate"
show-on-truncate-only
placement="bottom"
>
<template #title>
{{
collaboratorsMap.get(integration.created_by)?.display_name ||
collaboratorsMap
.get(integration.created_by)
?.email?.slice(0, collaboratorsMap.get(integration.created_by)?.email.indexOf('@'))
}}
</template>
{{
collaboratorsMap.get(integration.created_by)?.display_name ||
collaboratorsMap
.get(integration.created_by)
?.email?.slice(0, collaboratorsMap.get(integration.created_by)?.email.indexOf('@'))
}}
</NcTooltip>
</div>
<NcTooltip class="text-xs !leading-4 text-gray-600 truncate" show-on-truncate-only placement="bottom">
<template #title>
{{ collaboratorsMap.get(integration.created_by)?.email }}
</template>
{{ collaboratorsMap.get(integration.created_by)?.email }}
</NcTooltip>
</div>
</div>
<template v-else>{{ integration.created_by }} </template>
</div>
</td>
<td class="cell-usage">
<div>{{ integration?.source_count ?? 0 }}</div>
</td>
<td class="cell-actions" @click.stop>
<div class="justify-end">
<NcDropdown placement="bottomRight">
<NcButton size="small" type="secondary">
<GeneralIcon icon="threeDotVertical" />
</NcButton>
<template #overlay>
<NcMenu>
<NcMenuItem @click="editIntegration(integration)">
<GeneralIcon class="text-gray-800" icon="edit" />
<span>{{ $t('general.edit') }}</span>
</NcMenuItem>
<NcMenuItem @click="duplicateIntegration(integration)">
<GeneralIcon class="text-gray-800" icon="duplicate" />
<span>{{ $t('general.duplicate') }}</span>
</NcMenuItem>
<NcDivider />
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click="openDeleteIntegration(integration)">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}
</NcMenuItem>
</NcMenu>
</template>
</NcDropdown>
</div>
</td>
</tr>
</tbody>
</table>
</template>
</div>
<div
v-show="isLoadingIntegrations"
class="flex items-center justify-center absolute left-0 top-0 w-full h-full z-10 pb-10 pointer-events-none"
>
<div class="flex flex-col justify-center items-center gap-2">
<GeneralLoader size="xlarge" />
<span class="text-center">{{ $t('general.loading') }}</span>
</div>
</div>
<div
v-if="!isLoadingIntegrations && (!integrations?.length || !filteredIntegrations.length)"
class="flex-none integration-table-empty flex items-center justify-center py-8 px-6 h-full max-h-[calc(100%_-_94px)]"
>
<div
v-if="integrations?.length && !filteredIntegrations.length"
class="px-2 py-6 text-gray-500 flex flex-col items-center gap-6 text-center"
>
<img
src="~assets/img/placeholder/no-search-result-found.png"
class="!w-[164px] flex-none"
alt="No search results found"
/>
{{ $t('title.noResultsMatchedYourSearch') }}
</div>
<div v-else class="flex-none text-center flex flex-col items-center gap-3">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" class="!my-0" />
</div>
</div>
<div
v-if="integrationPaginationData.totalRows"
class="flex flex-row justify-center items-center bg-gray-50 min-h-10"
:class="{
'pointer-events-none': isLoadingIntegrations,
}"
>
<div class="flex justify-between items-center w-full px-6">
<div>&nbsp;</div>
<NcPagination
v-model:current="integrationPaginationData.page"
v-model:page-size="integrationPaginationData.pageSize"
:total="+integrationPaginationData.totalRows"
show-size-changer
:use-stored-page-size="false"
:prev-page-tooltip="`${renderAltOrOptlKey()}+←`"
:next-page-tooltip="`${renderAltOrOptlKey()}+→`"
:first-page-tooltip="`${renderAltOrOptlKey()}+↓`"
:last-page-tooltip="`${renderAltOrOptlKey()}+↑`"
@update:current="loadConnections(undefined, undefined, false)"
@update:page-size="loadConnections(integrationPaginationData.page, $event, false)"
/>
<div class="text-gray-500 text-xs">
{{ integrationPaginationData.totalRows }} {{ integrationPaginationData.totalRows === 1 ? 'record' : 'records' }}
</div>
</div>
</div>
</div>
<GeneralDeleteModal
v-model:visible="isDeleteIntegrationModalOpen"
:entity-name="$t('general.connection')"
:on-delete="onDeleteConfirm"
:delete-label="$t('general.delete')"
:show-default-delete-msg="!isLoadingGetLinkedSources && !toBeDeletedIntegration?.sources?.length"
>
<template #entity-preview>
<template v-if="isLoadingGetLinkedSources">
<div class="rounded-lg overflow-hidden">
<a-skeleton-input active class="h-9 !rounded-md !w-full"></a-skeleton-input>
</div>
<div class="rounded-lg overflow-hidden mt-2">
<a-skeleton-input active class="h-9 !rounded-md !w-full"></a-skeleton-input>
</div>
</template>
<div v-else-if="toBeDeletedIntegration" class="w-full flex flex-col text-gray-800">
<div class="flex flex-row items-center py-2 px-3.25 bg-gray-50 rounded-lg text-gray-700 mb-4">
<WorkspaceIntegrationsIcon
:integration-type="toBeDeletedIntegration.sub_type"
size="xs"
class="!p-0 !bg-transparent"
/>
<div
class="text-ellipsis overflow-hidden select-none w-full pl-3"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ toBeDeletedIntegration.title }}
</div>
</div>
<div v-if="toBeDeletedIntegration?.sources?.length" class="flex flex-col pb-2 text-small leading-[18px] text-gray-500">
<div class="mb-1">Following external data sources using this connection will also be removed</div>
<ul class="!list-disc ml-6 mb-0">
<li
v-for="(source, idx) of toBeDeletedIntegration.sources"
:key="idx"
class="marker:text-gray-500 !marker:(flex items-center !-mt-1)"
>
<div class="flex items-center gap-1">
<div class="flex items-center">
&nbsp;
<GeneralProjectIcon
type="database"
class="!grayscale min-w-4 flex-none -ml-1"
:style="{
filter: 'grayscale(100%) brightness(115%)',
}"
/>
</div>
<NcTooltip class="!truncate !max-w-[45%] flex-none" show-on-truncate-only>
<template #title>
{{ source.project_title }}
</template>
{{ source.project_title }}
</NcTooltip>
>
<GeneralBaseLogo
class="!grayscale min-w-4 flex-none"
:style="{
filter: 'grayscale(100%) brightness(115%)',
}"
/>
<NcTooltip class="truncate !max-w-[45%] capitalize" show-on-truncate-only>
<template #title>
{{ source.alias }}
</template>
{{ source.alias }}
</NcTooltip>
</div>
</li>
</ul>
<div class="mt-2">Do you want to proceed anyway?</div>
</div>
</div>
</template>
</GeneralDeleteModal>
<NcModal v-model:visible="successConfirmModal.isOpen" centered size="small" @keydown.esc="successConfirmModal.isOpen = false">
<div class="flex gap-4">
<div>
<GeneralIcon icon="circleCheckSolid" class="flex-none !text-green-700 mt-0.5 !h-6 !w-6" />
</div>
<div class="flex flex-col gap-3">
<div class="flex">
<h3 class="!m-0 text-base font-weight-700 flex-1">
{{ successConfirmModal.title }}
</h3>
<NcButton size="xsmall" type="text" @click="successConfirmModal.isOpen = false">
<GeneralIcon icon="close" class="text-gray-600" />
</NcButton>
</div>
<div class="text-sm text-gray-700">
{{ successConfirmModal.description }}
</div>
<!-- Todo: add link -->
<a target="_blank" rel="noopener noreferrer"> Learn more </a>
</div>
</div>
</NcModal>
</div>
</template>
<style lang="scss" scoped>
.source-card-link {
@apply !text-black !no-underline;
.nc-new-integration-type-title {
@apply text-sm font-weight-600 text-gray-600;
}
}
.source-card {
@apply flex items-center border-1 rounded-lg p-3 cursor-pointer hover:bg-gray-50;
width: 288px;
.name {
@apply ml-4 text-md font-semibold;
}
}
:deep(.ant-input-affix-wrapper.nc-search-integration-input) {
&:not(:has(.ant-input-clear-icon-hidden)):has(.ant-input-clear-icon) {
@apply border-[var(--ant-primary-5)];
}
}
.nc-new-integration-type-wrapper {
@apply flex flex-col gap-3;
}
.table-container {
@apply border-1 border-gray-200 rounded-lg overflow-hidden w-full;
.nc-workspace-integration-table {
&.sticky-shadow {
th,
td {
&.cell-title {
@apply border-r-1 border-gray-200;
}
}
}
&:not(.sticky-shadow) {
th,
td {
&.cell-title {
@apply border-r-1 border-transparent;
}
}
}
thead {
@apply w-full;
th {
@apply bg-gray-50 text-sm text-gray-500 font-weight-500;
&.cell-title {
@apply sticky left-0 z-4 bg-gray-50;
}
}
}
tbody {
@apply w-full;
tr {
@apply cursor-pointer;
td {
@apply text-sm text-gray-600;
&.cell-title {
@apply sticky left-0 z-4 bg-white !text-gray-800 font-semibold;
}
}
}
}
tr {
@apply h-[54px] flex border-b-1 border-gray-200 w-full;
&:hover td {
@apply !bg-gray-50;
}
&.selected td {
@apply !bg-gray-50;
}
th,
td {
@apply h-full;
& > div {
@apply px-6 h-full flex-1 flex items-center;
}
&.cell-title {
@apply flex-1 sticky left-0 z-5;
& > div {
@apply min-w-[250px];
}
}
&.cell-added-by {
@apply basis-[20%];
& > div {
@apply min-w-[250px];
}
}
&.cell-type {
@apply basis-[20%];
& > div {
@apply min-w-[178px];
}
}
&.cell-created-date {
@apply basis-[20%];
& > div {
@apply min-w-[158px];
}
}
&.cell-usage {
@apply w-[120px];
& > div {
@apply min-w-[118px];
}
}
&.cell-actions {
@apply w-[100px];
& > div {
@apply min-w-[98px];
}
}
}
}
}
}
.cell-header {
@apply text-xs font-semibold text-gray-500;
}
</style>