Browse Source

Merge branch 'develop' into nc-cloud/changelog

pull/9629/head
Anbarasu 1 month ago committed by GitHub
parent
commit
b11d020830
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      .github/workflows/bats-test.yml
  2. 16
      .github/workflows/ci-cd.yml
  3. 2
      .github/workflows/jest-unit-test.yml
  4. 56
      .github/workflows/playwright-test-workflow.yml
  5. 3
      .github/workflows/pre-build-for-playwright.yml
  6. 4
      .github/workflows/release-docker.yml
  7. 27
      .github/workflows/release-nightly-dev.yml
  8. 10
      .github/workflows/release-npm.yml
  9. 15
      .github/workflows/release-pr.yml
  10. 11
      .github/workflows/release-secret-cli.yml
  11. 161
      .github/workflows/release-timely-docker.yml
  12. 4
      .github/workflows/sync-to-develop.yml
  13. 9
      .github/workflows/unit-test.yml
  14. 8
      .github/workflows/update-sdk-path.yml
  15. 3
      docker-compose/1_Auto_Upstall/tests/expects/install/redis.sh
  16. 3
      docker-compose/1_Auto_Upstall/tests/expects/install/scale.sh
  17. 3
      docker-compose/1_Auto_Upstall/tests/expects/install/watchtower.sh
  18. 15
      packages/nc-gui/components/feed/Recents/Card.vue
  19. 4
      packages/nc-gui/components/feed/Recents/index.vue
  20. 13
      packages/nc-gui/components/smartsheet/form/field-settings/visibility.vue
  21. 4
      packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue
  22. 4
      packages/nc-gui/components/smartsheet/toolbar/OpenedViewAction.vue
  23. 153
      packages/nc-gui/composables/useSharedExecutionFn.ts
  24. 2
      packages/nc-gui/context/index.ts
  25. 286
      packages/nc-gui/lang/es.json
  26. 12
      packages/nc-gui/lang/vi.json
  27. 2
      packages/nc-gui/package.json
  28. 21
      packages/nc-gui/store/notification.ts
  29. 1018
      packages/nc-lib-gui/package-lock.json
  30. 1
      packages/nc-mail-templates/package.json
  31. 1
      packages/nc-secret-mgr/package.json
  32. 4712
      packages/nocodb-sdk/pnpm-lock.yaml
  33. 80
      packages/nocodb/Dockerfile.timely
  34. 10
      packages/nocodb/src/controllers/public-datas-export.controller.ts
  35. 21
      packages/nocodb/src/run/timely.ts
  36. 7
      packages/nocodb/src/services/utils.service.ts
  37. 2
      packages/nocodb/src/utils/TeleBatchProcessor.ts
  38. 1
      packages/nocodb/src/utils/tele.ts
  39. 57
      packages/nocodb/webpack.timely.config.js
  40. 33213
      pnpm-lock.yaml
  41. 12
      pnpm-workspace.yaml

5
.github/workflows/bats-test.yml

@ -4,6 +4,7 @@ on:
push:
paths:
- 'docker-compose/1_Auto_Upstall/noco.sh'
- '.github/workflows/bats-test.yml'
workflow_dispatch:
jobs:
@ -18,7 +19,7 @@ jobs:
- name: Prepare matrix for test files
id: set-matrix
run: |
BATS_FILES=$(find docker-compose/setup-script/tests -name '*.bats')
BATS_FILES=$(find docker-compose/1_Auto_Upstall/tests -name '*.bats')
MATRIX_JSON=$(echo $BATS_FILES | tr -d '\n' | jq -Rsc 'split(" ")' | tr '"' "'")
echo "matrix=$MATRIX_JSON" >> $GITHUB_OUTPUT
test:
@ -39,7 +40,7 @@ jobs:
- name: Get working directory
run: |
WORKING_DIR="$(pwd)/docker-compose/setup-script/tests"
WORKING_DIR="$(pwd)/docker-compose/1_Auto_Upstall/tests"
echo "WORKING_DIR=$WORKING_DIR" >> $GITHUB_ENV
- name: Run BATS test

16
.github/workflows/ci-cd.yml

@ -38,12 +38,12 @@ jobs:
with:
fetch-depth: 1
# enable after fixing all validation errors
# - name: Validate OpenAPI definition
# uses: char0n/swagger-editor-validate@v1
# with:
# swagger-editor-url: http://localhost/
# definition-file: packages/nocodb/src/schema/swagger.json
# enable after fixing all validation errors
# - name: Validate OpenAPI definition
# uses: char0n/swagger-editor-validate@v1
# with:
# swagger-editor-url: http://localhost/
# definition-file: packages/nocodb/src/schema/swagger.json
- name: Validate Swagger JSON
run: |
@ -63,7 +63,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Setup Node
uses: actions/setup-node@v3
with:
@ -98,7 +98,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Setup Node
uses: actions/setup-node@v3
with:

2
.github/workflows/jest-unit-test.yml

@ -31,7 +31,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Get pnpm store directory
shell: bash
timeout-minutes: 1

56
.github/workflows/playwright-test-workflow.yml

@ -4,7 +4,7 @@ on:
workflow_call:
inputs:
shard:
description: 'Shard number'
description: "Shard number"
required: true
type: string
db:
@ -27,7 +27,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Get pnpm store directory
shell: bash
run: |
@ -144,32 +144,32 @@ jobs:
working-directory: ./tests/playwright
run: E2E_DB_TYPE=${{ inputs.db }} node ./scripts/stressTestNewlyAddedTest.js
# # Quick tests (pg on sqlite shard 0 and sqlite on sqlite shard 1)
# - name: Run quick server and tests (pg)
# if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }}
# working-directory: ./packages/nocodb
# run: |
# kill -9 $(lsof -t -i:8080)
# npm run watch:run:playwright:pg:cyquick > quick_${{ inputs.shard }}_test_backend.log &
# - name: Run quick server and tests (sqlite)
# if: ${{ inputs.db == 'sqlite' && inputs.shard == '2' }}
# working-directory: ./packages/nocodb
# run: |
# kill -9 $(lsof -t -i:8080)
# npm run watch:run:playwright:quick > quick_${{ inputs.shard }}_test_backend.log &
# - name: Wait for backend for sqlite-tests
# if: ${{ inputs.db == 'sqlite' }}
# working-directory: ./tests/playwright
# run: |
# while ! curl --output /dev/null --silent --head --fail http://localhost:8080; do
# printf '.'
# sleep 2
# done
# timeout-minutes: 1
# - name: Run quick tests
# if: ${{ inputs.db == 'sqlite' }}
# working-directory: ./tests/playwright
# run: PLAYWRIGHT_HTML_REPORT=playwright-report-quick npm run test:quick
# # Quick tests (pg on sqlite shard 0 and sqlite on sqlite shard 1)
# - name: Run quick server and tests (pg)
# if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }}
# working-directory: ./packages/nocodb
# run: |
# kill -9 $(lsof -t -i:8080)
# npm run watch:run:playwright:pg:cyquick > quick_${{ inputs.shard }}_test_backend.log &
# - name: Run quick server and tests (sqlite)
# if: ${{ inputs.db == 'sqlite' && inputs.shard == '2' }}
# working-directory: ./packages/nocodb
# run: |
# kill -9 $(lsof -t -i:8080)
# npm run watch:run:playwright:quick > quick_${{ inputs.shard }}_test_backend.log &
# - name: Wait for backend for sqlite-tests
# if: ${{ inputs.db == 'sqlite' }}
# working-directory: ./tests/playwright
# run: |
# while ! curl --output /dev/null --silent --head --fail http://localhost:8080; do
# printf '.'
# sleep 2
# done
# timeout-minutes: 1
# - name: Run quick tests
# if: ${{ inputs.db == 'sqlite' }}
# working-directory: ./tests/playwright
# run: PLAYWRIGHT_HTML_REPORT=playwright-report-quick npm run test:quick
- uses: actions/upload-artifact@v3
if: ${{ inputs.db == 'sqlite' }}

3
.github/workflows/pre-build-for-playwright.yml

@ -17,7 +17,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: remove use-node-version from .npmrc
run: sed -i '/^use-node-version/d' .npmrc
- name: Get pnpm store directory
@ -47,4 +47,3 @@ jobs:
zip -r ${FILE} .output || echo "UI build directory does not exists" >&2
echo "uploading ${FILE} to http://65.21.27.147/upload/${FILE}"
time curl -T "${FILE}" http://65.21.27.147/upload/${FILE} -n

4
.github/workflows/release-docker.yml

@ -48,7 +48,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Get Docker Repository
id: get-docker-repository
run: |
@ -135,6 +135,8 @@ jobs:
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
push: true
labels: |
"service=nocodb"
tags: |
nocodb/${{ steps.get-docker-repository.outputs.DOCKER_REPOSITORY }}:${{ steps.get-docker-repository.outputs.DOCKER_BUILD_TAG }}
nocodb/${{ steps.get-docker-repository.outputs.DOCKER_REPOSITORY }}:${{ steps.get-docker-repository.outputs.DOCKER_BUILD_LATEST_TAG }}

27
.github/workflows/release-nightly-dev.yml

@ -43,29 +43,20 @@ jobs:
nightly_build_tag: ${{ steps.tag-step.outputs.NIGHTLY_BUILD_TAG }}
is_daily: ${{ steps.tag-step.outputs.IS_DAILY }}
current_version: ${{ steps.tag-step.outputs.CURRENT_VERSION }}
# Build frontend and backend and publish to npm
release-npm:
needs: set-tag
uses: ./.github/workflows/release-npm.yml
with:
tag: ${{ needs.set-tag.outputs.nightly_build_tag }}
targetEnv: 'DEV'
secrets:
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
# Build executables and publish to GitHub
release-executables:
needs: [set-tag, release-npm]
uses: ./.github/workflows/release-timely-executables.yml
with:
tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.nightly_build_tag }}
secrets:
NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}"
# release-executables:
# needs: [set-tag, release-npm]
# uses: ./.github/workflows/release-timely-executables.yml
# with:
# tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.nightly_build_tag }}
# secrets:
# NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}"
# Build docker image and push to docker hub
release-docker:
needs: [set-tag, release-npm]
uses: ./.github/workflows/release-docker.yml
needs: [set-tag]
uses: ./.github/workflows/release-timely-docker.yml
with:
currentVersion: ${{ needs.set-tag.outputs.current_version }}
tag: ${{ needs.set-tag.outputs.nightly_build_tag }}

10
.github/workflows/release-npm.yml

@ -40,7 +40,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Checkout
uses: actions/checkout@v3
with:
@ -51,7 +51,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 18.19.1
registry-url: 'https://registry.npmjs.org'
registry-url: "https://registry.npmjs.org"
- run: |
export NODE_OPTIONS="--max_old_space_size=16384"
NOCODB_SDK_PKG_NAME=nocodb-sdk
@ -79,10 +79,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
signoff: true
branch: 'release/${{ github.event.inputs.tag || inputs.tag }}'
branch: "release/${{ github.event.inputs.tag || inputs.tag }}"
delete-branch: true
title: 'Release ${{ github.event.inputs.tag || inputs.tag }}'
labels: 'Bot: Automerge'
title: "Release ${{ github.event.inputs.tag || inputs.tag }}"
labels: "Bot: Automerge"
- name: Check outputs
if: ${{ github.event.inputs.targetEnv == 'PROD' || inputs.targetEnv == 'PROD' }}
run: |

15
.github/workflows/release-pr.yml

@ -50,22 +50,11 @@ jobs:
target_tag: ${{ steps.tag-step.outputs.TARGET_TAG }}
current_version: ${{ steps.tag-step.outputs.CURRENT_VERSION }}
# Build, install, publish frontend and backend to npm
release-npm:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' && github.event.pull_request.draft == false && github.base_ref == 'develop' && github.event.action != 'closed' }}
needs: [set-tag]
uses: ./.github/workflows/release-npm.yml
with:
tag: ${{ needs.set-tag.outputs.target_tag }}
targetEnv: 'DEV'
secrets:
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
# Build docker image and push to docker hub
release-docker:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]' && github.event.pull_request.draft == false && github.base_ref == 'develop' && github.event.action != 'closed' }}
needs: [release-npm, set-tag]
uses: ./.github/workflows/release-docker.yml
needs: [set-tag]
uses: ./.github/workflows/release-timely-docker.yml
with:
currentVersion: ${{ needs.set-tag.outputs.current_version }}
tag: ${{ needs.set-tag.outputs.target_tag }}

11
.github/workflows/release-secret-cli.yml

@ -19,13 +19,13 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Setup Node 18.19.1
# Setup .npmrc file to publish to npm
uses: actions/setup-node@v3
with:
node-version: 18.19.1
registry-url: 'https://registry.npmjs.org'
registry-url: "https://registry.npmjs.org"
- name: Cache pkg modules
id: cache-pkg
@ -68,7 +68,7 @@ jobs:
with:
node-version: 16
- name : Install nocodb, other dependencies and build executables
- name: Install nocodb, other dependencies and build executables
run: |
cd ./packages/nc-secret-mgr
@ -117,7 +117,6 @@ jobs:
runs-on: macos-latest
needs: build-and-publish
steps:
- uses: actions/download-artifact@master
with:
name: ${{ github.event.inputs.tag || inputs.tag }}
@ -134,9 +133,8 @@ jobs:
path: packages/nc-secret-mgr/mac-dist
retention-days: 1
publish-mac-executables:
needs: [sign-mac-executables,build-and-publish]
needs: [sign-mac-executables, build-and-publish]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@master
@ -153,4 +151,3 @@ jobs:
overwrite: true
file_glob: true
repo_name: nocodb/nc-secret-mgr

161
.github/workflows/release-timely-docker.yml

@ -0,0 +1,161 @@
name: "Release : Docker"
on:
# Triggered manually
workflow_dispatch:
inputs:
tag:
description: "Docker image tag"
required: true
targetEnv:
description: "Target Environment"
required: true
type: choice
options:
- DEV
- PROD
# Triggered by release-nocodb.yml / release-nightly-dev.yml / release-pr.yml
workflow_call:
inputs:
tag:
description: "Docker image tag"
required: true
type: string
targetEnv:
description: "Target Environment"
required: true
type: string
isDaily:
description: "Is it triggered by daily schedule"
required: false
type: string
currentVersion:
description: "The current NocoDB version"
required: false
type: string
secrets:
DOCKERHUB_USERNAME:
required: true
DOCKERHUB_TOKEN:
required: true
jobs:
buildx:
runs-on: ubuntu-latest
env:
working-directory: ./packages/nocodb
steps:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Get Docker Repository
id: get-docker-repository
run: |
DOCKER_REPOSITORY=nocodb-daily
DOCKER_BUILD_TAG=${{ github.event.inputs.tag || inputs.tag }}
DOCKER_BUILD_LATEST_TAG=latest
if [[ "$DOCKER_BUILD_TAG" =~ "-beta." ]]; then
DOCKER_BUILD_LATEST_TAG=$(echo $DOCKER_BUILD_TAG | awk -F '-beta.' '{print $1}')-beta.latest
fi
if [[ ${{ github.event.inputs.targetEnv || inputs.targetEnv }} == 'DEV' ]]; then
if [[ ${{ github.event.inputs.currentVersion || inputs.currentVersion || 'N/A' }} != 'N/A' ]]; then
DOCKER_BUILD_TAG=${{ github.event.inputs.currentVersion || inputs.currentVersion }}-${{ github.event.inputs.tag || inputs.tag }}
fi
if [[ ${{ inputs.isDaily || 'N' }} == 'Y' ]]; then
DOCKER_REPOSITORY=${DOCKER_REPOSITORY}-daily
else
DOCKER_REPOSITORY=${DOCKER_REPOSITORY}-timely
fi
fi
echo "DOCKER_REPOSITORY=${DOCKER_REPOSITORY}" >> $GITHUB_OUTPUT
echo "DOCKER_BUILD_TAG=${DOCKER_BUILD_TAG}" >> $GITHUB_OUTPUT
echo "DOCKER_BUILD_LATEST_TAG=${DOCKER_BUILD_LATEST_TAG}" >> $GITHUB_OUTPUT
echo DOCKER_REPOSITORY: ${DOCKER_REPOSITORY}
echo DOCKER_BUILD_TAG: ${DOCKER_BUILD_TAG}
echo DOCKER_BUILD_LATEST_TAG: ${DOCKER_BUILD_LATEST_TAG}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.ref }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: 18.19.1
- name: install dependencies
run: pnpm bootstrap
- name: Build gui and sdk
run: |
pnpm bootstrap &&
cd packages/nc-gui &&
pnpm run generate
# copy build to nocodb
rsync -rvzh ./dist/ ../nocodb/docker/nc-gui/
- name: build nocodb
run: |
# build nocodb ( pack nocodb-sdk and nc-gui )
cd packages/nocodb &&
EE=true pnpm exec webpack --config webpack.timely.config.js &&
# remove bundled libraries (nocodb-sdk, knex-snowflake)
pnpm uninstall --save-prod nocodb-sdk
- name: Update version in package.json
run: |
# update package.json
cd packages/nocodb &&
jq --arg VERSION "$VERSION" '.version = $VERSION' package.json > tmp.json &&
mv tmp.json package.json
env:
VERSION: ${{ steps.get-docker-repository.outputs.DOCKER_BUILD_TAG }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.2.1
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3.2.0
with:
context: ${{ env.working-directory }}
file: ${{ env.working-directory }}/Dockerfile.timely
build-args: NC_VERSION=${{ steps.get-docker-repository.outputs.DOCKER_BUILD_TAG }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
push: true
tags: |
nocodb/${{ steps.get-docker-repository.outputs.DOCKER_REPOSITORY }}:${{ steps.get-docker-repository.outputs.DOCKER_BUILD_TAG }}
nocodb/${{ steps.get-docker-repository.outputs.DOCKER_REPOSITORY }}:${{ steps.get-docker-repository.outputs.DOCKER_BUILD_LATEST_TAG }}
# Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache

4
.github/workflows/sync-to-develop.yml

@ -1,4 +1,4 @@
name: 'Sync changes back to develop branch from master'
name: "Sync changes back to develop branch from master"
on:
# Triggered manually
@ -16,7 +16,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Checkout
uses: actions/checkout@v3
with:

9
.github/workflows/unit-test.yml

@ -5,17 +5,16 @@ name: Backend Unit Tests
on:
push:
branches: [ "develop" ]
branches: ["develop"]
paths:
- "packages/nocodb/**"
pull_request:
branches: [ "develop" ]
branches: ["develop"]
paths:
- "packages/nocodb/**"
jobs:
unit-tests:
runs-on: ubuntu-latest
strategy:
@ -27,13 +26,13 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
cache: "pnpm"
- name: remove use-node-version from .npmrc
run: sed -i '/^use-node-version/d' .npmrc
- name: install dependencies

8
.github/workflows/update-sdk-path.yml

@ -12,7 +12,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
version: 9
- name: Setup Node
uses: actions/setup-node@v3
with:
@ -32,10 +32,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
signoff: true
branch: 'bot/update-nocodb-sdk-path'
branch: "bot/update-nocodb-sdk-path"
delete-branch: true
title: 'Update nocodb-sdk to local path'
labels: 'Bot: Automerge'
title: "Update nocodb-sdk to local path"
labels: "Bot: Automerge"
- name: Check outputs
run: |
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"

3
docker-compose/1_Auto_Upstall/tests/expects/install/redis.sh

@ -18,6 +18,9 @@ send "Y\r"
expect "Choose Community or Enterprise Edition*"
send "\r"
expect "Select PostgreSQL or SQLite as your database*"
send "P\r"
expect "Do you want to enabled Redis for caching*"
send "Y\r"

3
docker-compose/1_Auto_Upstall/tests/expects/install/scale.sh

@ -18,6 +18,9 @@ send "Y\r"
expect "Choose Community or Enterprise Edition*"
send "\r"
expect "Select PostgreSQL or SQLite as your database*"
send "P\r"
expect "Do you want to enabled Redis for caching*"
send "Y\r"

3
docker-compose/1_Auto_Upstall/tests/expects/install/watchtower.sh

@ -18,6 +18,9 @@ send "Y\r"
expect "Choose Community or Enterprise Edition*"
send "\r"
expect "Select PostgreSQL or SQLite as your database*"
send "P\r"
expect "Do you want to enabled Redis for caching*"
send "\r"

15
packages/nc-gui/components/feed/Recents/Card.vue

@ -22,6 +22,7 @@ const feedIcon = {
Twitter: iconMap.twitter,
Youtube: iconMap.youtube,
Github: iconMap.githubSolid,
Cloud: iconMap.ncCloud,
}
const truncate = ref(true)
@ -32,7 +33,7 @@ const expand = () => {
truncate.value = false
$e('c:nocodb:feed:recents:expand', {
title: Title,
type: 'github',
type: source,
})
}
@ -61,14 +62,20 @@ const renderedText = computedAsync(async () => {
})
const { width } = useWindowSize()
const handleOpenUrl = (url: string) => {
if (source === 'Cloud') return
openLink(url)
}
</script>
<template>
<div class="bg-white recent-card border-gray-200 border-1 rounded-2xl max-w-[540px] xl:max-w-[640px]">
<div class="flex items-center justify-between px-5 py-4">
<div class="flex items-center gap-3">
<component :is="feedIcon[source as any]" class="w-4 h-4 stroke-transparent" />
<span class="font-weight-medium text-nc-content-gray leading-5 cursor-pointer" @click="openLink(Url)">
<component :is="feedIcon[source]" class="w-4 h-4 stroke-transparent" />
<span class="font-weight-medium text-nc-content-gray leading-5 cursor-pointer" @click="handleOpenUrl">
{{ source }}
</span>
</div>
@ -76,7 +83,7 @@ const { width } = useWindowSize()
{{ timeAgo(CreatedAt) }}
</div>
</div>
<template v-if="source === 'Github'">
<template v-if="['Github', 'Cloud'].includes(source)">
<div class="pb-5">
<LazyCellAttachmentPreviewImage
v-if="Images?.length"

4
packages/nc-gui/components/feed/Recents/index.vue

@ -37,7 +37,9 @@ onMounted(() => {
<GeneralLoader size="xlarge" />
</div>
<div v-else class="flex flex-col my-6 items-center gap-6">
<FeedRecentsCard v-for="feed in socialFeed" :key="feed.Id" :item="feed" />
<template v-for="feed in socialFeed" :key="feed.Id">
<FeedRecentsCard v-if="['Github', 'Cloud', 'Youtube'].includes(feed['Feed Source'])" :item="feed" />
</template>
</div>
</div>
</template>

13
packages/nc-gui/components/smartsheet/form/field-settings/visibility.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
import type { ColumnType } from 'nocodb-sdk'
const { visibleColumns, activeField, allViewFilters, localColumnsMapByFkColumnId } = useFormViewStoreOrThrow()
const { visibleColumns, activeField, allViewFilters, localColumns, localColumnsMapByFkColumnId } = useFormViewStoreOrThrow()
const isOpen = ref<boolean>(false)
@ -9,6 +9,17 @@ const allFilters = ref({})
provide(AllFiltersInj, allFilters)
const fieldAlias = computed(() => {
return localColumns.value.reduce((acc, field) => {
if (field?.fk_column_id && field?.label?.trim()) {
acc[field.fk_column_id] = field.label
}
return acc
}, {} as Record<string, string>)
})
provide(FieldNameAlias, fieldAlias)
const visibilityError = computed(() => {
return parseProp(activeField.value?.meta)?.visibility?.errors || {}
})

4
packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue

@ -17,6 +17,8 @@ const customColumns = toRef(restProps, 'columns')
const meta = toRef(restProps, 'meta')
const fieldNameAlias = inject(FieldNameAlias, ref({} as Record<string, string>))
const { metas } = useMetas()
const localValue = computed({
@ -110,7 +112,7 @@ const options = computed<SelectProps['options']>(() =>
})
?.map((c: ColumnType) => ({
value: c.id,
label: c.title,
label: fieldNameAlias.value[c.id!] || c.title,
icon: h(
isVirtualCol(c) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'),
{

4
packages/nc-gui/components/smartsheet/toolbar/OpenedViewAction.vue

@ -5,6 +5,8 @@ const { isMobileMode } = useGlobal()
const { isSharedBase, base } = storeToRefs(useBase())
const { sharedView } = useSharedView()
const { t } = useI18n()
const { $api, $e } = useNuxtApp()
@ -188,7 +190,7 @@ function openDeleteDialog() {
</NcTooltip>
</div>
<NcDropdown
v-else
v-else-if="!sharedView"
v-model:visible="isDropdownOpen"
class="!xs:pointer-events-none nc-actions-menu-btn nc-view-context-btn"
overlay-class-name="nc-dropdown-actions-menu"

153
packages/nc-gui/composables/useSharedExecutionFn.ts

@ -0,0 +1,153 @@
import { useStorage, useTimeoutFn } from '@vueuse/core'
interface SharedExecutionOptions {
timeout?: number // Maximum time a lock can be held before it's considered stale - default 5000ms
storageDelay?: number // Delay before reading from storage to allow for changes to propagate - default 50ms
debug?: boolean // Enable or disable debug logging
}
const tabId = `tab-${Math.random().toString(36).slice(2, 9)}`
/**
* Creates a composable that ensures a function is executed only once across all tabs
* @param key Unique key to identify the function
* @param fn Function to be executed
* @param options Optional configuration (timeout, storageDelay)
* @returns A wrapped function that ensures single execution across tabs
*/
export function useSharedExecutionFn<T>(key: string, fn: () => Promise<T> | T, options: SharedExecutionOptions = {}) {
const { timeout = 5000, storageDelay = 50, debug = false } = options
const storageResultKey = `nc-shared-execution-${key}-result`
const storageLockKey = `nc-shared-execution-${key}-lock`
const storageResultState = useStorage<{
status?: 'success' | 'error'
result?: T
error?: any
}>(storageResultKey, {})
const debugLog = (...args: any[]) => {
if (debug) console.log(`[${tabId}]`, ...args)
}
debugLog(`Tab initialized with ID: ${tabId}`)
const getLock = (): { timestamp: number; tabId: string } | null => {
try {
return JSON.parse(localStorage.getItem(storageLockKey) || 'null')
} catch (error) {
debugLog(`Error reading lock:`, error)
return null
}
}
const acquireLock = async (): Promise<boolean> => {
let currentLock = getLock()
const now = Date.now()
if (!currentLock) {
localStorage.setItem(storageLockKey, JSON.stringify({ timestamp: now, tabId }))
// Allow storage updates to propagate - which will determine strictness of lock
await new Promise((resolve) => setTimeout(resolve, storageDelay))
currentLock = getLock()
if (currentLock?.tabId === tabId) {
debugLog(`Lock acquired successfully`)
return true
}
debugLog(`Lock acquired by ${currentLock?.tabId}`)
return false
}
const lockIsStale = now - currentLock.timestamp > timeout
if (lockIsStale) {
localStorage.setItem(storageLockKey, JSON.stringify({ timestamp: now, tabId }))
// Allow storage updates to propagate - which will determine strictness of lock
await new Promise((resolve) => setTimeout(resolve, storageDelay))
currentLock = getLock()
if (currentLock?.tabId === tabId) {
debugLog(`Stale lock acquired successfully`)
return true
}
debugLog(`Stale lock acquired by ${currentLock?.tabId}`)
return false
}
debugLog(`Lock is held by ${currentLock?.tabId}`)
return false
}
const releaseLock = (): void => {
const currentLock = getLock()
if (currentLock?.tabId === tabId) {
debugLog(`Releasing lock.`)
localStorage.removeItem(storageLockKey)
}
}
const sharedExecutionFn = async (): Promise<T> => {
debugLog(`sharedExecutionFn called`)
if (!(await acquireLock())) {
const currentLock = getLock()
return new Promise((resolve, reject) => {
let timedOut = false
const { start: startTimeout, stop: stopTimeout } = useTimeoutFn(
() => {
timedOut = true
localStorage.removeItem(storageLockKey)
reject(new Error(`Timeout waiting for result on key ${key}`))
},
currentLock?.timestamp ? timeout - (Date.now() - currentLock.timestamp) : timeout,
)
startTimeout()
if (storageResultState.value.status) {
storageResultState.value = { ...storageResultState.value, status: undefined }
}
until(() => storageResultState.value)
.toMatch((v) => v.status === 'success' || v.status === 'error')
.then((res) => {
if (timedOut) return
stopTimeout()
const { result, error } = res
result ? resolve(result) : reject(error)
})
})
}
try {
storageResultState.value = { ...storageResultState.value, status: undefined }
const result = await fn()
storageResultState.value = { status: 'success', result }
return result
} catch (error) {
storageResultState.value = { status: 'error', error }
throw error
} finally {
releaseLock()
debugLog(`Function execution completed (success or failure).`)
}
}
// Make sure to release lock on page unload
onBeforeMount(() => {
window.addEventListener('beforeunload', releaseLock)
})
// Remove listener on component unmount to avoid leaks
onBeforeUnmount(() => {
window.removeEventListener('beforeunload', releaseLock)
})
return sharedExecutionFn
}

2
packages/nc-gui/context/index.ts

@ -87,3 +87,5 @@ export const ActiveSourceInj: InjectionKey<
> = Symbol('active-source-injection')
export const IsToolbarIconMode: InjectionKey<ComputedRef<boolean>> = Symbol('toolbar-icon-mode-injection')
export const FieldNameAlias: InjectionKey<ComputedRef<Record<string, string>> | Ref<Record<string, string>>> =
Symbol('field-name-alias')

286
packages/nc-gui/lang/es.json

@ -39,67 +39,67 @@
}
},
"aggregation": {
"sum": "",
"sum": "Sum",
"count": "Cuenta",
"min": "Min",
"max": "Máximo",
"avg": "Media",
"min": "Mín",
"max": "Máx.",
"avg": "Prom",
"median": "Mediana",
"std_dev": "Desviación estándar",
"histogram": "Histograma",
"range": "Rango",
"percent_empty": "Vacío",
"percent_filled": "Lleno",
"percent_unique": "Único",
"count_unique": "Único",
"percent_filled": "Rellenado",
"percent_unique": "Unico",
"count_unique": "Unico",
"count_empty": "Vacío",
"count_filled": "Lleno",
"count_filled": "Rellenado",
"earliest_date": "Fecha mínima",
"latest_date": "Fecha máxima",
"date_range": "Rango de fechas",
"month_range": "Rango de meses",
"checked": "Marcado",
"unchecked": "Desmarcado",
"percent_checked": "Marcado",
"percent_unchecked": "Desmarcado",
"date_range": "Rango",
"month_range": "Rango",
"checked": "Comprobado",
"unchecked": "No seleccionado",
"percent_checked": "Comprobado",
"percent_unchecked": "No seleccionado",
"attachment_size": "Tamaño",
"none": "Ninguno"
"none": "Nada"
},
"aggregation_type": {
"sum": "Suma",
"count": "Cuenta",
"min": "Min",
"max": "Máximo",
"avg": "Media",
"min": "Mín.",
"max": "Máx.",
"avg": "Promedio",
"median": "Mediana",
"std_dev": "Desviación estándar",
"histogram": "Histograma",
"range": "Rango",
"percent_empty": "Porcentaje vacío",
"percent_filled": "Porcentaje lleno",
"percent_filled": "Porcentaje cubierto",
"percent_unique": "Porcentaje único",
"count_unique": "Único",
"count_unique": "Unico",
"count_empty": "Vacío",
"count_filled": "Lleno",
"count_filled": "Rellenado",
"earliest_date": "Fecha más temprana",
"latest_date": "Fecha más reciente",
"latest_date": "Última Fecha",
"date_range": "Rango de fechas",
"month_range": "Rango de meses",
"checked": "Marcado",
"unchecked": "Desmarcado",
"percent_checked": "Porcentaje Marcado",
"percent_unchecked": "Porcentaje Desmarcado",
"attachment_size": "Tamaño del Adjunto",
"month_range": "Rango de mes",
"checked": "Comprobado",
"unchecked": "No seleccionado",
"percent_checked": "Porcentaje comprobado",
"percent_unchecked": "Porcentaje Sin comprobar",
"attachment_size": "Tamaño del archivo adjunto",
"none": "Ninguno"
},
"general": {
"scripts": "Comandos",
"configure": "Configurar",
"switch": "Cambiar",
"on": "Encendido",
"onMultiple": "En Múltiple",
"switch": "Interruptor",
"on": "Activado",
"onMultiple": "En múltiples",
"manual": "Manual",
"trigger": "Activar",
"trigger": "Disparador",
"addLookupField": "Add {count} lookup fields",
"style": "Estilo",
"label": "Etiqueta",
@ -152,7 +152,7 @@
"code": "Código",
"duplicate": "Duplicar",
"duplicating": "Duplicando",
"duplicateEntity": "Duplicar {entity}",
"duplicateEntity": "Duplicado {entity}",
"activate": "Activar",
"action": "Acción",
"insert": "Insertar",
@ -167,7 +167,7 @@
"rename": "Renombrar",
"renameEntity": "Renombrar {entity}",
"reload": "Recargar",
"refresh": "Refrescar",
"refresh": "Actualizar",
"reset": "Reiniciar",
"install": "Instalar",
"show": "Mostrar",
@ -225,7 +225,7 @@
"betaNote": "Esta función se encuentra actualmente en fase beta.",
"moreInfo": "Puede encontrar más información aquí",
"logs": "Registros",
"groupingField": "Agrupar por campo",
"groupingField": "Apilar por campo",
"insertAfter": "Insertar a la derecha",
"insertBefore": "Insertar a la izquierda",
"insertAbove": "Insertar arriba",
@ -237,7 +237,7 @@
"move": "Mover",
"geoDataField": "Campo GeoDatos",
"type": "Tipo",
"subType": "Subtipo",
"subType": "Sub-tipo",
"name": "Nombre",
"changes": "Cambios",
"new": "Nuevo",
@ -277,10 +277,10 @@
"appearance": "Apariencia",
"now": "Ahora",
"set": "Establecer",
"format": "Formato",
"format": "Plantilla",
"colour": "Color",
"use": "Usar",
"stack": "Pila",
"stack": "Apilado",
"ipAddress": "Dirección IP",
"integration": "Integración",
"integrations": "Integraciones",
@ -364,8 +364,8 @@
"googleCalendar": "Google Calendar",
"googleDrive": "Google Drive",
"googleSheets": "Google Sheets",
"hubspot": "Hubspot",
"serviceHub": "Centro de Servicio",
"hubspot": "HubSpot",
"serviceHub": "Centro de servicios",
"jira": "Jira",
"mailchimp": "Mailchimp",
"microsoftAccess": "Microsoft Access",
@ -373,7 +373,7 @@
"microsoftOutlook": "Microsoft Outlook",
"miro": "Miro",
"salesforce": "Salesforce",
"serviceCloud": "Nube de Servicio",
"serviceCloud": "Servicios en la Nube",
"snowflake": "Snowflake",
"stripe": "Stripe",
"surveyMonkey": "SurveyMonkey",
@ -386,10 +386,10 @@
"postgreSQL": "PostgreSQL",
"sqlite": "SQLite",
"dataBricks": "DataBricks",
"mssqlServer": "Servidor MSSQL",
"mssqlServer": "MSSQL Server",
"oracle": "Oracle",
"telegram": "Telegram",
"whatsapp": "Whatsapp",
"whatsapp": "WhatsApp",
"gmail": "Gmail",
"pipedrive": "Pipedrive",
"microsoftDynamics365": "Microsoft Dynamics 365",
@ -397,7 +397,7 @@
"greenhouse": "Greenhouse",
"lever": "Lever",
"bitbucket": "BitBucket",
"quickbooks": "Quickbooks",
"quickbooks": "QuickBooks",
"intercom": "Intercom",
"dropbox": "Dropbox",
"openai": "OpenAI",
@ -411,24 +411,24 @@
"allIntegrationsSubtitle": "",
"databaseSubtitle": "Conecte y gestione sin problemas sus bases de datos con NocoDB.",
"communication": "Comunicación",
"communicationSubtitle": "Reciba notificaciones de cambios y agilice la comunicación del equipo con NocoDB.",
"projectManagement": "Gestión de proyectos",
"projectManagementSubtitle": "Mejore los flujos de trabajo de proyectos y la gestión de tareas con NocoDB.",
"communicationSubtitle": "Reciba notificaciones sobre los cambios y agilice la comunicación del equipo con NocoDB.",
"projectManagement": "Administración de proyectos",
"projectManagementSubtitle": "Mejore los flujos de trabajo de los proyectos y la gestión de tareas con NocoDB.",
"crm": "CRM",
"crmSubtitle": "Optimice la gestión de relaciones con clientes a través de integraciones con NocoDB.",
"crmSubtitle": "Optimice la gestión de las relaciones con los clientes mediante las integraciones de NocoDB.",
"marketing": "Marketing",
"marketingSubtitle": "Impulse sus esfuerzos de marketing con las potentes integraciones de NocoDB.",
"ats": "ATS",
"atsSubtitle": "Optimice su sistema de seguimiento de candidatos con NocoDB.",
"atsSubtitle": "Agilice su sistema de seguimiento de candidatos con NocoDB.",
"development": "Desarrollo",
"developmentSubtitle": "Acelere los procesos de desarrollo con integraciones de NocoDB.",
"developmentSubtitle": "Acelere los procesos de desarrollo con las integraciones de NocoDB.",
"finance": "Finanzas",
"financeSubtitle": "Simplifique las operaciones financieras y la gestión de datos con NocoDB.",
"ticketing": "Ticketing",
"ticketingSubtitle": "Gestione y rastree tickets de soporte de manera eficiente con NocoDB.",
"ticketing": "Sistema de tickets",
"ticketingSubtitle": "Gestione y haga un seguimiento eficiente de los tickets de soporte con NocoDB.",
"storageSubtitle": "Integre y organice sus soluciones de almacenamiento sin problemas con NocoDB.",
"others": "Otros",
"othersSubtitle": "Descubra integraciones adicionales versátiles para mejorar su experiencia con NocoDB.",
"othersSubtitle": "Descubra otras integraciones versátiles para mejorar su experiencia con NocoDB.",
"ai": "IA",
"spreadSheet": "Hoja de cálculo",
"spreadSheetSubtitle": "Conecte y gestione sus hojas de cálculo con NocoDB."
@ -488,13 +488,13 @@
},
"title": {
"searchWebhook": "Buscar webhook",
"webcam": "Cámara Web",
"uploadViaUrl": "Subir a través de URL",
"webcam": "Cámara web",
"uploadViaUrl": "Subir por URL",
"localFiles": "Local Files",
"renameBase": "Renombrar Base",
"renameWorkspace": "Renombrar Espacio de Trabajo",
"renamingWorkspace": "Renombrando Espacio de Trabajo",
"renamingBase": "Renombrando Base",
"renameBase": "Renombrar base",
"renameWorkspace": "Renombrar Espacio de trabajo",
"renamingWorkspace": "Renombrando el espacio de trabajo",
"renamingBase": "Renombrando la base",
"sso": "Autenticación (SSO)",
"docs": "Documentos",
"forum": "Foro",
@ -523,7 +523,7 @@
"removeFile": "Eliminar archivo",
"hasMany": "Tiene muchos",
"manyToMany": "Muchos a Muchos",
"oneToOne": "Uno a Uno",
"oneToOne": "Uno a uno",
"virtualRelation": "Relación virtual",
"linkMore": "Enlace más",
"linkMoreRecords": "Vincular más registros",
@ -617,7 +617,7 @@
"noOptionsFound": "No se encontraron opciones",
"surveyFormSubmitConfirmMsg": "¿Está seguro de que desea enviar este formulario?",
"noResultsMatchedYourSearch": "Your search did not yield any matching results.",
"looksLikeThisStackIsEmpty": "Parece que esta pila no tiene registros",
"looksLikeThisStackIsEmpty": "Parece que esta pila no tiene ningún registro",
"fromScratch": "Desde cero",
"fromFileAndExternalSources": "Desde archivos y fuentes externas",
"directlyInRealTime": "Directamente en tiempo real",
@ -630,12 +630,12 @@
"configuration": "Configuración",
"setup": "Configuración",
"configLabel": "Configurar {label}",
"switchToProd": "Cambiar a una base de datos lista para producción",
"sharedBase": "Shared Base",
"fieldID": "ID de Campo",
"addDescription": "Agregar descripción",
"switchToProd": "Cambie a una aplicación base de datos lista para producción",
"sharedBase": "Base compartida",
"fieldID": "ID de campo",
"addDescription": "Añadir descripción",
"editDescription": "Editar descripción",
"urlFormula": "Fórmula de URL",
"urlFormula": "Fórmula URL",
"selectIcon": "ninguno",
"selectAWebhook": "--seleccionar un webhook--",
"openUrl": "Abrir URL",
@ -644,27 +644,27 @@
"defaultView": "Vista por defecto",
"recordInsert": "Insertar registro",
"recordUpdate": "Actualizar registro",
"recordDelete": "Eliminar registro",
"supportDocs": "Docs de soporte",
"addedOn": "Añadido el",
"changeDisplayValueField": "Cambiar campo de valor de visualización",
"recordDelete": "Borrar registro",
"supportDocs": "Documentos de apoyo",
"addedOn": "Añadido él",
"changeDisplayValueField": "Cambiar el valor de campo a visualizar",
"selectYourNewTitleFor": "Select your new display value field for ",
"searchDisplayValue": "Seleccionar campo de valor de visualización",
"changeTitleField": "Cambiar campo de título",
"clearAll": "Borrar todo",
"addNewLookupHelperText1": "Los campos de búsqueda muestran datos de registros vinculados. Seleccione campos específicos de",
"addNewLookupHelperText2": " tabla para añadirlos como campos de búsqueda en esta tabla.",
"formatting": "Formato",
"selectAFormatType": "- -Seleccionar un tipo de formato (opcional)- -",
"searchDisplayValue": "Seleccione el valor de campo a visualizar",
"changeTitleField": "Cambiar el título del campo",
"clearAll": "Limpiar todo",
"addNewLookupHelperText1": "Los campos de búsqueda muestran los datos de los registros vinculados. Seleccione campos específicos de ",
"addNewLookupHelperText2": "Para agregarlos como campos de Búsqueda en esta tabla.",
"formatting": "Formateando",
"selectAFormatType": "-Seleccione un tipo de formulario (opcional)- -",
"formatType": "Tipo de formato",
"toUpload": "para subir",
"dragFilesHere": "arrastra los archivos aquí",
"browseFiles": "examinar archivos",
"clickTo": "Hacer clic para",
"allowAccessToYourCamera": "Por favor permite el acceso a tu cámara",
"toUpload": "Para cargar",
"dragFilesHere": "Arrastre los archivos aquí",
"browseFiles": "Examinar Archivos",
"clickTo": "Haga clic para",
"allowAccessToYourCamera": "Por favor, permita el acceso a su cámara",
"openFile": "Abrir archivo",
"enterValidUrl": "Introduce una URL válida para subir archivos",
"addFilesFromUrl": "Agregar archivos desde URL",
"enterValidUrl": "Introduzca una URL válida para cargar archivos",
"addFilesFromUrl": "Añadir archivos desde URL",
"uploading": "Subiendo",
"dropHere": "Soltar aquí",
"addMore": "Añadir más",
@ -674,46 +674,46 @@
"allowMetaWrite": "Allow Schema Edit",
"allowDataWrite": "Allow Data Edit",
"selectView": "Seleccionar una vista",
"connectionDetails": "Detalles de conexión de la fuente",
"connectionDetails": "Detalles de la conexión origen",
"metaSync": "Meta Sync",
"mention": "Mención",
"today": "Hoy",
"currentDate": "Fecha actual",
"workspace": "Espacio de trabajo",
"txt": "Valor de registro TXT",
"transferOwnership": "Transferir Propiedad",
"recentActivity": "Actividad Reciente",
"goToMembers": "Ir a los Miembros",
"addMember": "Añadir Miembro",
"numberOfMembers": "N.º de Miembros",
"numberOfBases": "N.º de Bases",
"numberOfRecords": "N.º de Registros",
"workspaceName": "Nombre del Espacio de Trabajo",
"workspaceWithoutOwner": "Espacio de Trabajo sin Propietarios",
"inviteUsersToWorkspace": "Invitar Usuarios al Espacio de Trabajo",
"txt": "Valor del registro TXT",
"transferOwnership": "Transferir la Propiedad",
"recentActivity": "Actividad reciente",
"goToMembers": "Ir a Miembros",
"addMember": "Añadir miembro",
"numberOfMembers": "No. Miembros",
"numberOfBases": "No. Bases",
"numberOfRecords": "No. Registros",
"workspaceName": "Nombre del espacio de trabajo",
"workspaceWithoutOwner": "Espacio de trabajo sin propietarios",
"inviteUsersToWorkspace": "Invitar usuarios al espacio de trabajo",
"selectWorkspace": "-seleccionar espacios de trabajo a los que invitar-",
"addMembersToOrganization": "Añadir Miembros a la Organización",
"memberIn": "Miembro en:",
"assignAs": "Asignar como",
"signOutUser": "Cerrar sesión del usuario",
"signOutUser": "Cerrar sesión de usuario",
"signOutUsers": "Cerrar sesión de usuarios",
"deactivateUser": "Desactivar usuario",
"deactivateUsers": "Desactivar usuarios",
"lastActive": "Último activo",
"dateAdded": "Fecha de incorporación",
"lastActive": "Última vez activo",
"dateAdded": "Fecha creación",
"uploadImage": "Subir imagen",
"organizationProfile": "Perfil de la organización",
"organizationImage": "Imagen de la organización",
"organizationName": "Nombre de la organización",
"activeDomains": "Dominios activos",
"domains": "Dominios",
"disablePublicSharing": "Deshabilitar compartición pública",
"shareSettings": "Configuración de compartir",
"disablePublicSharing": "Desactivar Compartir Público",
"shareSettings": "Compartir ajustes",
"deleteUserAndData": "Eliminar usuario y sus datos",
"userOptions": "Opciones de usuario",
"deleteThisOrganization": "Eliminar esta organización",
"dangerZone": "Zona de peligro",
"childView": "Vista hija",
"deleteThisOrganization": "Eliminar esta Organización",
"dangerZone": "Zona peligrosa",
"childView": "Vista Hijo",
"selectYear": "Seleccionar Año",
"save": "Guardar",
"cancel": "Cancelar",
@ -725,14 +725,14 @@
"newProvider": "Nuevo proveedor",
"generalSettings": "Ajustes Generales",
"adminPanel": "Panel de administración",
"moveWorkspaceToOrg": "Mover espacio de trabajo a la organización",
"moveWorkspaceToOrg": "Mover espacio de trabajo a organización",
"ssoSettings": "Ajustes SSO",
"addDomain": "Añadir dominio",
"addDomain": "Agregar dominio",
"domain": "Dominio",
"settings": "Configuración",
"settings": "Ajustes",
"workspaces": "Espacios de trabajo",
"back": "Volver",
"dashboard": "Panel de control",
"dashboard": "Dashboard",
"organizeBy": "Organizar por",
"previous": "Anterior",
"nextMonth": "Mes siguiente",
@ -754,12 +754,12 @@
"noToken": "Sin Token",
"tokenLimit": "Sólo se permite un token por usuario",
"duplicateAttachment": "Archivo con el nombre {filename} ya adjuntado",
"tableIdColon": "ID DE TABLA: {tableId}",
"tableIdColon": "ID de Tabla: {tableId}",
"viewIdColon": "VIEW ID: {viewId}",
"toAddress": "A Dirección",
"subject": "Asunto",
"body": "Cuerpo",
"commaSeparatedMobileNumber": "Número móvil separado por comas",
"commaSeparatedMobileNumber": "Número Celular separado por Comas",
"headerName": "Nombre del encabezado",
"icon": "Icono",
"max": "Máximo",
@ -788,12 +788,12 @@
"optional": "(Opcional)",
"clickToMake": "Pulsar para hacer",
"visibleForRole": "visible para el rol:",
"inUI": "en el panel de control de la interfaz de usuario",
"inUI": "En él UI del Dashboard",
"projectSettings": "Ajustes básicos",
"clickToHide": "Clic para ocultar",
"clickToDownload": "Clic para descargar",
"forRole": "para el rol",
"clickToCopyTableID": "Clic para copiar ID de Tabla",
"clickToCopyTableID": "Haga clic para copiar el ID de la tabla",
"clickToCopyViewID": "Clic para copiar View ID",
"viewMode": "Modo solo lectura",
"searchUsers": "Buscar usuarios",
@ -840,9 +840,9 @@
"databaseType": "Tipo en base de datos",
"lengthValue": "Longitud/valor",
"dbType": "Tipo de Base de Datos",
"servername": "nombre del servidor / dirección del host",
"servername": "Nombre del Servidor / Dirección del Host",
"sqliteFile": "Ruta del archivo SQLite",
"hostAddress": "Dirección del servidor",
"hostAddress": "Dirección del host",
"port": "Número de puerto",
"username": "Usuario",
"password": "Contraseña",
@ -947,7 +947,7 @@
"hasMany": "tiene muchos",
"belongsTo": "pertenece a",
"manyToMany": "tienen una relación de muchos a muchos",
"oneToOne": "tener una relación uno a uno",
"oneToOne": "Tienen una relación de uno a uno",
"extraConnectionParameters": "Parámetros de conexión adicionales",
"commentsOnly": "Sólo comentarios",
"documentation": "Documentación",
@ -964,7 +964,7 @@
"noAccess": "Sin acceso",
"restApis": "API REST",
"apis": "API",
"apiSnippet": "Fragmentos de API",
"apiSnippet": "API Snippets",
"includeData": "Incluir datos",
"includeView": "Incluir vista",
"includeWebhook": "Incluir Webhook",
@ -988,36 +988,36 @@
"appearanceSettings": "Ajustes de apariencia",
"backgroundColor": "Color de Fondo",
"hideNocodbBranding": "Ocultar marca NocoDB",
"showOnConditions": "Show on conditions",
"showOnConditions": "Mostrar en condiciones",
"showFieldOnConditionsMet": "Muestra el campo sólo cuando se cumplen las condiciones",
"limitOptions": "Limitar opciones",
"limitOptionsSubtext": "Limite las opciones visibles a los usuarios seleccionando las opciones disponibles",
"clearSelection": "Borrar selección",
"limitOptions": "Limitar las opciones",
"limitOptionsSubtext": "Limitar opciones visibles para los usuarios seleccionando las opciones disponibles",
"clearSelection": "Limpiar selección",
"displayAsProgress": "Mostrar como progreso",
"relationType": "Tipo de relación",
"showThousandsSeparator": "Mostrar separador de miles",
"signUpForFree": "Regístrate gratis",
"signUpForFree": "Regístrese gratis",
"coverImageField": "Cover image field",
"fitImage": "Ajustar imagen",
"coverImageArea": "Imagen de portada",
"syncData": "Sincronizar datos",
"syncDataModalSubtitle": "Registre los servicios en los que está interesado para recibir notificaciones cuando estén disponibles",
"syncDataModalSubtitle": "Registre los servicios que le interesan para recibir una notificación cuando estén disponibles",
"redirectToUrl": "Redirigir a URL"
},
"activity": {
"webhookDetails": "Detalles del Webhook",
"hideWeekends": "Ocultar fines de semana",
"webhookDetails": "Detalles de Webhook",
"hideWeekends": "Ocultar los fines de semana",
"renameBase": "Renombrar base",
"renameWorkspace": "Renombrar espacio de trabajo",
"deactivate": "Desactivar",
"manageUsers": "Gestionar usuarios",
"newWorkspace": "Espacio de trabajo nuevo",
"newWorkspace": "Nuevo espacio de trabajo",
"addDomain": "Añadir dominio",
"addMembers": "Agregar Miembros",
"enterEmail": "Ingrese direcciones de correo electrónico",
"inviteToBase": "Invitar a la Base",
"addMembers": "Añadir miembros",
"enterEmail": "Ingresar direcciones de correo electrónico",
"inviteToBase": "Invitar a la base",
"inviteToWorkspace": "Invitar al espacio de trabajo",
"addMember": "Agregar Miembro a la Base",
"addMember": "Añadir miembro a la base",
"noRange": "La vista del calendario requiere un rango de fechas",
"goToToday": "Ir a Hoy",
"toggleSidebar": "Alternar Barra Lateral",
@ -1230,7 +1230,7 @@
},
"kanban": {
"collapseStack": "Colapsar pila",
"collapseAll": "Colapsar todo",
"collapseAll": "Contraer todo",
"expandAll": "Expandir todo",
"renameStack": "Renombrar pila",
"deleteStack": "Borrar pila",
@ -1251,28 +1251,28 @@
"addFieldFromFormView": "Añadir Campo",
"selectAllFields": "Seleccionar todos los campos",
"preFilledFields": {
"title": "Habilitar Pre-rellenar",
"title": "Activar pre-rellenado",
"default": "Por defecto",
"locked": "Bloquear campos pre-rellenados como solo lectura",
"locked": "Bloquear los campos pre-rellenados como solo lectura",
"hidden": "Ocultar campos pre-rellenados",
"lockedFieldTooltip": "Valor pre-rellenado"
},
"getPreFilledLink": "Obtener enlace pre-rellenado",
"group": "Grupo",
"goToDocs": "Ir a Documentos",
"goToDocs": "Ir a documentos",
"addCondition": "Añadir condición",
"addConditionGroup": "Añadir grupo de condiciones"
},
"tooltip": {
"currentDateNotAvail": "Current date option not available for this data source / data type",
"privateConnection": "Habilitar para hacer esta conexión privada y oculta para otros creadores en este espacio de trabajo.",
"optionalDatabaseName": "Opcional. Usa la base de datos predeterminada \"{database}\" si se deja en blanco",
"optionalSchemaName": "Opcional. Usa el esquema predeterminado \"{schema}\" si se deja en blanco.",
"schemaChangeDisabled": "La edición del esquema está deshabilitada para esta fuente de datos.",
"privateConnection": "Active esta opción para que esta conexión sea privada y quede oculta a los demás creadores de este espacio de trabajo.",
"optionalDatabaseName": "Opcional. Utiliza la base de datos por defecto \"{database}\" si se deja en blanco.",
"optionalSchemaName": "Opcional. Utiliza el esquema por defecto \"{schema}\" si se deja en blanco.",
"schemaChangeDisabled": "La edición del esquema está desactivada para esta fuente de datos.",
"typeNotAllowed": "Este tipo de datos no está permitido.",
"dataWriteOptionDisabled": "Data editing can only be disabled when 'Schema editing' is also disabled.",
"allowMetaWrite": "Esta opción permite la modificación del esquema de la base de datos, incluyendo la adición, alteración o eliminación de tablas y columnas. Úselo con precaución, ya que los cambios pueden afectar la integridad estructural de su base de datos.",
"allowDataWrite": "Esta opción permite crear, actualizar o eliminar registros dentro de tablas de la base de datos. Ideal para administradores que necesitan cambiar datos directamente.",
"allowMetaWrite": "Esta opción permite modificar el esquema de la base de datos, incluyendo la adición, alteración o eliminación de tablas y columnas. Utilícela con precaución, ya que los cambios pueden afectar a la integridad estructural de su base de datos.",
"allowDataWrite": "Esta opción permite crear, actualizar o eliminar registros dentro de las tablas de la base de datos. Ideal para usuarios administrativos que necesitan modificar datos directamente.",
"reachedSourceLimit": "Limitado a 10 fuentes de datos por base",
"saveChanges": "Guardar cambios",
"xcDB": "Crear un nuevo proyecto",
@ -1304,9 +1304,9 @@
"changeIconColour": "Cambiar el color del icono",
"preFillFormInfo": "Generate share form URL with pre-filled field data. To get a pre-filled link, make sure you’ve filled the necessary fields in the form view builder.",
"surveyFormInfo": "Modo formulario con un campo por página",
"useFieldEditMenuToConfigFieldType": "Utiliza el menú de edición de campo para conversiones de tipo después de que el archivo sea importado",
"useFieldEditMenuToConfigFieldType": "Utilizar el menú de edición de campos para las conversiones de tipo después de importar el archivo",
"roleInheritedFromWorkspace": "Rol heredado del espacio de trabajo",
"comingSoonIntegration": "¡Próximamente! Haga clic para votar por la integración que necesita en NocoDB."
"comingSoonIntegration": "¡Próximamente! Haz clic para votar a favor de la integración que necesitas en NocoDB."
},
"placeholder": {
"searchIcons": "Buscar iconos",
@ -1321,7 +1321,7 @@
"projName": "Ingresa el nombre de proyecto",
"selectGroupField": "Seleccione un campo de agrupación",
"selectGroupFieldNotFound": "No se encuentra ningún campo de selección único. Por favor, cree uno primero.",
"selectCoverImageField": "Seleccione un campo para la imagen de portada",
"selectCoverImageField": "Seleccione un campo de imagen de portada",
"selectGeoField": "Seleccione un campo de datos geográficos",
"notSelected": "-no seleccionado-",
"selectGeoFieldNotFound": "No se encuentra ningún campo de datos geográficos. Por favor, cree uno primero.",
@ -1357,7 +1357,7 @@
"key": "Clave",
"createTable": "¡Crea tu primera tabla!",
"createTableLabel": "Create your first table effortlessly, from scratch, or by importing/connecting to an external database.",
"noTokenCreated": "No se crearon tokens API",
"noTokenCreated": "No se han creado tokens de API",
"noTokenCreatedLabel": "Begin by creating API tokens to unlock advanced functionalities.",
"inviteYourTeam": "Invita a tu equipo",
"inviteYourTeamLabel": "Streamline collaboration and productivity with your team – start by inviting them to join your workspace.",

12
packages/nc-gui/lang/vi.json

@ -304,10 +304,10 @@
"workspaces": "Các không gian làm việc",
"project": "Dự định",
"projects": "Dự án.",
"table": "Bàn",
"tables": "Những cái bàn",
"field": "Cánh đồng",
"fields": "Lĩnh vực",
"table": "Bảng",
"tables": "Bảng",
"field": "Trường dữ liệu",
"fields": "Trường dữ liệu",
"column": "Cột",
"columns": "Cột",
"cell": "Ô",
@ -815,7 +815,7 @@
"accountEmailID": "ID Email Tài khoản",
"backToWorkspace": "Quay lại Workspace",
"untitledToken": "Token chưa có tiêu đề",
"tableName": "Tên bảng.",
"tableName": "Tên bảng",
"dashboardName": "Tên bảng điều khiển",
"createView": "Create View",
"creatingView": "Tạo Chế độ xem",
@ -1132,7 +1132,7 @@
"createTable": "Create New Table",
"createDashboard": "Tạo bảng chính",
"createWorkspace": "Tạo không gian làm việc",
"refreshTable": "Bàn làm mới",
"refreshTable": "Làm mới bảng",
"renameTable": "Đổi tên bảng",
"renameLayout": "Đổi tên bố cục",
"deleteTable": "Xóa bảng",

2
packages/nc-gui/package.json

@ -1,6 +1,7 @@
{
"name": "nc-gui",
"private": true,
"packageManager": "pnpm@9.6.0",
"description": "NocoDB Frontend",
"author": {
"name": "NocoDB",
@ -70,6 +71,7 @@
"html-entities": "^2.5.2",
"httpsnippet": "^2.0.0",
"inflection": "^1.13.4",
"isomorphic-dompurify": "^1.13.0",
"jsbarcode": "^3.11.6",
"jsep": "^1.3.8",
"jwt-decode": "^3.1.2",

21
packages/nc-gui/store/notification.ts

@ -1,9 +1,10 @@
import { defineStore } from 'pinia'
import type { NotificationType } from 'nocodb-sdk'
import axios, { type CancelTokenSource } from 'axios'
import { CancelToken } from 'axios'
import { useStorage } from '@vueuse/core'
const CancelToken = axios.CancelToken
export const useNotification = defineStore('notificationStore', () => {
const isTokenRefreshInProgress = useStorage(TOKEN_REFRESH_PROGRESS_KEY, false)
@ -27,16 +28,24 @@ export const useNotification = defineStore('notificationStore', () => {
let cancelTokenSource: CancelTokenSource | null
const pollNotifications = async () => {
try {
if (!token.value) return
const pollNotificationsApiCall = async () => {
// set up cancel token for polling to cancel when token changes/token is removed
cancelTokenSource = CancelToken.source()
const res = await api.notification.poll({
return await api.notification.poll({
cancelToken: cancelTokenSource.token,
})
}
const sharedExecutionPollNotificationsApiCall = useSharedExecutionFn('notification', pollNotificationsApiCall, {
timeout: 30000,
})
const pollNotifications = async () => {
try {
if (!token.value) return
const res = await sharedExecutionPollNotificationsApiCall()
if (res.status === 'success') {
if (notificationTab.value === 'unread') {

1018
packages/nc-lib-gui/package-lock.json generated

File diff suppressed because it is too large Load Diff

1
packages/nc-mail-templates/package.json

@ -2,6 +2,7 @@
"name": "nc-mail-templates",
"version": "1.0.0",
"description": "",
"packageManager": "pnpm@9.6.0",
"main": "index.js",
"type": "module",
"scripts": {

1
packages/nc-secret-mgr/package.json

@ -2,6 +2,7 @@
"name": "nc-secret-mgr",
"version": "0.0.1",
"description": "",
"packageManager": "pnpm@9.6.0",
"main": "dist/cli.js",
"bin": "dist/cli.js",
"scripts": {

4712
packages/nocodb-sdk/pnpm-lock.yaml

File diff suppressed because it is too large Load Diff

80
packages/nocodb/Dockerfile.timely

@ -0,0 +1,80 @@
# syntax=docker/dockerfile:1
###########
# Litestream Builder
###########
FROM golang:alpine3.19 as lt-builder
WORKDIR /usr/src/
RUN apk add --no-cache git make musl-dev gcc
# build litestream
RUN git clone https://github.com/benbjohnson/litestream.git litestream
RUN cd litestream && go install ./cmd/litestream
RUN cp $GOPATH/bin/litestream /usr/src/lt
###########
# Builder
###########
FROM node:18.19.1-alpine as builder
WORKDIR /usr/src/app
# install node-gyp dependencies
RUN apk add --no-cache python3 make g++
# install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate
# Copy application dependency manifests to the container image.
COPY --link ./package.json ./package.json
COPY --link ./docker/main.js ./docker/main.js
COPY --link ./docker/start-litestream.sh /usr/src/appEntry/start.sh
COPY --link src/public/ ./docker/public/
COPY --link ./docker/nc-gui/ ./docker/nc-gui/
# for pnpm to generate a flat node_modules without symlinks
# so that modclean could work as expected
RUN echo "node-linker=hoisted" > .npmrc
# install production dependencies,
# reduce node_module size with modclean & removing sqlite deps,
# and add execute permission to start.sh
RUN pnpm install --prod --shamefully-hoist \
&& pnpm dlx modclean --patterns="default:*" --ignore="nc-lib-gui/**,dayjs/**,express-status-monitor/**,@azure/msal-node/dist/**" --run \
&& rm -rf ./node_modules/sqlite3/deps \
&& chmod +x /usr/src/appEntry/start.sh
##########
# Runner
##########
FROM alpine:3.19
WORKDIR /usr/src/app
ENV LITESTREAM_S3_SKIP_VERIFY=false \
LITESTREAM_RETENTION=1440h \
LITESTREAM_RETENTION_CHECK_INTERVAL=72h \
LITESTREAM_SNAPSHOT_INTERVAL=24h \
LITESTREAM_SYNC_INTERVAL=60s \
NC_DOCKER=0.6 \
NC_TOOL_DIR=/usr/app/data/ \
NODE_ENV=production \
PORT=8080
RUN apk add --update --no-cache \
dasel \
dumb-init \
nodejs
# Copy litestream binary and config file
COPY --link --from=lt-builder /usr/src/lt /usr/local/bin/litestream
COPY --link ./docker/litestream.yml /etc/litestream.yml
# Copy production code & main entry file
COPY --link --from=builder /usr/src/app/ /usr/src/app/
COPY --link --from=builder /usr/src/appEntry/ /usr/src/appEntry/
EXPOSE 8080
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
# Start Nocodb
CMD ["/usr/src/appEntry/start.sh"]

10
packages/nocodb/src/controllers/public-datas-export.controller.ts

@ -52,6 +52,11 @@ export class PublicDatasExportController {
NcError.invalidSharedViewPassword();
}
// check if download is allowed, in general it's called as CSV download
if (!view.meta?.allowCSVDownload) {
NcError.forbidden('Download is not allowed for this view');
}
const model = await view.getModelWithInfo(context);
await view.getColumns(context);
@ -115,6 +120,11 @@ export class PublicDatasExportController {
NcError.invalidSharedViewPassword();
}
// check if download is allowed
if (!view.meta?.allowCSVDownload) {
NcError.forbidden('Download is not allowed for this view');
}
const model = await view.getModelWithInfo(context);
await view.getColumns(context);

21
packages/nocodb/src/run/timely.ts

@ -0,0 +1,21 @@
import path from 'path';
import cors from 'cors';
import express from 'express';
import Noco from '~/Noco';
const server = express();
server.enable('trust proxy');
server.use(cors());
server.use(
process.env.NC_DASHBOARD_URL ?? '/dashboard',
express.static(path.join(__dirname, 'nc-gui')),
);
server.set('view engine', 'ejs');
(async () => {
const httpServer = server.listen(process.env.PORT || 8080, async () => {
console.log(`App started successfully.\nVisit -> ${Noco.dashboardUrl}`);
server.use(await Noco.init({}, httpServer, server));
});
})().catch((e) => console.log(e));

7
packages/nocodb/src/services/utils.service.ts

@ -80,7 +80,7 @@ export class UtilsService {
constructor(protected readonly configService: ConfigService<AppConfig>) {}
lastSyncTime = dayjs();
lastSyncTime = null;
async versionInfo() {
if (
@ -526,7 +526,10 @@ export class UtilsService {
}
let payload = null;
if (dayjs().isAfter(this.lastSyncTime.add(3, 'hours'))) {
if (
!this.lastSyncTime ||
dayjs().isAfter(this.lastSyncTime.add(3, 'hours'))
) {
payload = await T.payload();
this.lastSyncTime = dayjs();
}

2
packages/nocodb/src/utils/TeleBatchProcessor.ts

@ -43,7 +43,7 @@ class TeleBatchProcessor {
return;
}
await axios.post('https://nocodb.com/api/v1/telemetry', batch);
await axios.post('https://telemetry.nocodb.com/api/v1/telemetry', batch);
}
}

1
packages/nocodb/src/utils/tele.ts

@ -264,6 +264,7 @@ class Tele {
xc_version: process.env.NC_SERVER_UUID,
env: process.env.NODE_ENV || 'production',
oneClick: !!process.env.NC_ONE_CLICK,
disabled: isDisabled,
};
try {
payload.os_type = os.type();

57
packages/nocodb/webpack.timely.config.js

@ -0,0 +1,57 @@
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const webpack = require('webpack');
const CopyPlugin = require('copy-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { resolveTsAliases } = require('./build-utils/resolveTsAliases');
module.exports = {
entry: './src/run/timely.ts',
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: {
loader: 'ts-loader',
options: {
transpileOnly: true,
},
},
},
],
},
optimization: {
minimize: true, //Update this to true or false
minimizer: [new TerserPlugin()],
nodeEnv: false,
},
externals: [
nodeExternals({
allowlist: ['nocodb-sdk'],
}),
],
resolve: {
extensions: ['.tsx', '.ts', '.js', '.json'],
alias: resolveTsAliases(path.resolve('./tsconfig.json')),
},
mode: 'production',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'docker'),
library: 'libs',
libraryTarget: 'umd',
globalObject: "typeof self !== 'undefined' ? self : this",
},
node: {
__dirname: false,
},
plugins: [
new webpack.EnvironmentPlugin(['EE']),
new CopyPlugin({
patterns: [{ from: 'src/public', to: 'public' }],
}),
],
target: 'node',
};

33213
pnpm-lock.yaml

File diff suppressed because it is too large Load Diff

12
pnpm-workspace.yaml

@ -1,7 +1,7 @@
packages:
- 'packages/nocodb-sdk'
- 'packages/nc-gui'
- 'packages/nc-mail-templates'
- 'packages/nocodb'
- 'tests/playwright'
- 'packages/nc-secret-mgr'
- "packages/nocodb-sdk"
- "packages/nc-gui"
- "packages/nc-mail-templates"
- "packages/nocodb"
- "tests/playwright"
- "packages/nc-secret-mgr"

Loading…
Cancel
Save