mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-02 09:36:49 +00:00
feat: data reflection preps (#10227)
* feat: integration hooks * feat: data reflection * feat: improved UX for data reflection * chore: lint * fix(nc-gui): update nocodb integration ui * fix(nocodb): type error * fix(nc-gui): nocodb integration icon and modal gap issue * fix: defer integration hooks * fix: check proper state * refactor(nc-gui): integration modal * refactor(nc-gui): integration modal ui changes * refactor: change default port * fix(nc-gui): add base id copy input * fix(nc-gui): schema dropdown placement and item height issue * fix(nc-gui): nocodb connection bg color issue * fix(nc-gui): update nocodb integration count and user logo * fix: rspack keep class * feat: get connection menu item * chore: rebase issue * fix: hide nc from sources * feat: move data reflection to model level * fix: remove deprecated fn & fix type errors * feat: reflection settings * feat: feature flag for data reflection * refactor: avoid save on feature flags * fix: properly show host * fix: PR requested changes * fix: use named parameters for queries --------- Co-authored-by: Ramesh Mane <101566080+rameshmane7218@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
password?: boolean
|
||||
}>()
|
||||
|
||||
const { copy } = useCopy()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const copied = ref(false)
|
||||
|
||||
const copyValue = async () => {
|
||||
await copy(props.modelValue)
|
||||
message.info(t('msg.info.copiedToClipboard'))
|
||||
copied.value = true
|
||||
setTimeout(() => {
|
||||
copied.value = false
|
||||
}, 2000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative inline-flex items-center w-full h-full group" @click="copyValue">
|
||||
<a-input :value="modelValue" disabled :type="password ? 'password' : 'input'" class="!pr-8 !truncate" />
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer transition-colors text-nc-content-gray-muted group-hover:text-nc-content-gray-subtle"
|
||||
>
|
||||
<GeneralIcon v-if="copied" class="max-h-4 min-w-4 !text-current" icon="check" />
|
||||
<GeneralIcon v-else class="max-h-4 min-w-4 !text-current" icon="copy" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,102 @@
|
||||
<script lang="ts" setup>
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const vModel = useVModel(props, 'modelValue', emit)
|
||||
|
||||
const { basesList } = storeToRefs(useBases())
|
||||
|
||||
const isOpen = ref<boolean>(false)
|
||||
|
||||
const { copy } = useCopy()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const copied = ref(false)
|
||||
|
||||
const copyValue = async () => {
|
||||
await copy(vModel.value ?? '')
|
||||
message.info(t('msg.info.copiedToClipboard'))
|
||||
copied.value = true
|
||||
setTimeout(() => {
|
||||
copied.value = false
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (basesList.value?.length && basesList.value[0]?.id) {
|
||||
vModel.value = basesList.value[0].id
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative group w-full border-1 border-nc-border-gray-medium rounded-lg bg-nc-bg-gray-extralight h-8 flex items-center text-nc-content-gray-muted"
|
||||
>
|
||||
<NcDropdown v-model:visible="isOpen" overlay-class-name="overflow-hidden max-w-[320px]">
|
||||
<div class="h-full flex-1 px-3 mr-8 flex items-center gap-2 cursor-pointer" @click.stop>
|
||||
<div>
|
||||
{{ vModel }}
|
||||
</div>
|
||||
<GeneralIcon
|
||||
icon="chevronDown"
|
||||
class="!text-current opacity-70 flex-none transform transition-transform duration-250 w-3.5 h-3.5"
|
||||
:class="{ '!rotate-180': isOpen }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #overlay>
|
||||
<NcList
|
||||
v-model:value="vModel"
|
||||
v-model:open="isOpen"
|
||||
:list="basesList"
|
||||
option-label-key="title"
|
||||
search-input-placeholder="Search"
|
||||
option-value-key="id"
|
||||
close-on-select
|
||||
:item-height="56"
|
||||
class="!w-full"
|
||||
container-class-name="!max-h-[171px]"
|
||||
@update:value="copyValue"
|
||||
>
|
||||
<template #listItemContent="{ option }">
|
||||
<div class="flex-1 flex flex-col truncate">
|
||||
<div class="flex items-center gap-2">
|
||||
<GeneralBaseIconColorPicker
|
||||
:type="option?.type"
|
||||
:model-value="parseProp(option.meta).iconColor"
|
||||
size="xsmall"
|
||||
readonly
|
||||
>
|
||||
</GeneralBaseIconColorPicker>
|
||||
<div class="truncate text-nc-content-gray">
|
||||
{{ option.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full pl-7 text-nc-content-gray-muted text-small leading-[18px] truncate">
|
||||
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
|
||||
<template #title>
|
||||
{{ option?.title }}
|
||||
</template>
|
||||
{{ option?.title }}
|
||||
</NcTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</NcList>
|
||||
</template>
|
||||
</NcDropdown>
|
||||
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-2 cursor-pointer transition-colors text-nc-content-gray-muted group-hover:text-nc-content-gray-subtle"
|
||||
@click="copyValue"
|
||||
>
|
||||
<GeneralIcon v-if="copied" class="max-h-4 min-w-4 !text-current" icon="check" />
|
||||
<GeneralIcon v-else class="max-h-4 min-w-4 !text-current" icon="copy" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,305 @@
|
||||
<script setup lang="ts">
|
||||
import type { IntegrationCategoryType, SyncDataType } from 'nocodb-sdk'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
integrationType: IntegrationCategoryType
|
||||
integrationSubType: SyncDataType
|
||||
}>()
|
||||
|
||||
const emits = defineEmits(['update:open'])
|
||||
|
||||
const {
|
||||
connectionDetails,
|
||||
connectionUrl,
|
||||
connectionHost,
|
||||
selectedBase,
|
||||
createConnectionDetails,
|
||||
getConnectionDetails,
|
||||
deleteConnectionDetails,
|
||||
dataReflectionEnabled,
|
||||
} = useDataReflection()
|
||||
|
||||
onMounted(async () => {
|
||||
await getConnectionDetails()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WorkspaceIntegrationsFormsEditOrAddCommonWrapper v-bind="props" @update:open="emits('update:open', $event)">
|
||||
<template v-if="dataReflectionEnabled" #headerRightExtra>
|
||||
<NcButton type="danger" size="small" @click="deleteConnectionDetails">Disable connection</NcButton>
|
||||
</template>
|
||||
<template #leftPanel="{ class: leftPanelClass }">
|
||||
<div :class="leftPanelClass">
|
||||
<div
|
||||
v-if="!dataReflectionEnabled"
|
||||
class="nc-nocodb-connection-details-placeholder flex flex-col gap-8 w-full h-full items-center justify-center text-center mt-10"
|
||||
>
|
||||
<img
|
||||
src="~assets/img/placeholder/nocodb-pg-integration.png"
|
||||
class="!w-full !max-w-[864px] flex-none"
|
||||
alt="NocoDb X Pg integration"
|
||||
/>
|
||||
<span class="text-base font-bold">Connect with your favorite tools</span>
|
||||
<span class="text-sm text-nc-content-gray-subtle2">Integrate with your favourite tools by bypassing our APIs</span>
|
||||
<NcButton size="small" type="primary" @click="createConnectionDetails"> Get connection details </NcButton>
|
||||
<div>
|
||||
<!-- For spacing -->
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="connection-details bg-white relative h-full flex flex-col w-full">
|
||||
<div class="h-full max-h-[calc(100%_-_65px)] flex">
|
||||
<div class="connection-details-left-panel nc-scrollbar-thin relative">
|
||||
<div v-if="connectionDetails" class="h-full w-[768px] mx-auto">
|
||||
<a-form
|
||||
ref="form"
|
||||
:model="connectionDetails"
|
||||
hide-required-mark
|
||||
name="external-base-create-form"
|
||||
layout="vertical"
|
||||
no-style
|
||||
class="flex flex-col gap-5.5"
|
||||
>
|
||||
<div class="nc-form-section">
|
||||
<div class="nc-form-section-title">General</div>
|
||||
<div class="nc-form-section-body">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Connection name">
|
||||
<a-input value="NocoDB" disabled />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nc-form-section">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="nc-form-section-title">Connection details</div>
|
||||
</div>
|
||||
|
||||
<div class="nc-form-section-body">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="Connection URL">
|
||||
<LazyWorkspaceIntegrationsConnectCopyInput v-model="connectionUrl" class="nc-connection-url" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Host">
|
||||
<LazyWorkspaceIntegrationsConnectCopyInput v-model="connectionHost" class="nc-connection-host" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Port">
|
||||
<LazyWorkspaceIntegrationsConnectCopyInput
|
||||
v-model="connectionDetails.port"
|
||||
class="nc-connection-port"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Username">
|
||||
<LazyWorkspaceIntegrationsConnectCopyInput
|
||||
v-model="connectionDetails.username"
|
||||
class="nc-connection-username"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Password">
|
||||
<LazyWorkspaceIntegrationsConnectCopyInput
|
||||
v-model="connectionDetails.password"
|
||||
password
|
||||
class="nc-connection-password"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Database">
|
||||
<LazyWorkspaceIntegrationsConnectCopyInput
|
||||
v-model="connectionDetails.database"
|
||||
class="nc-connection-database"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="Schema">
|
||||
<LazyWorkspaceIntegrationsConnectSchemaInput v-model="selectedBase" class="nc-connection-schema" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- For spacing -->
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
<general-overlay v-else :model-value="true" inline transition class="!bg-opacity-15">
|
||||
<div class="flex items-center justify-center h-full w-full !bg-white !bg-opacity-85 z-1000">
|
||||
<a-spin size="large" />
|
||||
</div>
|
||||
</general-overlay>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!dataReflectionEnabled" #rightPanel> </template>
|
||||
</WorkspaceIntegrationsFormsEditOrAddCommonWrapper>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.connection-details-left-panel {
|
||||
@apply flex-1 flex justify-center;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-header) {
|
||||
@apply !-mt-4 !p-0 flex items-center !cursor-default children:first:flex;
|
||||
}
|
||||
:deep(.ant-collapse-icon-position-right > .ant-collapse-item > .ant-collapse-header .ant-collapse-arrow) {
|
||||
@apply !right-0;
|
||||
}
|
||||
|
||||
:deep(.ant-collapse-content-box) {
|
||||
@apply !px-0 !pb-0 !pt-3;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-explain-error) {
|
||||
@apply !text-xs;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
:deep(.ant-divider) {
|
||||
@apply m-0;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-with-help .ant-form-item-explain) {
|
||||
@apply !min-h-0;
|
||||
}
|
||||
|
||||
:deep(.ant-select .ant-select-selector .ant-select-selection-item) {
|
||||
@apply font-weight-400;
|
||||
}
|
||||
|
||||
.connection-details {
|
||||
:deep(.ant-input-affix-wrapper),
|
||||
:deep(.ant-input),
|
||||
:deep(.ant-select) {
|
||||
@apply !appearance-none border-solid rounded-md;
|
||||
|
||||
&:disabled {
|
||||
@apply bg-nc-bg-gray-extralight text-nc-content-gray-muted border-nc-border-gray-medium;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input-password) {
|
||||
input {
|
||||
@apply !border-none my-0;
|
||||
}
|
||||
}
|
||||
|
||||
.nc-form-section {
|
||||
@apply flex flex-col gap-3;
|
||||
}
|
||||
.nc-form-section-title {
|
||||
@apply text-sm font-bold text-gray-800;
|
||||
}
|
||||
.nc-form-section-body {
|
||||
@apply flex flex-col gap-3;
|
||||
}
|
||||
|
||||
.nc-connection-json-editor {
|
||||
@apply min-h-[300px] max-h-[600px];
|
||||
resize: vertical;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label > label.ant-form-item-required:after) {
|
||||
@apply content-['*'] inline-block text-inherit text-red-500 ml-1;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
&.ant-form-item-has-error {
|
||||
&:not(:has(.ant-input-password)) .ant-input {
|
||||
&:not(:hover):not(:focus):not(:disabled) {
|
||||
@apply shadow-default;
|
||||
}
|
||||
&:hover:not(:focus):not(:disabled) {
|
||||
@apply shadow-hover;
|
||||
}
|
||||
&:focus {
|
||||
@apply shadow-error ring-0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-number,
|
||||
.ant-input-affix-wrapper.ant-input-password {
|
||||
&:not(:hover):not(:focus-within):not(:disabled) {
|
||||
@apply shadow-default;
|
||||
}
|
||||
&:hover:not(:focus-within):not(:disabled) {
|
||||
@apply shadow-hover;
|
||||
}
|
||||
&:focus-within {
|
||||
@apply shadow-error ring-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(.ant-form-item-has-error) {
|
||||
&:not(:has(.ant-input-password)) .ant-input {
|
||||
&:not(:hover):not(:focus):not(:disabled) {
|
||||
@apply shadow-default border-gray-200;
|
||||
}
|
||||
&:hover:not(:focus):not(:disabled) {
|
||||
@apply border-gray-200 shadow-hover;
|
||||
}
|
||||
&:focus {
|
||||
@apply shadow-selected ring-0;
|
||||
}
|
||||
}
|
||||
.ant-input-number,
|
||||
.ant-input-affix-wrapper.ant-input-password {
|
||||
&:not(:hover):not(:focus-within):not(:disabled) {
|
||||
@apply shadow-default border-gray-200;
|
||||
}
|
||||
&:hover:not(:focus-within):not(:disabled) {
|
||||
@apply border-gray-200 shadow-hover;
|
||||
}
|
||||
&:focus-within {
|
||||
@apply shadow-selected ring-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-row:not(.ant-form-item)) {
|
||||
@apply !-mx-1.5;
|
||||
& > .ant-col {
|
||||
@apply !px-1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.nc-edit-or-add-integration-left-panel {
|
||||
&:has(.nc-nocodb-connection-details-placeholder) {
|
||||
@apply bg-nc-bg-gray-extralight;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user