Browse Source

Merge pull request #6616 from nocodb/nc-fix/redirection-issue

Miscellaneous changes
pull/6623/head
Pranav C 11 months ago committed by GitHub
parent
commit
3041e66560
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      packages/nc-gui/assets/nc-icons/owner.svg
  2. 12
      packages/nc-gui/components/account/Profile.vue
  3. 20
      packages/nc-gui/components/account/ResetPassword.vue
  4. 4
      packages/nc-gui/components/account/SignupSettings.vue
  5. 26
      packages/nc-gui/components/account/Token.vue
  6. 30
      packages/nc-gui/components/account/UserList.vue
  7. 24
      packages/nc-gui/components/account/UsersModal.vue
  8. 6
      packages/nc-gui/components/api-client/Headers.vue
  9. 6
      packages/nc-gui/components/api-client/Params.vue
  10. 6
      packages/nc-gui/components/cell/attachment/index.vue
  11. 36
      packages/nc-gui/components/dlg/share-and-collaborate/ShareBase.vue
  12. 13
      packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue
  13. 2
      packages/nc-gui/components/erd/TableNode.vue
  14. 2
      packages/nc-gui/components/general/ShareProject.vue
  15. 5
      packages/nc-gui/components/smartsheet/Kanban.vue
  16. 2
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  17. 4
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  18. 9
      packages/nc-gui/components/smartsheet/details/Erd.vue
  19. 11
      packages/nc-gui/components/smartsheet/details/Fields.vue
  20. 2
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue
  21. 2
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  22. 1
      packages/nc-gui/composables/useGlobal/types.ts
  23. 1
      packages/nc-gui/lib/types.ts
  24. 16
      packages/nc-gui/middleware/auth.global.ts
  25. 10
      packages/nc-gui/plugins/a.i18n.ts
  26. 10
      packages/nc-gui/plugins/api.ts
  27. 10
      packages/nc-gui/plugins/state.ts
  28. 88
      packages/nc-gui/utils/colorsUtils.ts
  29. 1
      packages/nocodb/src/controllers/bulk-data-alias.controller.ts
  30. 19
      packages/nocodb/src/helpers/catchError.ts
  31. 36
      packages/nocodb/src/services/shared-bases.service.ts

9
packages/nc-gui/assets/nc-icons/owner.svg

@ -1,9 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_18_1042" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<rect width="16" height="16" fill="#D9D9D9"/>
<mask id="mask0_18_1022" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
<rect width="16" height="16" fill="currentColor"/>
</mask>
<g mask="url(#mask0_18_1042)">
<path d="M6.66665 8.00002C5.93331 8.00002 5.30554 7.73891 4.78331 7.21669C4.26109 6.69447 3.99998 6.06669 3.99998 5.33335C3.99998 4.60002 4.26109 3.97224 4.78331 3.45002C5.30554 2.9278 5.93331 2.66669 6.66665 2.66669C7.39998 2.66669 8.02776 2.9278 8.54998 3.45002C9.0722 3.97224 9.33331 4.60002 9.33331 5.33335C9.33331 6.06669 9.0722 6.69447 8.54998 7.21669C8.02776 7.73891 7.39998 8.00002 6.66665 8.00002ZM1.33331 12V11.4667C1.33331 11.1 1.42776 10.7556 1.61665 10.4334C1.80554 10.1111 2.06665 9.86669 2.39998 9.70002C2.96665 9.41113 3.60554 9.16669 4.31665 8.96669C4.71274 8.85529 4.93609 8.67502 5.79165 8.67502H6.02498C6.09165 8.67502 6.15831 8.68613 6.22498 8.70835C5.99998 9.00002 5.33331 9.66669 4.99998 10C4.99998 10 4.8376 10.2057 4.54165 10.3C3.91387 10.5 3.39998 10.7 2.99998 10.9C2.89998 10.9556 2.81942 11.0334 2.75831 11.1334C2.6972 11.2334 2.66665 11.3445 2.66665 11.4667V12H4.99998C5.33331 12.3334 5.33331 12.3334 5.66665 12.6917C5.90596 12.949 6.19998 13.1334 6.33331 13.3334H2.66665C2.29998 13.3334 1.98609 13.2028 1.72498 12.9417C1.46387 12.6806 1.33331 12.3667 1.33331 12ZM6.66665 6.66669C7.03331 6.66669 7.3472 6.53613 7.60831 6.27502C7.86942 6.01391 7.99998 5.70002 7.99998 5.33335C7.99998 4.96669 7.86942 4.6528 7.60831 4.39169C7.3472 4.13058 7.03331 4.00002 6.66665 4.00002C6.29998 4.00002 5.98609 4.13058 5.72498 4.39169C5.46387 4.6528 5.33331 4.96669 5.33331 5.33335C5.33331 5.70002 5.46387 6.01391 5.72498 6.27502C5.98609 6.53613 6.29998 6.66669 6.66665 6.66669Z" fill="currentColor" stroke="none"/>
<path d="M9.99998 12.6667C10.6555 12.6667 11.2639 12.5167 11.825 12.2167C12.3861 11.9167 12.8333 11.5111 13.1666 11C12.8333 10.4889 12.3861 10.0833 11.825 9.78333C11.2639 9.48333 10.6555 9.33333 9.99998 9.33333C9.34442 9.33333 8.73609 9.48333 8.17498 9.78333C7.61387 10.0833 7.16665 10.4889 6.83331 11C7.16665 11.5111 7.61387 11.9167 8.17498 12.2167C8.73609 12.5167 9.34442 12.6667 9.99998 12.6667ZM9.99998 14C8.93331 14 7.98054 13.7194 7.14165 13.1583C6.30276 12.5972 5.69998 11.8778 5.33331 11C5.69998 10.1222 6.30276 9.40278 7.14165 8.84167C7.98054 8.28056 8.93331 8 9.99998 8C11.0666 8 12.0194 8.28056 12.8583 8.84167C13.6972 9.40278 14.3 10.1222 14.6666 11C14.3 11.8778 13.6972 12.5972 12.8583 13.1583C12.0194 13.7194 11.0666 14 9.99998 14ZM9.99998 12C9.7222 12 9.48609 11.9028 9.29165 11.7083C9.0972 11.5139 8.99998 11.2778 8.99998 11C8.99998 10.7222 9.0972 10.4861 9.29165 10.2917C9.48609 10.0972 9.7222 10 9.99998 10C10.2778 10 10.5139 10.0972 10.7083 10.2917C10.9028 10.4861 11 10.7222 11 11C11 11.2778 10.9028 11.5139 10.7083 11.7083C10.5139 11.9028 10.2778 12 9.99998 12Z" fill="currentColor" stroke="none"/>
<g mask="url(#mask0_18_1022)">
<path d="M7.99999 9.20002C7.33332 9.20002 6.76665 8.96668 6.29999 8.50002C5.83332 8.03335 5.59999 7.46668 5.59999 6.80002C5.59999 6.13335 5.83332 5.56668 6.29999 5.10002C6.76665 4.63335 7.33332 4.40002 7.99999 4.40002C8.66665 4.40002 9.23332 4.63335 9.69999 5.10002C10.1667 5.56668 10.4 6.13335 10.4 6.80002C10.4 7.46668 10.1667 8.03335 9.69999 8.50002C9.23332 8.96668 8.66665 9.20002 7.99999 9.20002ZM7.99999 8.00002C8.33332 8.00002 8.61665 7.88335 8.84999 7.65002C9.08332 7.41668 9.19999 7.13335 9.19999 6.80002C9.19999 6.46668 9.08332 6.18335 8.84999 5.95002C8.61665 5.71668 8.33332 5.60002 7.99999 5.60002C7.66665 5.60002 7.38332 5.71668 7.14999 5.95002C6.91665 6.18335 6.79999 6.46668 6.79999 6.80002C6.79999 7.13335 6.91665 7.41668 7.14999 7.65002C7.38332 7.88335 7.66665 8.00002 7.99999 8.00002ZM7.99999 2.88335L3.99999 4.41668V7.41668C3.99999 8.00557 4.08332 8.57779 4.24999 9.13335C4.41665 9.6889 4.64443 10.2056 4.93332 10.6833C5.4111 10.4611 5.90832 10.2917 6.42499 10.175C6.94165 10.0583 7.46665 10 7.99999 10C8.53332 10 9.05832 10.0583 9.57499 10.175C10.0917 10.2917 10.5889 10.4611 11.0667 10.6833C11.3555 10.2056 11.5833 9.6889 11.75 9.13335C11.9167 8.57779 12 8.00557 12 7.41668V4.41668L7.99999 2.88335ZM7.99999 11.2C7.59999 11.2 7.20554 11.2389 6.81665 11.3167C6.42777 11.3945 6.04999 11.5111 5.68332 11.6667C6.00554 12.0111 6.3611 12.3111 6.74999 12.5667C7.13888 12.8222 7.55554 13.0167 7.99999 13.15C8.44443 13.0167 8.8611 12.8222 9.24999 12.5667C9.63888 12.3111 9.99443 12.0111 10.3167 11.6667C9.94999 11.5111 9.57221 11.3945 9.18332 11.3167C8.79443 11.2389 8.39999 11.2 7.99999 11.2ZM7.99999 14.3333C7.93332 14.3333 7.86665 14.3306 7.79999 14.325C7.73332 14.3195 7.67221 14.3056 7.61665 14.2833C6.1611 13.8056 4.99443 12.9222 4.11665 11.6333C3.23888 10.3445 2.79999 8.93891 2.79999 7.41668V4.41668C2.79999 4.16113 2.86943 3.93335 3.00832 3.73335C3.14721 3.53335 3.33332 3.38891 3.56665 3.30002L7.56665 1.76668C7.7111 1.71113 7.85554 1.68335 7.99999 1.68335C8.14443 1.68335 8.28888 1.71113 8.43332 1.76668L12.4333 3.30002C12.6667 3.38891 12.8528 3.53335 12.9917 3.73335C13.1305 3.93335 13.2 4.16113 13.2 4.41668V7.41668C13.2 8.93891 12.7611 10.3445 11.8833 11.6333C11.0055 12.9222 9.83888 13.8056 8.38332 14.2833C8.32777 14.3056 8.26665 14.3195 8.19999 14.325C8.13332 14.3306 8.06665 14.3333 7.99999 14.3333Z" fill="currentColor" stroke="none"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

12
packages/nc-gui/components/account/Profile.vue

@ -63,10 +63,10 @@ const onValidate = async (_: any, valid: boolean) => {
<template>
<div class="flex flex-col items-center">
<div class="flex flex-col w-150">
<div class="flex font-bold text-xl">{{ $t('labels.profile') }}</div>
<div class="flex font-bold text-xl" data-rec="true">{{ $t('labels.profile') }}</div>
<div class="mt-5 flex flex-col border-1 rounded-2xl border-gray-200 p-6 gap-y-2">
<div class="flex font-medium text-base">{{ $t('labels.accountDetails') }}</div>
<div class="flex text-gray-500">{{ $t('labels.controlAppearance') }}</div>
<div class="flex font-medium text-base" data-rec="true">{{ $t('labels.accountDetails') }}</div>
<div class="flex text-gray-500" data-rec="true">{{ $t('labels.controlAppearance') }}</div>
<div class="flex flex-row mt-4">
<div class="flex h-20 mt-1.5">
<GeneralUserIcon size="xlarge" :email="user?.email" />
@ -81,7 +81,7 @@ const onValidate = async (_: any, valid: boolean) => {
@finish="onSubmit"
@validate="onValidate"
>
<div class="text-gray-800 mb-1.5">{{ $t('general.name') }}</div>
<div class="text-gray-800 mb-1.5" data-rec="true">{{ $t('general.name') }}</div>
<a-form-item name="title" :rules="formRules.title">
<a-input
v-model:value="form.title"
@ -90,7 +90,7 @@ const onValidate = async (_: any, valid: boolean) => {
data-testid="nc-account-settings-rename-input"
/>
</a-form-item>
<div class="text-gray-800 mb-1.5">{{ $t('labels.accountEmailID') }}</div>
<div class="text-gray-800 mb-1.5" data-rec="true">{{ $t('labels.accountEmailID') }}</div>
<a-input
v-model:value="email"
class="w-full !rounded-md !py-1.5"
@ -98,7 +98,7 @@ const onValidate = async (_: any, valid: boolean) => {
disabled
data-testid="nc-account-settings-email-input"
/>
<div class="flex flex-row w-full justify-end mt-8">
<div class="flex flex-row w-full justify-end mt-8" data-rec="true">
<NcButton
type="primary"
html-type="submit"

20
packages/nc-gui/components/account/ResetPassword.vue

@ -72,14 +72,19 @@ const resetError = () => {
>
<Transition name="layout">
<div v-if="error" class="mx-auto mb-4 bg-red-500 text-white rounded-lg w-3/4 p-1">
<div data-testid="nc-user-settings-form__error" class="flex items-center gap-2 justify-center">
<div data-testid="nc-user-settings-form__error" class="flex items-center gap-2 justify-center" data-rec="true">
<MaterialSymbolsWarning />
{{ error }}
</div>
</div>
</Transition>
<a-form-item :label="$t('placeholder.password.current')" name="currentPassword" :rules="formRules.currentPassword">
<a-form-item
:label="$t('placeholder.password.current')"
data-rec="true"
name="currentPassword"
:rules="formRules.currentPassword"
>
<a-input-password
v-model:value="form.currentPassword"
data-testid="nc-user-settings-form__current-password"
@ -90,7 +95,7 @@ const resetError = () => {
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.new')" name="password" :rules="formRules.password">
<a-form-item :label="$t('placeholder.password.new')" data-rec="true" name="password" :rules="formRules.password">
<a-input-password
v-model:value="form.password"
data-testid="nc-user-settings-form__new-password"
@ -101,7 +106,12 @@ const resetError = () => {
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.confirm')" name="passwordRepeat" :rules="formRules.passwordRepeat">
<a-form-item
:label="$t('placeholder.password.confirm')"
data-rec="true"
name="passwordRepeat"
:rules="formRules.passwordRepeat"
>
<a-input-password
v-model:value="form.passwordRepeat"
data-testid="nc-user-settings-form__new-password-repeat"
@ -120,7 +130,7 @@ const resetError = () => {
type="primary"
html-type="submit"
>
<div class="flex justify-center items-center gap-2">
<div class="flex justify-center items-center gap-2" data-rec="true">
<component :is="iconMap.passwordChange" />
{{ $t('activity.changePwd') }}
</div>

4
packages/nc-gui/components/account/SignupSettings.vue

@ -42,7 +42,9 @@ loadSettings()
@change="saveSettings"
/>
</a-form-item>
{{ $t('labels.inviteOnlySignup') }}
<span data-rec="true">
{{ $t('labels.inviteOnlySignup') }}
</span>
</div>
</div>
</template>

26
packages/nc-gui/components/account/Token.vue

@ -163,7 +163,7 @@ const handleCancel = () => {
<div class="h-full pt-2">
<div class="max-w-202 mx-auto px-4 h-full" data-testid="nc-token-list">
<div class="py-2 flex gap-4 items-baseline justify-between">
<h6 class="text-2xl text-left font-bold">{{ $t('title.apiTokens') }}</h6>
<h6 class="text-2xl text-left font-bold" data-rec="true">{{ $t('title.apiTokens') }}</h6>
<NcTooltip :disabled="!(isEeUI && tokens.length)">
<template #title>{{ $t('labels.tokenLimit') }}</template>
<NcButton
@ -175,23 +175,29 @@ const handleCancel = () => {
tooltip="bottom"
@click="showNewTokenModal = true"
>
<span class="hidden md:block">
<span class="hidden md:block" data-rec="true">
{{ $t('title.addNewToken') }}
</span>
<span class="flex items-center justify-center md:hidden">
<span class="flex items-center justify-center md:hidden" data-rec="true">
<component :is="iconMap.plus" />
</span>
</NcButton>
</NcTooltip>
</div>
<span>{{ $t('msg.apiTokenCreate') }}</span>
<span data-rec="true">{{ $t('msg.apiTokenCreate') }}</span>
<div class="mt-5 h-[calc(100%-13rem)]">
<div class="h-full w-full !overflow-hidden rounded-md">
<div class="flex w-full pl-5 bg-gray-50 border-1 rounded-t-md">
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9">{{ $t('title.tokenName') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('title.creator') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-3/9 text-start">{{ $t('labels.token') }}</span>
<span class="py-3.5 pl-19 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('labels.actions') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9" data-rec="true">{{ $t('title.tokenName') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9 text-start" data-rec="true">{{
$t('title.creator')
}}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-3/9 text-start" data-rec="true">{{
$t('labels.token')
}}</span>
<span class="py-3.5 pl-19 text-gray-500 font-medium text-3.5 w-2/9 text-start" data-rec="true">{{
$t('labels.actions')
}}</span>
</div>
<div class="nc-scrollbar-md !overflow-y-auto flex flex-col h-[calc(100%-5rem)]">
<div v-if="showNewTokenModal">
@ -212,7 +218,9 @@ const handleCancel = () => {
data-testid="nc-token-input"
@press-enter="generateToken"
/>
<span v-if="!isValidTokenName" class="text-red-500 text-xs font-light mt-1.5 ml-1">{{ errorMessage }} </span>
<span v-if="!isValidTokenName" class="text-red-500 text-xs font-light mt-1.5 ml-1" data-rec="true"
>{{ errorMessage }}
</span>
</div>
<div class="flex gap-2 justify-start">
<NcButton v-if="!isLoading" type="secondary" size="small" @click="handleCancel">

30
packages/nc-gui/components/account/UserList.vue

@ -155,7 +155,7 @@ const openDeleteModal = (user: UserType) => {
<template>
<div data-testid="nc-super-user-list" class="h-full">
<div class="max-w-195 mx-auto h-full">
<div class="text-2xl text-left font-weight-bold mb-4">{{ $t('title.userManagement') }}</div>
<div class="text-2xl text-left font-weight-bold mb-4" data-rec="true">{{ $t('title.userManagement') }}</div>
<div class="py-2 flex gap-4 items-center justify-between">
<a-input v-model:value="searchText" class="!max-w-90 !rounded-md" placeholder="Search members" @change="loadUsers()">
<template #prefix>
@ -165,7 +165,7 @@ const openDeleteModal = (user: UserType) => {
<div class="flex gap-3 items-center justify-center">
<component :is="iconMap.reload" class="cursor-pointer" @click="loadUsers(currentPage, currentLimit)" />
<NcButton data-testid="nc-super-user-invite" size="small" type="primary" @click="openInviteModal">
<div class="flex items-center gap-1">
<div class="flex items-center gap-1" data-rec="true">
<component :is="iconMap.plus" />
{{ $t('activity.inviteUser') }}
</div>
@ -174,9 +174,11 @@ const openDeleteModal = (user: UserType) => {
</div>
<div class="w-full rounded-md max-w-250 h-[calc(100%-12rem)] rounded-md overflow-hidden mt-5">
<div class="flex w-full bg-gray-50 border-1 rounded-t-md">
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-2/3 text-start pl-6">{{ $t('labels.email') }}</div>
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start">{{ $t('objects.role') }}</div>
<div class="flex py-3.5 text-gray-500 font-medium text-3.5 w-28 justify-end mr-4">
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-2/3 text-start pl-6" data-rec="true">
{{ $t('labels.email') }}
</div>
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start" data-rec="true">{{ $t('objects.role') }}</div>
<div class="flex py-3.5 text-gray-500 font-medium text-3.5 w-28 justify-end mr-4" data-rec="true">
{{ $t('labels.action') }}
</div>
</div>
@ -203,7 +205,9 @@ const openDeleteModal = (user: UserType) => {
</GeneralTruncateText>
</div>
<div class="text-3.5 text-start w-1/3">
<div v-if="el?.roles?.includes('super')" class="font-weight-bold">{{ $t('labels.superAdmin') }}</div>
<div v-if="el?.roles?.includes('super')" class="font-weight-bold" data-rec="true">
{{ $t('labels.superAdmin') }}
</div>
<NcSelect
v-else
v-model:value="el.roles"
@ -216,8 +220,8 @@ const openDeleteModal = (user: UserType) => {
:value="OrgUserRoles.CREATOR"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgCreator') }}
</span>
</a-select-option>
@ -227,8 +231,8 @@ const openDeleteModal = (user: UserType) => {
:value="OrgUserRoles.VIEWER"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgViewer') }}
</span>
</a-select-option>
@ -254,11 +258,11 @@ const openDeleteModal = (user: UserType) => {
<!-- Resend invite Email -->
<NcMenuItem @click="resendInvite(el)">
<component :is="iconMap.email" class="flex text-gray-600" />
<div>{{ $t('activity.resendInvite') }}</div>
<div data-rec="true">{{ $t('activity.resendInvite') }}</div>
</NcMenuItem>
<NcMenuItem @click="copyInviteUrl(el)">
<component :is="iconMap.copy" class="flex text-gray-600" />
<div>{{ $t('activity.copyInviteURL') }}</div>
<div data-rec="true">{{ $t('activity.copyInviteURL') }}</div>
</NcMenuItem>
<NcMenuItem @click="copyPasswordResetUrl(el)">
<component :is="iconMap.copy" class="flex text-gray-600" />
@ -266,7 +270,7 @@ const openDeleteModal = (user: UserType) => {
</NcMenuItem>
</template>
<NcDivider v-if="!el.roles?.includes('super')" />
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click="openDeleteModal(el)">
<NcMenuItem data-rec="true" class="!text-red-500 !hover:bg-red-50" @click="openDeleteModal(el)">
<MaterialSymbolsDeleteOutlineRounded />
{{ $t('general.remove') }} {{ $t('objects.user') }}
</NcMenuItem>

24
packages/nc-gui/components/account/UsersModal.vue

@ -110,7 +110,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
>
<div class="flex flex-col">
<div class="flex flex-row justify-between items-center pb-1.5 mb-2 border-b-1 w-full">
<a-typography-title class="select-none" :level="4"> {{ $t('activity.inviteUser') }}</a-typography-title>
<a-typography-title class="select-none" :level="4" data-rec="true"> {{ $t('activity.inviteUser') }}</a-typography-title>
<a-button type="text" class="!rounded-md mr-1 -mt-1.5" @click="emit('closed')">
<template #icon>
@ -124,13 +124,13 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div class="flex flex-col mt-1 pb-5">
<div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]">
<component :is="iconMap.account" />
<div class="text-xs ml-0.5 mt-0.5">{{ $t('activity.copyInviteURL') }}</div>
<div class="text-xs ml-0.5 mt-0.5" data-rec="true">{{ $t('activity.copyInviteURL') }}</div>
</div>
<a-alert class="!mt-2" type="success" show-icon>
<template #message>
<div class="flex flex-row justify-between items-center py-1">
<div class="flex pl-2 text-green-700 text-xs">
<div class="flex pl-2 text-green-700 text-xs" data-rec="true">
{{ inviteUrl }}
</div>
@ -143,7 +143,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</template>
</a-alert>
<div class="flex text-xs text-gray-500 mt-2 justify-start ml-2">
<div class="flex text-xs text-gray-500 mt-2 justify-start ml-2" data-rec="true">
{{ $t('msg.info.userInviteNoSMTP') }}
{{ usersData.invitationToken && usersData.emails }}
</div>
@ -153,7 +153,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div class="flex flex-row justify-center items-center space-x-0.5">
<MaterialSymbolsSendOutline class="flex mx-auto text-gray-600 h-[0.8rem]" />
<div class="text-xs text-gray-600">{{ $t('activity.inviteMore') }}</div>
<div class="text-xs text-gray-600" data-rec="true">{{ $t('activity.inviteMore') }}</div>
</div>
</a-button>
</div>
@ -177,7 +177,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
name="emails"
:rules="[{ required: true, message: 'Please input email' }]"
>
<div class="ml-1 mb-1 text-xs text-gray-500">{{ $t('datatype.Email') }}:</div>
<div class="ml-1 mb-1 text-xs text-gray-500" data-rec="true">{{ $t('datatype.Email') }}:</div>
<a-input
:ref="emailInput"
@ -191,7 +191,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div class="flex flex-col w-2/4">
<a-form-item name="role" :rules="[{ required: true, message: 'Role required' }]">
<div class="ml-1 mb-1 text-xs text-gray-500">{{ $t('labels.selectUserRole') }}</div>
<div class="ml-1 mb-1 text-xs text-gray-500" data-rec="true">{{ $t('labels.selectUserRole') }}</div>
<a-select v-model:value="usersData.role" class="nc-user-roles" dropdown-class-name="nc-dropdown-user-role">
<a-select-option
@ -199,8 +199,8 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
:value="OrgUserRoles.CREATOR"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgCreator') }}
</span>
</a-select-option>
@ -210,8 +210,8 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
:value="OrgUserRoles.VIEWER"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
<div data-rec="true">{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal" data-rec="true">
{{ $t('msg.info.roles.orgViewer') }}
</span>
</a-select-option>
@ -224,7 +224,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<a-button type="primary" class="!rounded-md" html-type="submit">
<div class="flex flex-row justify-center items-center space-x-1.5">
<MaterialSymbolsSendOutline class="flex h-[0.8rem]" />
<div>{{ $t('activity.invite') }}</div>
<div data-rec="true">{{ $t('activity.invite') }}</div>
</div>
</a-button>
</div>

6
packages/nc-gui/components/api-client/Headers.vue

@ -66,10 +66,10 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
<tr>
<th></th>
<th>
<div class="text-left font-normal ml-2">{{ $t('labels.headerName') }}</div>
<div class="text-left font-normal ml-2" data-rec="true">{{ $t('labels.headerName') }}</div>
</th>
<th>
<div class="text-left font-normal ml-2">{{ $t('placeholder.value') }}</div>
<div class="text-left font-normal ml-2" data-rec="true">{{ $t('placeholder.value') }}</div>
</th>
<th class="w-8"></th>
</tr>
@ -124,7 +124,7 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
<td :colspan="12" class="">
<NcButton size="small" type="secondary" @click="addHeaderRow">
<div class="flex flex-row items-center gap-x-1">
<div>{{ $t('labels.addHeader') }}</div>
<div data-rec="true">{{ $t('labels.addHeader') }}</div>
<component :is="iconMap.plus" class="flex mx-auto" />
</div>
</NcButton>

6
packages/nc-gui/components/api-client/Params.vue

@ -24,11 +24,11 @@ const deleteParamRow = (i: number) => {
<thead class="h-8">
<tr>
<th>
<div class="text-left font-normal ml-2">{{ $t('title.parameterName') }}</div>
<div class="text-left font-normal ml-2" data-rec="true">{{ $t('title.parameterName') }}</div>
</th>
<th>
<div class="text-left font-normal ml-2">{{ $t('placeholder.value') }}</div>
<div class="text-left font-normal ml-2" data-rec="true">{{ $t('placeholder.value') }}</div>
</th>
<th class="w-8">
@ -69,7 +69,7 @@ const deleteParamRow = (i: number) => {
<td :colspan="12" class="">
<NcButton size="small" type="secondary" @click="addParamRow">
<div class="flex flex-row items-center gap-x-1">
<div>{{ $t('activity.addParameter') }}</div>
<div data-rec="true">{{ $t('activity.addParameter') }}</div>
<component :is="iconMap.plus" class="flex mx-auto" />
</div>
</NcButton>

6
packages/nc-gui/components/cell/attachment/index.vue

@ -188,6 +188,7 @@ const onExpand = () => {
v-model="isOverDropZone"
inline
:target="currentCellRef"
data-rec="true"
class="nc-attachment-cell-dropzone text-white text-lg ring ring-accent ring-opacity-100 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl"
>
<MaterialSymbolsFileCopyOutline class="text-accent" />
@ -205,7 +206,9 @@ const onExpand = () => {
<component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />
<NcTooltip placement="bottom">
<template #title>{{ $t('activity.attachmentDrop') }} </template>
<template #title
><span data-rec="true">{{ $t('activity.attachmentDrop') }} </span></template
>
<div v-if="active || !visibleItems.length || (isForm && visibleItems.length)" class="flex items-center gap-1">
<MaterialSymbolsAttachFile
@ -213,6 +216,7 @@ const onExpand = () => {
/>
<div
v-if="!visibleItems.length"
data-rec="true"
class="group-hover:text-primary text-gray-500 dark:text-gray-200 dark:group-hover:!text-white text-xs"
>
{{ $t('activity.addFiles') }}

36
packages/nc-gui/components/dlg/share-and-collaborate/ShareBase.vue

@ -1,5 +1,15 @@
<script setup lang="ts">
import { extractSdkResponseErrorMsg, message, onMounted, storeToRefs, useBase, useDashboard, useNuxtApp } from '#imports'
import {
extractSdkResponseErrorMsg,
message,
onMounted,
storeToRefs,
useBase,
useDashboard,
useGlobal,
useNuxtApp,
useWorkspace,
} from '#imports'
interface ShareBase {
uuid?: string
@ -20,9 +30,23 @@ const sharedBase = ref<null | ShareBase>(null)
const { base } = storeToRefs(useBase())
const url = computed(() =>
sharedBase.value && sharedBase.value.uuid ? `${dashboardUrl.value}#/base/${sharedBase.value.uuid}` : '',
)
const { getBaseUrl, appInfo } = useGlobal()
const workspaceStore = useWorkspace()
const url = computed(() => {
if (!sharedBase.value || !sharedBase.value.uuid) return ''
// get base url for workspace
const baseUrl = getBaseUrl(workspaceStore.activeWorkspaceId)
let dashboardUrl1 = dashboardUrl.value
if (baseUrl) {
dashboardUrl1 = `${baseUrl}${appInfo.value?.dashboardPath}`
}
return encodeURI(`${dashboardUrl1}#/base/${sharedBase.value.uuid}`)
})
const loadBase = async () => {
try {
@ -50,6 +74,8 @@ const createShareBase = async (role = ShareBaseRole.Viewer) => {
sharedBase.value = res ?? {}
sharedBase.value!.role = role
base.value.uuid = res.uuid
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@ -63,6 +89,8 @@ const disableSharedBase = async () => {
await $api.base.sharedBaseDisable(base.value.id)
sharedBase.value = null
base.value.uuid = undefined
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}

13
packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue

@ -6,6 +6,7 @@ import { useMetas } from '#imports'
const { view: _view, $api } = useSmartsheetStoreOrThrow()
const { $e } = useNuxtApp()
const { getBaseUrl, appInfo } = useGlobal()
const { dashboardUrl } = useDashboard()
@ -13,6 +14,8 @@ const viewStore = useViewsStore()
const { metas } = useMetas()
const workspaceStore = useWorkspace()
const isUpdating = ref({
public: false,
password: false,
@ -162,7 +165,15 @@ function sharedViewUrl() {
viewType = 'view'
}
return encodeURI(`${dashboardUrl?.value}#/nc/${viewType}/${activeView.value.uuid}`)
// get base url for workspace
const baseUrl = getBaseUrl(workspaceStore.activeWorkspaceId)
let dashboardUrl1 = dashboardUrl.value
if (baseUrl) {
dashboardUrl1 = `${baseUrl}${appInfo.value?.dashboardPath}`
}
return encodeURI(`${dashboardUrl1}#/nc/${viewType}/${activeView.value.uuid}`)
}
const toggleViewShare = async () => {

2
packages/nc-gui/components/erd/TableNode.vue

@ -32,7 +32,7 @@ const hasColumns = computed(() => data.pkAndFkColumns.length || data.nonPkColumn
const nonPkColumns = computed(() =>
data.nonPkColumns
// Removed MM system column from the table node
.filter((col) => !(col.system && isLinksOrLTAR(col) && /nc_.*___nc_m2m_.*/.test(col.title!))),
.filter((col) => !(col.system && isLinksOrLTAR(col) && /.*_nc_m2m_.*/.test(col.title!))),
)
watch(

2
packages/nc-gui/components/general/ShareProject.vue

@ -8,7 +8,7 @@ interface Props {
const { disabled, isViewToolbar } = defineProps<Props>()
const { isMobileMode } = useGlobal()
const { isMobileMode, getMainUrl } = useGlobal()
const { visibility, showShareModal } = storeToRefs(useShare())

5
packages/nc-gui/components/smartsheet/Kanban.vue

@ -1,6 +1,6 @@
<script lang="ts" setup>
import Draggable from 'vuedraggable'
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import {
ActiveViewInj,
FieldsInj,
@ -16,7 +16,6 @@ import {
iconMap,
inject,
isImage,
isLTAR,
onBeforeUnmount,
provide,
useAttachment,
@ -573,7 +572,7 @@ const getRowId = (row: RowType) => {
</div>
</template>
<template v-for="(attachment, index) in attachments(record)">
<template v-for="attachment in attachments(record)">
<LazyCellAttachmentImage
v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)"
:key="attachment.path"

2
packages/nc-gui/components/smartsheet/VirtualCell.vue

@ -51,8 +51,6 @@ const isForm = inject(IsFormInj, ref(false))
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
emit('navigate', dir)

4
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -3,7 +3,7 @@ import Draggable from 'vuedraggable'
import { UITypes } from 'nocodb-sdk'
import InfiniteLoading from 'v3-infinite-loading'
import { IsKanbanInj, enumColor, iconMap, onMounted, useColumnCreateStoreOrThrow, useVModel, watch } from '#imports'
import { IsKanbanInj, enumColor, iconMap, onMounted, useColumnCreateStoreOrThrow, useVModel } from '#imports'
interface Option {
color: string
@ -21,7 +21,7 @@ const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, validateInfos, isMysql, isPg } = useColumnCreateStoreOrThrow()
const { setAdditionalValidations, validateInfos } = useColumnCreateStoreOrThrow()
// const { base } = storeToRefs(useBase())

9
packages/nc-gui/components/smartsheet/details/Erd.vue

@ -23,6 +23,13 @@ const indicator = h(LoadingOutlined, {
<a-spin size="large" :indicator="indicator" />
</div>
<LazyErdView v-else :table="activeTable" :source-id="activeTable?.source_id" :show-all-columns="false" />
<Suspense v-else>
<LazyErdView :table="activeTable" :source-id="activeTable?.source_id" :show-all-columns="false" />
<template #fallback>
<div class="h-full w-full flex flex-col justify-center items-center mt-28">
<a-spin size="large" :indicator="indicator" />
</div>
</template>
</Suspense>
</div>
</template>

11
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -43,6 +43,8 @@ const moveOps = ref<moveOp[]>([])
const visibilityOps = ref<fieldsVisibilityOps[]>([])
const fieldsListWrapperDomRef = ref<HTMLElement>()
const {
fields: viewFields,
toggleFieldVisibility,
@ -192,6 +194,13 @@ const addField = (field?: TableExplorerColumn, before = false) => {
setFieldMoveHook(field, before)
}
changeField({})
// Scroll to the bottom of the list for new field add
setTimeout(() => {
if (!field && !before && fieldsListWrapperDomRef.value) {
fieldsListWrapperDomRef.value.scrollTop = fieldsListWrapperDomRef.value.scrollHeight
}
}, 100)
}
const displayColumn = computed(() => {
@ -626,7 +635,7 @@ onMounted(async () => {
</div>
</div>
<div class="flex flex-row rounded-lg border-1 border-gray-200">
<div class="nc-scrollbar-md !overflow-auto w-full flex-grow-1 nc-fields-height">
<div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto w-full flex-grow-1 nc-fields-height">
<Draggable v-model="fields" item-key="id" @change="onMove($event)">
<template #item="{ element: field }">
<div

2
packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue

@ -3,6 +3,7 @@ import {
ActiveViewInj,
IsLockedInj,
computed,
iconMap,
inject,
ref,
useGlobal,
@ -10,7 +11,6 @@ import {
useSmartsheetStoreOrThrow,
useViewFilters,
watch,
iconMap,
} from '#imports'
const isLocked = inject(IsLockedInj, ref(false))

2
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -346,7 +346,7 @@ useMenuCloseOnEsc(open)
<div class="pt-0.25 w-full bg-gray-50"></div>
</div>
<div class="flex flex-col py-1 nc-scrollbar-md max-h-[47.5vh] pr-5">
<div class="flex flex-col my-1.5 nc-scrollbar-md max-h-[47.5vh] pr-5">
<div class="nc-fields-list">
<div
v-if="!fields?.filter((el) => el.title.toLowerCase().includes(filterQuery.toLowerCase())).length"

1
packages/nc-gui/composables/useGlobal/types.ts

@ -78,6 +78,7 @@ export interface Actions {
viewId?: string
}) => void
getBaseUrl: (workspaceId: string) => string | undefined
getMainUrl: (workspaceId: string) => string | undefined
}
export type ReadonlyState = Readonly<Pick<State, 'token' | 'user'>> & Omit<State, 'token' | 'user'>

1
packages/nc-gui/lib/types.ts

@ -125,6 +125,7 @@ type NcProject = BaseType & {
temp_title?: string
edit?: boolean
starred?: boolean
uuid?: string
}
interface UndoRedoAction {

16
packages/nc-gui/middleware/auth.global.ts

@ -1,6 +1,7 @@
import type { Api } from 'nocodb-sdk'
import type { Actions } from '~/composables/useGlobal/types'
import { defineNuxtRouteMiddleware, extractSdkResponseErrorMsg, message, navigateTo, useApi, useGlobal, useRoles } from '#imports'
import { defineNuxtRouteMiddleware, message, navigateTo, useApi, useGlobal, useRoles } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
/**
* Global auth middleware
@ -46,7 +47,9 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
}
/** if user isn't signed in and google auth is enabled, try to check if sign-in data is present */
if (!state.signedIn.value && state.appInfo.value.googleAuthEnabled) await tryGoogleAuth(api, state.signIn)
if (!state.signedIn.value && state.appInfo.value.googleAuthEnabled) {
await tryGoogleAuth(api, state.signIn)
}
/** if public allow all visitors */
if (to.meta.public) return
@ -54,13 +57,18 @@ export default defineNuxtRouteMiddleware(async (to, from) => {
/** if shared base allow without validating */
if (to.params.typeOrId === 'base') return
/** if auth is required or unspecified (same as required) and user is not signed in, redirect to signin page */
/** if auth is required or unspecified (same `as required) and user is not signed in, redirect to signin page */
if ((to.meta.requiresAuth || typeof to.meta.requiresAuth === 'undefined') && !state.signedIn.value) {
/** If this is the first usern navigate to signup page directly */
if (state.appInfo.value.firstUser) {
const query = to.fullPath !== '/' && to.fullPath.match(/^\/(?!\?)/) ? { continueAfterSignIn: to.fullPath } : {}
if (query.continueAfterSignIn) {
localStorage.setItem('continueAfterSignIn', query.continueAfterSignIn)
}
return navigateTo({
path: '/signup',
query: to.fullPath !== '/' && to.fullPath.match(/^\/(?!\?)/) ? { continueAfterSignIn: to.fullPath } : {},
query,
})
}

10
packages/nc-gui/plugins/a.i18n.ts

@ -1,6 +1,6 @@
import { createI18n } from 'vue-i18n'
import { isClient } from '@vueuse/core'
import { LanguageAlias, applyLanguageDirection, defineNuxtPlugin, isRtlLang, nextTick } from '#imports'
import { LanguageAlias, applyLanguageDirection, defineNuxtPlugin, isEeUI, isRtlLang, nextTick } from '#imports'
import type { Language, NocoI18n } from '#imports'
let globalI18n: NocoI18n
@ -43,10 +43,16 @@ export async function loadLocaleMessages(
return nextTick()
}
export default defineNuxtPlugin(async (nuxtApp) => {
const i18nPlugin = defineNuxtPlugin(async (nuxtApp) => {
globalI18n = await createI18nPlugin()
nuxtApp.vueApp.i18n = globalI18n
nuxtApp.vueApp.use(globalI18n)
})
const defaultExport = isEeUI ? defineNuxtPlugin(async () => {}) : i18nPlugin
export { i18nPlugin }
export default defaultExport

10
packages/nc-gui/plugins/api.ts

@ -1,6 +1,12 @@
import { defineNuxtPlugin, useApi } from '#imports'
import { defineNuxtPlugin, isEeUI, useApi } from '#imports'
export default defineNuxtPlugin((nuxtApp) => {
const apiPlugin = defineNuxtPlugin((nuxtApp) => {
/** injects a global api instance */
nuxtApp.provide('api', useApi().api)
})
const defaultExport = isEeUI ? defineNuxtPlugin(async () => {}) : apiPlugin
export { apiPlugin }
export default defaultExport

10
packages/nc-gui/plugins/state.ts

@ -1,4 +1,4 @@
import { Language, LanguageAlias, defineNuxtPlugin, useApi, useGlobal } from '#imports'
import { Language, LanguageAlias, defineNuxtPlugin, isEeUI, useApi, useGlobal } from '#imports'
import { loadLocaleMessages, setI18nLanguage } from '~/plugins/a.i18n'
/**
@ -13,7 +13,7 @@ import { loadLocaleMessages, setI18nLanguage } from '~/plugins/a.i18n'
* console.log($state.lang.value) // 'en'
* ```
*/
export default defineNuxtPlugin(async () => {
const statePlugin = defineNuxtPlugin(async () => {
const state = useGlobal()
const { api } = useApi({ useGlobalInstance: true })
@ -35,3 +35,9 @@ export default defineNuxtPlugin(async () => {
console.error(e)
}
})
const defaultExport = isEeUI ? defineNuxtPlugin(async () => {}) : statePlugin
export { statePlugin }
export default defaultExport

88
packages/nc-gui/utils/colorsUtils.ts

@ -113,85 +113,85 @@ export const baseThemeColors = [
const designSystem = {
light: [
'#EBF0FF',
'#D6E0FF',
'#ADC2FF',
'#85A3FF',
'#5C85FF',
// '#EBF0FF',
// '#D6E0FF',
// '#ADC2FF',
// '#85A3FF',
// '#5C85FF',
'#3366FF',
'#2952CC',
'#1F3D99',
'#142966',
'#0A1433',
'#FCFCFC',
'#F9F9FA',
'#F4F4F5',
'#E7E7E9',
'#D5D5D9',
'#9AA2AF',
'#6A7184',
// '#FCFCFC',
// '#F9F9FA',
// '#F4F4F5',
// '#E7E7E9',
// '#D5D5D9',
// '#9AA2AF',
// '#6A7184',
'#4A5268',
'#374151',
'#1F293A',
'#101015',
'#FFF2F1',
'#FFDBD9',
'#FFB7B2',
'#FF928C',
'#FF6E65',
'#FF4A3F',
// '#FFF2F1',
// '#FFDBD9',
// '#FFB7B2',
// '#FF928C',
// '#FF6E65',
// '#FF4A3F',
'#E8463C',
'#CB3F36',
'#B23830',
'#7D2721',
'#FFEEFB',
'#FED8F4',
'#FEB0E8',
'#FD89DD',
'#FD61D1',
// '#FFEEFB',
// '#FED8F4',
// '#FEB0E8',
// '#FD89DD',
// '#FD61D1',
'#FC3AC6',
'#CA2E9E',
'#972377',
'#65174F',
'#320C28',
'#FFF5EF',
'#FEE6D6',
'#FDCDAD',
'#FCB483',
'#FB9B5A',
// '#FFF5EF',
// '#FEE6D6',
// '#FDCDAD',
// '#FCB483',
// '#FB9B5A',
'#FA8231',
'#E1752C',
'#C86827',
'#964E1D',
'#4B270F',
'#F3ECFA',
'#E5D4F5',
'#CBA8EB',
'#B17DE1',
'#9751D7',
// '#F3ECFA',
// '#E5D4F5',
// '#CBA8EB',
// '#B17DE1',
// '#9751D7',
'#7D26CD',
'#641EA4',
'#4B177B',
'#320F52',
'#190829',
'#EDF9FF',
'#D7F2FF',
'#AFE5FF',
'#86D9FF',
'#5ECCFF',
// '#EDF9FF',
// '#D7F2FF',
// '#AFE5FF',
// '#86D9FF',
// '#5ECCFF',
'#36BFFF',
'#2B99CC',
'#207399',
'#164C66',
'#0B2633',
'#fffbf2',
'#fff0d1',
'#fee5b0',
'#fdd889',
'#fdcb61',
'#fcbe3a',
// '#fffbf2',
// '#fff0d1',
// '#fee5b0',
// '#fdd889',
// '#fdcb61',
// '#fcbe3a',
'#ca982e',
'#977223',
'#654c17',

1
packages/nocodb/src/controllers/bulk-data-alias.controller.ts

@ -10,7 +10,6 @@ import {
Response,
UseGuards,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { GlobalGuard } from '~/guards/global/global.guard';
import { BulkDataAliasService } from '~/services/bulk-data-alias.service';
import { Acl } from '~/middlewares/extract-ids/extract-ids.middleware';

19
packages/nocodb/src/helpers/catchError.ts

@ -390,8 +390,20 @@ export default function (
try {
return await requestHandler(req, res, next);
} catch (e) {
// todo: error log
console.log(requestHandler.name ? `${requestHandler.name} ::` : '', e);
// skip unnecessary error logging
if (
process.env.NC_ENABLE_ALL_API_ERROR_LOGGING === 'true' ||
!(
e instanceof BadRequest ||
e instanceof AjvError ||
e instanceof Unauthorized ||
e instanceof Forbidden ||
e instanceof NotFound ||
e instanceof NotImplemented ||
e instanceof UnprocessableEntity
)
)
console.log(requestHandler.name ? `${requestHandler.name} ::` : '', e);
const dbError = extractDBError(e);
@ -418,7 +430,8 @@ export default function (
} else if (e instanceof NotAllowed) {
return res.status(405).json({ msg: e.message });
}
next(e);
// if some other error occurs then send 500 and a generic message
res.status(500).json({ msg: 'Internal server error' });
}
};
}

36
packages/nocodb/src/services/shared-bases.service.ts

@ -1,6 +1,8 @@
import { Injectable } from '@nestjs/common';
import { AppEvents } from 'nocodb-sdk';
import { v4 as uuidv4 } from 'uuid';
import { ConfigService } from '@nestjs/config';
import type { AppConfig } from '~/interface/config';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import { validatePayload } from '~/helpers';
import { NcError } from '~/helpers/catchError';
@ -13,7 +15,10 @@ const config = {
@Injectable()
export class SharedBasesService {
constructor(private readonly appHooksService: AppHooksService) {}
constructor(
private readonly appHooksService: AppHooksService,
private configService: ConfigService<AppConfig>,
) {}
async createSharedBaseLink(param: {
baseId: string;
@ -42,7 +47,11 @@ export class SharedBasesService {
await Base.update(base.id, data);
data.url = `${param.siteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`;
data.url = this.getUrl({
base,
siteUrl: param.siteUrl,
});
delete data.password;
this.appHooksService.emit(AppEvents.SHARED_BASE_GENERATE_LINK, {
@ -79,7 +88,11 @@ export class SharedBasesService {
await Base.update(base.id, data);
data.url = `${param.siteUrl}${config.dashboardPath}#/nc/base/${data.uuid}`;
data.url = this.getUrl({
base,
siteUrl: param.siteUrl,
});
delete data.password;
this.appHooksService.emit(AppEvents.SHARED_BASE_GENERATE_LINK, {
link: data.url,
@ -88,6 +101,21 @@ export class SharedBasesService {
return data;
}
private getUrl({ base, siteUrl: _siteUrl }: { base: Base; siteUrl: string }) {
let siteUrl = _siteUrl;
const baseDomain = process.env.NC_BASE_HOST_NAME;
const dashboardPath = this.configService.get('dashboardPath', {
infer: true,
});
if (baseDomain) {
siteUrl = `https://${base['fk_workspace_id']}.${baseDomain}${dashboardPath}`;
}
return `${siteUrl}${config.dashboardPath}#/base/${base.uuid}`;
}
async disableSharedBaseLink(param: { baseId: string }): Promise<any> {
const base = await Base.get(param.baseId);
@ -120,7 +148,7 @@ export class SharedBasesService {
roles: base.roles,
};
if (data.uuid)
data.url = `${param.siteUrl}${config.dashboardPath}#/nc/base/${data.shared_base_id}`;
data.url = `${param.siteUrl}${config.dashboardPath}#/base/${data.shared_base_id}`;
return data;
}

Loading…
Cancel
Save