Browse Source

Merge branch 'develop' of https://github.com/nocodb/nocodb into geodata-prototyping-restart

pull/4749/head
flisowna 2 years ago
parent
commit
bbe291af5a
  1. 2
      .github/workflows/pr-to-master.yml
  2. 98
      .github/workflows/release-executables.yml
  3. 4
      .github/workflows/release-npm.yml
  4. 76
      .github/workflows/release-pr.yml
  5. 4
      .github/workflows/update-sdk-path.yml
  6. 2
      charts/nocodb/.gitignore
  7. 23
      charts/nocodb/.helmignore
  8. 12
      charts/nocodb/Chart.yaml
  9. 22
      charts/nocodb/templates/NOTES.txt
  10. 82
      charts/nocodb/templates/_helpers.tpl
  11. 13
      charts/nocodb/templates/configmap.yaml
  12. 73
      charts/nocodb/templates/deployment.yaml
  13. 28
      charts/nocodb/templates/hpa.yaml
  14. 61
      charts/nocodb/templates/ingress.yaml
  15. 14
      charts/nocodb/templates/pvc.yaml
  16. 13
      charts/nocodb/templates/secret.yaml
  17. 15
      charts/nocodb/templates/service.yaml
  18. 12
      charts/nocodb/templates/serviceaccount.yaml
  19. 15
      charts/nocodb/templates/tests/test-connection.yaml
  20. 98
      charts/nocodb/values.yaml
  21. 27
      markdown/readme/languages/korean.md
  22. 2
      packages/nc-gui/components/account/UserList.vue
  23. 13
      packages/nc-gui/components/cell/Currency.vue
  24. 8
      packages/nc-gui/components/cell/Json.vue
  25. 37
      packages/nc-gui/components/cell/MultiSelect.vue
  26. 13
      packages/nc-gui/components/cell/Percent.vue
  27. 39
      packages/nc-gui/components/cell/SingleSelect.vue
  28. 6
      packages/nc-gui/components/smartsheet/Gallery.vue
  29. 60
      packages/nc-gui/components/smartsheet/Kanban.vue
  30. 2
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  31. 2
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  32. 2
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  33. 2
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  34. 3
      packages/nc-gui/components/virtual-cell/Formula.vue
  35. 3
      packages/nc-gui/composables/useMultiSelect/index.ts
  36. 13
      packages/nc-gui/composables/useSharedFormViewStore.ts
  37. 1
      packages/nc-gui/lang/ar.json
  38. 1
      packages/nc-gui/lang/bn_IN.json
  39. 19
      packages/nc-gui/lang/cs.json
  40. 1
      packages/nc-gui/lang/da.json
  41. 4
      packages/nc-gui/lang/de.json
  42. 1
      packages/nc-gui/lang/en.json
  43. 1
      packages/nc-gui/lang/es.json
  44. 1
      packages/nc-gui/lang/eu.json
  45. 1
      packages/nc-gui/lang/fa.json
  46. 1
      packages/nc-gui/lang/fi.json
  47. 233
      packages/nc-gui/lang/fr.json
  48. 1
      packages/nc-gui/lang/he.json
  49. 1
      packages/nc-gui/lang/hi.json
  50. 1
      packages/nc-gui/lang/hr.json
  51. 1
      packages/nc-gui/lang/id.json
  52. 1
      packages/nc-gui/lang/it.json
  53. 1
      packages/nc-gui/lang/ja.json
  54. 1
      packages/nc-gui/lang/ko.json
  55. 1
      packages/nc-gui/lang/lv.json
  56. 1
      packages/nc-gui/lang/nl.json
  57. 1
      packages/nc-gui/lang/no.json
  58. 1
      packages/nc-gui/lang/pl.json
  59. 1
      packages/nc-gui/lang/pt.json
  60. 1
      packages/nc-gui/lang/pt_BR.json
  61. 1
      packages/nc-gui/lang/ru.json
  62. 1
      packages/nc-gui/lang/sk.json
  63. 1
      packages/nc-gui/lang/sl.json
  64. 1
      packages/nc-gui/lang/sv.json
  65. 1
      packages/nc-gui/lang/th.json
  66. 1
      packages/nc-gui/lang/tr.json
  67. 1
      packages/nc-gui/lang/uk.json
  68. 1
      packages/nc-gui/lang/vi.json
  69. 1
      packages/nc-gui/lang/zh-Hans.json
  70. 1
      packages/nc-gui/lang/zh-Hant.json
  71. 4
      packages/nc-gui/nuxt.config.ts
  72. 26
      packages/nc-gui/package-lock.json
  73. 2
      packages/nc-lib-gui/package.json
  74. 24
      packages/nc-plugin/package-lock.json
  75. 112
      packages/noco-docs/content/en/getting-started/installation.md
  76. 8
      packages/noco-docs/content/en/setup-and-usages/account-settings.md
  77. 34571
      packages/noco-docs/package-lock.json
  78. 2
      packages/noco-docs/package.json
  79. 17
      packages/nocodb-sdk/package-lock.json
  80. 2
      packages/nocodb-sdk/package.json
  81. 2
      packages/nocodb-sdk/src/lib/Api.ts
  82. 17
      packages/nocodb-sdk/src/lib/formulaHelpers.ts
  83. 1
      packages/nocodb/README.md
  84. 80
      packages/nocodb/package-lock.json
  85. 4
      packages/nocodb/package.json
  86. 2
      packages/nocodb/src/lib/Noco.ts
  87. 132
      packages/nocodb/src/lib/db/sql-client/lib/mysql/mysql.queries.ts
  88. 14
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts
  89. 2
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts
  90. 86
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts
  91. 17
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts
  92. 69
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts
  93. 6
      packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/sortV2.ts
  94. 21
      packages/nocodb/src/lib/meta/api/columnApis.ts
  95. 2
      packages/nocodb/src/lib/meta/api/swagger/swaggerHtml.ts
  96. 13
      packages/nocodb/src/lib/meta/api/sync/helpers/job.ts
  97. 6
      packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts
  98. 2
      packages/nocodb/src/lib/models/Base.ts
  99. 13
      packages/nocodb/src/lib/models/Filter.ts
  100. 27
      packages/nocodb/src/lib/models/Model.ts
  101. Some files were not shown because too many files have changed in this diff Show More

2
.github/workflows/pr-to-master.yml

@ -55,7 +55,7 @@ jobs:
echo "Pull Request URL - ${{ steps.cpr.outputs.pr_url }}"
- name: automerge
if: ${{ github.event.inputs.targetEnv == 'PROD' || inputs.targetEnv == 'PROD' }}
uses: "pascalgn/automerge-action@v0.14.3"
uses: "pascalgn/automerge-action@v0.15.5"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
PULL_REQUEST: "${{ steps.cpr.outputs.pr_number }}"

98
.github/workflows/release-executables.yml

@ -21,14 +21,6 @@ jobs:
build-executables:
runs-on: ubuntu-latest
steps:
# Get the latest draft release for asset upload url
- uses: cardinalby/git-get-release-action@v1
id: get_release
env:
GITHUB_TOKEN: ${{ secrets.NC_GITHUB_TOKEN }}
with:
latest: 1
draft: true
- uses: actions/checkout@v3
- name: Cache node modules
id: cache-npm
@ -107,54 +99,25 @@ jobs:
mv ./dist/Noco-macos-arm64 ./mac-dist/
mv ./dist/Noco-macos-x64 ./mac-dist/
- name: Upload win-arm64 build to asset
- name: Upload executables to asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./scripts/pkg-executable/dist/Noco-win-arm64.exe
asset_name: Noco-win-arm64.exe
asset_content_type: application/octet-stream
- name: Upload win-x64 build to asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./scripts/pkg-executable/dist/Noco-win-x64.exe
asset_name: Noco-win-x64.exe
asset_content_type: application/octet-stream
- name: Upload linux-arm64 build to asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./scripts/pkg-executable/dist/Noco-linux-arm64
asset_name: Noco-linux-arm64
asset_content_type: application/octet-stream
- name: Upload linux-x64 build to asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.get_release.outputs.upload_url }}
asset_path: ./scripts/pkg-executable/dist/Noco-linux-x64
asset_name: Noco-linux-x64
asset_content_type: application/octet-stream
draft: true
tag_name: ${{ github.event.inputs.tag || inputs.tag }}
files: |
./scripts/pkg-executable/dist/Noco-win-arm64.exe
./scripts/pkg-executable/dist/Noco-win-x64.exe
./scripts/pkg-executable/dist/Noco-linux-arm64
./scripts/pkg-executable/dist/Noco-linux-x64
- uses: actions/upload-artifact@master
with:
name: ${{ github.event.inputs.tag || inputs.tag }}
path: scripts/pkg-executable/mac-dist
retention-days: 1
outputs:
upload_url: ${{ steps.get_release.outputs.upload_url }}
sign-mac-executables:
runs-on: macos-latest
needs: build-executables
@ -205,40 +168,17 @@ jobs:
id: compress
- name: Upload macos-x64 build to asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.build-executables.outputs.upload_url }}
asset_path: ./scripts/pkg-executable/mac-dist/Noco-macos-x64
asset_name: Noco-macos-x64
asset_content_type: application/octet-stream
- name: Upload macos-arm64 build to asset
uses: actions/upload-release-asset@v1
- name: Upload macos executable to asset
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.build-executables.outputs.upload_url }}
asset_path: ./scripts/pkg-executable/mac-dist/Noco-macos-arm64
asset_name: Noco-macos-arm64
asset_content_type: application/octet-stream
- name: Upload macos compressed build(for homebrew) to asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.build-executables.outputs.upload_url }}
asset_path: ./scripts/pkg-executable/mac-dist/nocodb.tar.gz
asset_name: nocodb.tar.gz
asset_content_type: application/octet-stream
draft: true
tag_name: ${{ github.event.inputs.tag || inputs.tag }}
files: |
./scripts/pkg-executable/mac-dist/Noco-macos-x64
./scripts/pkg-executable/mac-dist/Noco-macos-arm64
./scripts/pkg-executable/mac-dist/nocodb.tar.gz
- name: Generate Homebrew Formula class and push
run: |
@ -267,7 +207,3 @@ jobs:
git commit ./Formula/nocodb.rb -m "Automatic publish"
git push

4
.github/workflows/release-npm.yml

@ -68,7 +68,7 @@ jobs:
- name: Create Pull Request
if: ${{ github.event.inputs.targetEnv == 'PROD' || inputs.targetEnv == 'PROD' }}
id: cpr
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v4.2.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@ -84,7 +84,7 @@ jobs:
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
- name: automerge
if: ${{ github.event.inputs.targetEnv == 'PROD' || inputs.targetEnv == 'PROD' }}
uses: "pascalgn/automerge-action@v0.14.3"
uses: "pascalgn/automerge-action@v0.15.5"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
PULL_REQUEST: "${{ steps.cpr.outputs.pull-request-number }}"

76
.github/workflows/release-pr.yml

@ -33,7 +33,7 @@ jobs:
# Get current PR number
PR_NUMBER=${{github.event.number}}
# Get current version
CURRENT_VERSION=$(curl -fs https://docs.nocodb.com/releases | grep article | grep div | grep h2 | grep 'id\="[^"]*' -o | cut -c 5-)
CURRENT_VERSION=$(curl -fs https://docs.nocodb.com/releases | grep article | grep div | grep h2 | grep 'id\="[^"]*' -o | cut -c 5- | cut -d\: -f1)
# Construct tag name
TAG_NAME=pr-${PR_NUMBER}-${CURRENT_DATE}-${CURRENT_TIME}
echo "TARGET_TAG=${TAG_NAME}" >> $GITHUB_OUTPUT
@ -72,14 +72,14 @@ jobs:
DOCKERHUB_TOKEN: "${{ secrets.DOCKERHUB_TOKEN }}"
# Build executables and publish to GitHub
release-executables:
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, release-npm]
uses: ./.github/workflows/release-timely-executables.yml
with:
tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}
secrets:
NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}"
# release-executables:
# 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, release-npm]
# uses: ./.github/workflows/release-timely-executables.yml
# with:
# tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}
# secrets:
# NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}"
# Add a comment for PR docker build
leave-comment:
@ -132,40 +132,40 @@ jobs:
retention-days: 2
# Add a comment for PR executable build
leave-executable-comment:
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' }}
runs-on: 'ubuntu-latest'
needs: [release-executables, set-tag]
steps:
- uses: peter-evans/commit-comment@v2
with:
body: |
### Run Executables
# leave-executable-comment:
# 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' }}
# runs-on: 'ubuntu-latest'
# needs: [release-executables, set-tag]
# steps:
# - uses: peter-evans/commit-comment@v2
# with:
# body: |
# ### Run Executables
#### MacOS
# #### MacOS
```bash
mkdir -p ./${{ needs.set-tag.outputs.current_version }}/${{ needs.set-tag.outputs.target_tag }} && cd "$_" \
&& curl http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-macos-arm64 -o noco -L \
&& chmod +x noco \
&& ./noco
```
#### Linux
# ```bash
# mkdir -p ./${{ needs.set-tag.outputs.current_version }}/${{ needs.set-tag.outputs.target_tag }} && cd "$_" \
# && curl http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-macos-arm64 -o noco -L \
# && chmod +x noco \
# && ./noco
# ```
# #### Linux
```bash
mkdir -p ./${{ needs.set-tag.outputs.current_version }}/${{ needs.set-tag.outputs.target_tag }} && cd "$_" \
&& curl http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-linux-x64 -o noco -L \
&& chmod +x noco \
&& ./noco
```
#### Windows
# ```bash
# mkdir -p ./${{ needs.set-tag.outputs.current_version }}/${{ needs.set-tag.outputs.target_tag }} && cd "$_" \
# && curl http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-linux-x64 -o noco -L \
# && chmod +x noco \
# && ./noco
# ```
# #### Windows
```bash
iwr http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-win-arm64.exe
.\Noco-win-arm64.exe
```
# ```bash
# iwr http://dl.nocodb.com/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }}/Noco-win-arm64.exe
# .\Noco-win-arm64.exe
# ```
For executables visit [here](https://github.com/nocodb/nocodb-timely/releases/tag/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }})
# For executables visit [here](https://github.com/nocodb/nocodb-timely/releases/tag/${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.target_tag }})
# delete the uffizzi preview created off of this PR
delete-uffizzi-preview:

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

@ -27,7 +27,7 @@ jobs:
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v4.2.3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@ -41,7 +41,7 @@ jobs:
echo "Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}"
echo "Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}"
- name: automerge
uses: "pascalgn/automerge-action@v0.14.3"
uses: "pascalgn/automerge-action@v0.15.5"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
PULL_REQUEST: "${{ steps.cpr.outputs.pull-request-number }}"

2
charts/nocodb/.gitignore vendored

@ -0,0 +1,2 @@
charts/
Chart.lock

23
charts/nocodb/.helmignore

@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

12
charts/nocodb/Chart.yaml

@ -0,0 +1,12 @@
apiVersion: v2
appVersion: 0.100.2
dependencies:
- condition: postgresql.enabled
name: postgresql
repository: https://charts.bitnami.com/bitnami
version: ~11.6.6
description: A Helm chart for Kubernetes
maintainers: []
name: nocodb
type: application
version: 0.3.0

22
charts/nocodb/templates/NOTES.txt

@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "nocodb.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "nocodb.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "nocodb.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "nocodb.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

82
charts/nocodb/templates/_helpers.tpl

@ -0,0 +1,82 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "nocodb.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "nocodb.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "nocodb.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "nocodb.labels" -}}
helm.sh/chart: {{ include "nocodb.chart" . }}
{{ include "nocodb.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "nocodb.selectorLabels" -}}
app.kubernetes.io/name: {{ include "nocodb.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "nocodb.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "nocodb.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
{{- define "postgresHost" -}}
{{- printf "%s-postgresql" .Release.Name }}
{{- end }}
{{- define "postgresDatabase" -}}
{{- .Values.postgresql.auth.database }}
{{- end }}
{{- define "postgresUsername" -}}
{{- .Values.postgresql.auth.username }}
{{- end }}
{{- define "postgresPassword" -}}
{{- .Values.postgresql.auth.password }}
{{- end }}
{{- define "databaseUri" -}}
{{- printf "pg://%s:5432?u=%s&p=%s&d=%s" (include "postgresHost" .) (include "postgresUsername" .) (include "postgresPassword" .) (include "postgresDatabase" .) }}
{{- end }}

13
charts/nocodb/templates/configmap.yaml

@ -0,0 +1,13 @@
{{- if .Values.extraEnvs }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "nocodb.fullname" . }}
labels:
{{- include "nocodb.labels" . | nindent 4 }}
data:
{{- range $key, $value := .Values.extraEnvs }}
{{ $key }}: |-
{{- $value | nindent 4 }}
{{- end }}
{{- end }}

73
charts/nocodb/templates/deployment.yaml

@ -0,0 +1,73 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "nocodb.fullname" . }}
labels:
{{- include "nocodb.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "nocodb.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "nocodb.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "nocodb.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
volumeMounts:
- name: {{ include "nocodb.fullname" . }}
mountPath: /usr/app/data
envFrom:
- configMapRef:
name: {{ include "nocodb.fullname" . }}
- secretRef:
name: {{ include "nocodb.fullname" . }}
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: {{ include "nocodb.fullname" . }}
persistentVolumeClaim:
claimName: {{ include "nocodb.fullname" . }}

28
charts/nocodb/templates/hpa.yaml

@ -0,0 +1,28 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "nocodb.fullname" . }}
labels:
{{- include "nocodb.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "nocodb.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

61
charts/nocodb/templates/ingress.yaml

@ -0,0 +1,61 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "nocodb.fullname" . -}}
{{- $svcPort := .Values.ingress.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "nocodb.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

14
charts/nocodb/templates/pvc.yaml

@ -0,0 +1,14 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "nocodb.fullname" . }}
labels:
{{- include "nocodb.selectorLabels" . | nindent 8 }}
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: {{ .Values.storage.size }}
storageClassName: {{ .Values.storage.storageClassName }}
volumeMode: Filesystem

13
charts/nocodb/templates/secret.yaml

@ -0,0 +1,13 @@
{{- if .Values.extraSecretEnvs }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "nocodb.fullname" . }}
labels:
{{- include "nocodb.labels" . | nindent 4 }}
data:
NC_DB: {{ include "databaseUri" . | b64enc}}
{{- range $key, $value := .Values.extraSecretEnvs }}
{{ $key }}: '{{ $value | b64enc }}'
{{- end }}
{{- end }}

15
charts/nocodb/templates/service.yaml

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "nocodb.fullname" . }}
labels:
{{- include "nocodb.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "nocodb.selectorLabels" . | nindent 4 }}

12
charts/nocodb/templates/serviceaccount.yaml

@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "nocodb.serviceAccountName" . }}
labels:
{{- include "nocodb.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

15
charts/nocodb/templates/tests/test-connection.yaml

@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "nocodb.fullname" . }}-test-connection"
labels:
{{- include "nocodb.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "nocodb.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

98
charts/nocodb/values.yaml

@ -0,0 +1,98 @@
# Default values for nocodb.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: nocodb/nocodb
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: false
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 8080
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
extraEnvs:
NC_PUBLIC_URL: https:/nocodb.local.org
extraSecretEnvs:
NC_AUTH_JWT_SECRET: secretString
storage:
size: 3Gi
storageClassName: ""
postgresql:
enabled: false
auth:
database: nocodb
username: nocodb
password: secretPass
persistence:
size: 8Gi

27
markdown/readme/languages/korean.md

@ -6,7 +6,7 @@
</h1>
<p align="center">
MySQL, PostgreSQL, SQL Server, SQLite, MariaDB를 똑똑한 스프레드시트로 바꿔줍니다.
MySQL, PostgreSQL, SQL Server, SQLite, MariaDB를 스마트 스프레드시트로 바꿔줍니다.
</p>
<div align="center">
@ -32,7 +32,7 @@ MySQL, PostgreSQL, SQL Server, SQLite, MariaDB를 똑똑한 스프레드시트
<a href="https://www.producthunt.com/posts/nocodb?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-nocodb" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=297536&theme=dark" alt="NocoDB - The Open Source Airtable alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
</p>
# 바로 써보기
# 빠른 시도
### Docker 사용
@ -65,7 +65,7 @@ npm start
### GUI
대시보드 접근하기 : [http://localhost:8080/dashboard](http://localhost:8080/dashboard)
대시보드 접근 : [http://localhost:8080/dashboard](http://localhost:8080/dashboard)
# 커뮤니티 가입
@ -114,18 +114,17 @@ npm start
# 기능
### 스프레드시트같은 인터페이스
### 스프레드시트 인터페이스
- ⚡ 정말 쉬운 검색, 정렬, 필터링, 열 숨기기
- ⚡ 뷰 만들기: 그리드, 갤러리, 칸반, 간트 차트, 양식
- ⚡ 뷰 공유하기: 완전 공개, 패스워드 걸고 공개
- ⚡ 개인화하거나 잠글 수 있는 뷰
- ⚡ 이미지를 자신의 공간에 업로드 (S3, Minio, GCP, Azure, Digitalocean, Linode, OVH, Backblaze 등)
- ⚡ 역할 부여: 소유자, 작성자, 편집자, 보기 전용, 의견 제시만, 원하는 대로
- ⚡ 접근 통제: 데이터베이스, 테이블 및 열 수준까지도 상세한 통제 가능
- ⚡ 기본 오퍼레이션: 테이블, 칼럼, 로우 CRUD
- ⚡ 필드 오퍼레이션: 정렬, 필터, 칼럼 보기/숨기기
- ⚡ 뷰 타입: 그리드, 갤러리, 칸반, 간트 차트, 양식(Form)
- ⚡ 공유: 공개 / 비공개 뷰 (비밀 번호 설정)
- ⚡ 다양한 셀 타입: ID, LinkToAnotherRecord, Lookup, Rollup, SingleLine Text, Attachment, Currency, Formula 등
- ⚡ 역할에 따른 접근 제한: 다양한 수준의 세분화된 액세스 제어
### 워크플로 자동화를 위한 앱스토어
크게 채팅, 이메일, 저장소 세 가지 카테고리에 대한 통합을 제공합니다. 자세한 사항은 <a href="https://docs.nocodb.com/setup-and-usages/app-store" target="_blank">App Store</a> 를 참고하세요.
- ⚡ 채팅: MS 팀즈, 슬랙, 디스코드, 매터모스트
- ⚡ 이메일: SMTP, SES, MailChimp
- ⚡ SMS: Twilio
@ -188,12 +187,12 @@ docker-compose up -d
여기서 확인해주세요.
[환경변수 ](https://docs.nocodb.com/getting-started/installation#environment-variables)
# 개발 환경에 설치하기
# 개발 환경에 설치
여기서 확인해주세요.
[개발 환경에 설치하는 법](https://docs.nocodb.com/engineering/development-setup)
# 기여하기
# 기여
여기서 확인해주세요.
[기여 가이드라인](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md).

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

@ -71,7 +71,7 @@ const deleteUser = async (userId: string) => {
Modal.confirm({
title: 'Are you sure you want to delete this user?',
type: 'warn',
content: 'On deleting, user will remove from organization and any sync source(Airtable) created by user will get removed',
content: 'Upon deletion, the user will be removed from the installation.',
onOk: async () => {
try {
await api.orgUsers.delete(userId)

13
packages/nc-gui/components/cell/Currency.vue

@ -14,7 +14,18 @@ const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj)!
const vModel = useVModel(props, 'modelValue', emit)
const _vModel = useVModel(props, 'modelValue', emit)
const vModel = computed({
get: () => _vModel.value,
set: (value: unknown) => {
if (value === '') {
_vModel.value = null
} else {
_vModel.value = value as number
}
},
})
const lastSaved = ref()

8
packages/nc-gui/components/cell/Json.vue

@ -123,8 +123,8 @@ useSelectedCellKeyupListener(active, (e) => {
<template>
<component :is="isExpanded ? AModal : 'div'" v-model:visible="isExpanded" :closable="false" centered :footer="null">
<div v-if="editEnabled && !readonly" class="flex flex-col w-full">
<div class="flex flex-row justify-between pt-1 pb-2">
<div v-if="editEnabled && !readonly" class="flex flex-col w-full" @mousedown.stop @mouseup.stop @click.stop>
<div class="flex flex-row justify-between pt-1 pb-2" @mousedown.stop>
<a-button type="text" size="small" @click="isExpanded = !isExpanded">
<CilFullscreenExit v-if="isExpanded" class="h-2.5" />
@ -134,8 +134,8 @@ useSelectedCellKeyupListener(active, (e) => {
<div v-if="!isForm || isExpanded" class="flex flex-row">
<a-button type="text" size="small" :onclick="clear"><div class="text-xs">Cancel</div></a-button>
<a-button type="primary" size="small" :disabled="!!error || localValue === vModel">
<div class="text-xs" @click="onSave">Save</div>
<a-button type="primary" size="small" :disabled="!!error || localValue === vModel" @click="onSave">
<div class="text-xs">Save</div>
</a-button>
</div>
</div>

37
packages/nc-gui/components/cell/MultiSelect.vue

@ -19,6 +19,7 @@ import {
useEventListener,
useMetas,
useProject,
useRoles,
useSelectedCellKeyupListener,
watch,
} from '#imports'
@ -57,6 +58,8 @@ const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const { hasRole } = useRoles()
const { isPg, isMysql } = useProject()
// a variable to keep newly created options value
@ -80,6 +83,10 @@ const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true))
const editAllowed = computed(() => hasEditRoles.value && (active.value || editable.value))
const vModel = computed({
get: () => {
const selected = selectedIds.value.reduce((acc, id) => {
@ -154,10 +161,12 @@ watch(
)
watch(isOpen, (n, _o) => {
if (!n) {
aselect.value?.$el?.querySelector('input')?.blur()
} else {
aselect.value?.$el?.querySelector('input')?.focus()
if (editAllowed.value) {
if (!n) {
aselect.value?.$el?.querySelector('input')?.blur()
} else {
aselect.value?.$el?.querySelector('input')?.focus()
}
}
})
@ -167,7 +176,7 @@ useSelectedCellKeyupListener(active, (e) => {
isOpen.value = false
break
case 'Enter':
if (active.value && !isOpen.value) {
if (editAllowed.value && active.value && !isOpen.value) {
isOpen.value = true
}
break
@ -179,6 +188,10 @@ useSelectedCellKeyupListener(active, (e) => {
// skip
break
default:
if (!editAllowed.value) {
e.preventDefault()
break
}
// toggle only if char key pressed
if (!(e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) && e.key?.length === 1) {
e.stopPropagation()
@ -272,14 +285,14 @@ const onTagClick = (e: Event, onClose: Function) => {
:bordered="false"
clear-icon
show-search
:show-arrow="!readOnly"
:show-arrow="hasEditRoles && !readOnly && (editable || (active && vModel.length === 0))"
:open="isOpen && (active || editable)"
:disabled="readOnly"
:class="{ '!ml-[-8px]': readOnly }"
:class="{ '!ml-[-8px]': readOnly, 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`"
@search="search"
@keydown.stop
@click="isOpen = (active || editable) && !isOpen"
@click="isOpen = editAllowed && !isOpen"
>
<a-select-option
v-for="op of options"
@ -303,7 +316,11 @@ const onTagClick = (e: Event, onClose: Function) => {
</a-tag>
</a-select-option>
<a-select-option v-if="searchVal && isOptionMissing && !isPublic" :key="searchVal" :value="searchVal">
<a-select-option
v-if="searchVal && isOptionMissing && !isPublic && (hasRole('owner', true) || hasRole('creator', true))"
:key="searchVal"
:value="searchVal"
>
<div class="flex gap-2 text-gray-500 items-center h-full">
<MdiPlusThick class="min-w-4" />
<div class="text-xs whitespace-normal">
@ -318,7 +335,7 @@ const onTagClick = (e: Event, onClose: Function) => {
class="rounded-tag nc-selected-option"
:style="{ display: 'flex', alignItems: 'center' }"
:color="options.find((el) => el.title === val)?.color"
:closable="(active || editable) && (vModel.length > 1 || !column?.rqd)"
:closable="editAllowed && (active || editable) && (vModel.length > 1 || !column?.rqd)"
:close-icon="h(MdiCloseCircle, { class: ['ms-close-icon'] })"
@click="onTagClick($event, onClose)"
@close="onClose"

13
packages/nc-gui/components/cell/Percent.vue

@ -12,7 +12,18 @@ const emits = defineEmits(['update:modelValue'])
const editEnabled = inject(EditModeInj)
const vModel = useVModel(props, 'modelValue', emits)
const _vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({
get: () => _vModel.value,
set: (value) => {
if (value === '') {
_vModel.value = null
} else {
_vModel.value = value
}
},
})
const focus: VNodeRef = (el) => {
;(el as HTMLInputElement)?.focus()

39
packages/nc-gui/components/cell/SingleSelect.vue

@ -14,6 +14,7 @@ import {
extractSdkResponseErrorMsg,
inject,
ref,
useRoles,
useSelectedCellKeyupListener,
watch,
} from '#imports'
@ -49,6 +50,8 @@ const searchVal = ref()
const { getMeta } = useMetas()
const { hasRole } = useRoles()
const { isPg, isMysql } = useProject()
// a variable to keep newly created option value
@ -73,6 +76,10 @@ const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
const hasEditRoles = computed(() => hasRole('owner', true) || hasRole('creator', true) || hasRole('editor', true))
const editAllowed = computed(() => hasEditRoles.value && (active.value || editable.value))
const vModel = computed({
get: () => tempSelectedOptState.value ?? modelValue,
set: (val) => {
@ -87,10 +94,12 @@ const vModel = computed({
})
watch(isOpen, (n, _o) => {
if (!n) {
aselect.value?.$el?.querySelector('input')?.blur()
} else {
aselect.value?.$el?.querySelector('input')?.focus()
if (editAllowed.value) {
if (!n) {
aselect.value?.$el?.querySelector('input')?.blur()
} else {
aselect.value?.$el?.querySelector('input')?.focus()
}
}
})
@ -100,11 +109,15 @@ useSelectedCellKeyupListener(active, (e) => {
isOpen.value = false
break
case 'Enter':
if (active.value && !isOpen.value) {
if (editAllowed.value && active.value && !isOpen.value) {
isOpen.value = true
}
break
default:
if (!editAllowed.value) {
e.preventDefault()
break
}
// toggle only if char key pressed
if (!(e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) && e.key?.length === 1) {
e.stopPropagation()
@ -174,7 +187,7 @@ const toggleMenu = (e: Event) => {
vModel.value = ''
return
}
isOpen.value = (active.value || editable.value) && !isOpen.value
isOpen.value = editAllowed.value && !isOpen.value
}
</script>
@ -183,11 +196,12 @@ const toggleMenu = (e: Event) => {
ref="aselect"
v-model:value="vModel"
class="w-full"
:allow-clear="!column.rqd && active"
:class="{ 'caret-transparent': !hasEditRoles }"
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && (active || editable)"
:open="isOpen"
:disabled="readOnly"
:show-arrow="!readOnly && (active || editable || vModel === null)"
:show-arrow="hasEditRoles && !readOnly && (editable || (active && vModel === null))"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen ? 'active' : ''}`"
show-search
@select="isOpen = false"
@ -216,8 +230,11 @@ const toggleMenu = (e: Event) => {
</span>
</a-tag>
</a-select-option>
<a-select-option v-if="searchVal && isOptionMissing && !isPublic" :key="searchVal" :value="searchVal">
<a-select-option
v-if="searchVal && isOptionMissing && !isPublic && (hasRole('owner', true) || hasRole('creator', true))"
:key="searchVal"
:value="searchVal"
>
<div class="flex gap-2 text-gray-500 items-center h-full">
<MdiPlusThick class="min-w-4" />
<div class="text-xs whitespace-normal">

6
packages/nc-gui/components/smartsheet/Gallery.vue

@ -17,6 +17,7 @@ import {
createEventHook,
extractPkFromRow,
inject,
isLTAR,
nextTick,
onMounted,
provide,
@ -210,7 +211,10 @@ watch(view, async (nextView) => {
</template>
<div v-for="col in fieldsWithoutCover" :key="`record-${record.row.id}-${col.id}`">
<div v-if="!isRowEmpty(record, col)" class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full">
<div
v-if="!isRowEmpty(record, col) || isLTAR(col.uidt)"
class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full"
>
<div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5">
<div class="w-full text-gray-600">
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />

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

@ -13,6 +13,7 @@ import {
MetaInj,
OpenNewRecordFormHookInj,
inject,
isLTAR,
onBeforeMount,
onBeforeUnmount,
provide,
@ -472,35 +473,40 @@ watch(view, async (nextView) => {
:key="`record-${record.row.id}-${col.id}`"
class="flex flex-col rounded-lg w-full"
>
<!-- Smartsheet Header (Virtual) Cell -->
<div v-if="!isRowEmpty(record, col)" class="flex flex-row w-full justify-start pt-2">
<div class="w-full text-gray-400">
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
<div v-if="!isRowEmpty(record, col) || isLTAR(col.uidt)">
<!-- Smartsheet Header (Virtual) Cell -->
<div class="flex flex-row w-full justify-start pt-2">
<div class="w-full text-gray-400">
<LazySmartsheetHeaderVirtualCell
v-if="isVirtualCol(col)"
:column="col"
:hide-menu="true"
/>
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
</div>
</div>
</div>
<!-- Smartsheet (Virtual) Cell -->
<div
v-if="!isRowEmpty(record, col)"
class="flex flex-row w-full items-center justify-start pl-[6px]"
:class="{ '!ml-[-12px]': col.uidt === UITypes.SingleSelect }"
>
<LazySmartsheetVirtualCell
v-if="isVirtualCol(col)"
v-model="record.row[col.title]"
class="text-sm pt-1"
:column="col"
:row="record"
/>
<LazySmartsheetCell
v-else
v-model="record.row[col.title]"
class="text-sm pt-1"
:column="col"
:edit-enabled="false"
:read-only="true"
/>
<!-- Smartsheet (Virtual) Cell -->
<div
class="flex flex-row w-full items-center justify-start pl-[6px]"
:class="{ '!ml-[-12px]': col.uidt === UITypes.SingleSelect }"
>
<LazySmartsheetVirtualCell
v-if="isVirtualCol(col)"
v-model="record.row[col.title]"
class="text-sm pt-1"
:column="col"
:row="record"
/>
<LazySmartsheetCell
v-else
v-model="record.row[col.title]"
class="text-sm pt-1"
:column="col"
:edit-enabled="false"
:read-only="true"
/>
</div>
</div>
</div>
</a-card>

2
packages/nc-gui/components/smartsheet/column/LookupOptions.vue

@ -56,7 +56,7 @@ const columns = $computed<ColumnType[]>(() => {
<template>
<div class="p-6 w-full flex flex-col border-2 mb-2 mt-4">
<div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.linkToAnotherRecord')" v-bind="validateInfos.fk_relation_column_id">
<a-select
v-model:value="vModel.fk_relation_column_id"
dropdown-class-name="!w-64 nc-dropdown-relation-table"

2
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -76,7 +76,7 @@ const columns = $computed(() => {
<template>
<div class="p-6 w-full flex flex-col border-2 mb-2 mt-4">
<div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.linkToAnotherRecord')" v-bind="validateInfos.fk_relation_column_id">
<a-select
v-model:value="vModel.fk_relation_column_id"
dropdown-class-name="!w-64 nc-dropdown-relation-table"

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

@ -236,7 +236,7 @@ defineExpose({
:key="`${i}_7`"
v-model:value="filter.value"
class="nc-filter-value-select"
:disabled="filter.readOnly"
:disabled="filter.readOnly || !filter.fk_column_id"
@click.stop
@input="saveOrUpdate(filter, i)"
/>

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

@ -208,7 +208,7 @@ const iframeCode = computed(() => {
frameborder="0"
width="100%"
height="700"
style="background: transparent; border: 1px solid #ddd"/>`
style="background: transparent; border: 1px solid #ddd"></iframe>`
})
const copyIframeCode = async () => {

3
packages/nc-gui/components/virtual-cell/Formula.vue

@ -24,11 +24,10 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
<template #title>
<span class="font-bold">{{ column.colOptions.error }}</span>
</template>
<span>ERR!</span>
</a-tooltip>
<div class="p-2" @dblclick="activateShowEditNonEditableFieldWarning">
<div v-else class="p-2" @dblclick="activateShowEditNonEditableFieldWarning">
<div v-if="urls" v-html="urls" />
<div v-else>{{ result }}</div>

3
packages/nc-gui/composables/useMultiSelect/index.ts

@ -267,7 +267,8 @@ export function useMultiSelect(
// copy - ctrl/cmd +c
case 67:
// set clipboard context only if single cell selected
if (selectedRange.isSingleCell() && rowObj.row[columnObj.title!]) {
// or if selected range is empty
if (selectedRange.isSingleCell() || (selectedRange.isEmpty() && rowObj && columnObj)) {
clipboardContext = {
value: rowObj.row[columnObj.title!],
uidt: columnObj.uidt as UITypes,

13
packages/nc-gui/composables/useSharedFormViewStore.ts

@ -43,7 +43,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
const { metas, setMeta } = useMetas()
const { loadProject } = useProject()
const { project } = useProject()
const { t } = useI18n()
@ -86,7 +86,16 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
await setMeta(viewMeta.model)
await loadProject(true, viewMeta.project_id)
// if project is not defined then set it with an object containing base
if (!project.value?.bases)
project.value = {
bases: [
{
id: viewMeta.base_id,
type: viewMeta.client,
},
],
}
const relatedMetas = { ...viewMeta.relatedMetas }

1
packages/nc-gui/lang/ar.json

@ -277,6 +277,7 @@
"selectUserRole": "حدد دور المستخدم",
"childTable": "جدول فرعي",
"childColumn": "عمود فرعي",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "عند التحديث",
"onDelete": "عند الحذف",
"account": "حساب",

1
packages/nc-gui/lang/bn_IN.json

@ -277,6 +277,7 @@
"selectUserRole": "বযবহরকর ভিিচন করন",
"childTable": "Child table",
"childColumn": "Child column",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "আপড",
"onDelete": "ডিিটএ",
"account": "Account",

19
packages/nc-gui/lang/cs.json

@ -57,9 +57,9 @@
"reference": "Reference",
"function": "Funkce",
"confirm": "Potvrdit",
"generate": "Generate",
"generate": "Generovat",
"copy": "Kopírovat",
"misc": "Miscellaneous",
"misc": "Ostatní",
"lock": "Uzamknout",
"unlock": "Odemknout",
"credentials": "Přihlašovací údaje",
@ -113,7 +113,7 @@
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
"sqlVIew": "SQL pohled"
},
"datatype": {
"ID": "ID",
@ -188,16 +188,16 @@
"metaOperations": "Metadata Operations",
"audit": "Audit",
"auditLogs": "Audit Log",
"sqlMigrations": "SQL Migrations",
"sqlMigrations": "SQL migrace",
"dbCredentials": "Database Credentials",
"advancedParameters": "SSL & Advanced parameters",
"headCreateProject": "Create Project | NocoDB",
"headLogin": "Log In | NocoDB",
"resetPassword": "Reset your password",
"teamAndSettings": "Team & Settings",
"headCreateProject": "Vytvořit projekt | NocoDB",
"headLogin": "Přihlásit se | NocoDB",
"resetPassword": "Obnovit heslo",
"teamAndSettings": "Tým a nastavení",
"apiDocs": "API Docs",
"importFromAirtable": "Import From Airtable",
"generateToken": "Generate Token",
"generateToken": "Generovat token",
"APIsAndSupport": "APIs & Support",
"helpCenter": "Help center",
"swaggerDocumentation": "Swagger Documentation",
@ -277,6 +277,7 @@
"selectUserRole": "Vybrat uživatelskou roli",
"childTable": "Podřízená tabulka",
"childColumn": "Podřízený sloupec",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Při aktualizaci",
"onDelete": "Při odstranění",
"account": "Účet",

1
packages/nc-gui/lang/da.json

@ -277,6 +277,7 @@
"selectUserRole": "Vælg brugerrolle",
"childTable": "Børnebord",
"childColumn": "Barn kolonne",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "På opdatering",
"onDelete": "På Delete.",
"account": "Account",

4
packages/nc-gui/lang/de.json

@ -278,6 +278,7 @@
"selectUserRole": "Benutzerrolle auswählen",
"childTable": "Child-Tabelle",
"childColumn": "Child-Spalte",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Update",
"onDelete": "Löschen",
"account": "Benutzerkonto",
@ -505,8 +506,7 @@
},
"nonEditableFields": {
"computedFieldUnableToClear": "Warning: Computed field - unable to clear text",
"qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed.",
"barcodeFieldsCannotBeDirectlyChanged": "Warning: Barcode fields cannot be directly changed."
"qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed."
}
},
"info": {

1
packages/nc-gui/lang/en.json

@ -278,6 +278,7 @@
"selectUserRole": "Select User Role",
"childTable": "Child table",
"childColumn": "Child column",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "On Update",
"onDelete": "On Delete",
"account": "Account",

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

@ -277,6 +277,7 @@
"selectUserRole": "Seleccione el rol de usuario",
"childTable": "Tabla hija",
"childColumn": "Columna hija",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "En actualización",
"onDelete": "En eliminar",
"account": "Account",

1
packages/nc-gui/lang/eu.json

@ -277,6 +277,7 @@
"selectUserRole": "Select User Role",
"childTable": "Child table",
"childColumn": "Child column",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "On Update",
"onDelete": "On Delete",
"account": "Account",

1
packages/nc-gui/lang/fa.json

@ -277,6 +277,7 @@
"selectUserRole": "نقش کاربر را انتخاب کنید",
"childTable": "جدول فرزند",
"childColumn": "ستون فرزند",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "هنگام به روز رسانی",
"onDelete": "هنگام حذف",
"account": "Account",

1
packages/nc-gui/lang/fi.json

@ -277,6 +277,7 @@
"selectUserRole": "Valitse käyttäjän rooli",
"childTable": "Lapsipöytä",
"childColumn": "Lapsipylväs",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Päivitys",
"onDelete": "Poista",
"account": "Account",

233
packages/nc-gui/lang/fr.json

@ -26,9 +26,9 @@
"install": "Installer",
"show": "Montrer",
"hide": "Cacher",
"showAll": "Afficher tout",
"hideAll": "Cacher tout",
"showMore": "Montrer plus",
"showAll": "Tout afficher",
"hideAll": "Tout cacher",
"showMore": "Afficher davantage",
"showOptions": "Afficher les options",
"hideOptions": "Masquer les options",
"showMenu": "Afficher le menu",
@ -59,22 +59,22 @@
"confirm": "Confirmer",
"generate": "Générer",
"copy": "Copier",
"misc": "Miscellaneous",
"lock": "Lock",
"unlock": "Unlock",
"credentials": "Credentials",
"help": "Help",
"misc": "Divers",
"lock": "Verrouiller",
"unlock": "Déverrouiller",
"credentials": "Identifiants",
"help": "Aide",
"questions": "Questions",
"reachOut": "Reach out here",
"betaNote": "This feature is currently in beta.",
"moreInfo": "More information can be found here",
"logs": "Logs",
"groupingField": "Grouping Field",
"insertAfter": "Insert After",
"insertBefore": "Insert Before",
"hideField": "Hide Field",
"sortAsc": "Sort Ascending",
"sortDesc": "Sort Descending"
"betaNote": "Cette fonctionnalité est encore en développement.",
"moreInfo": "Plus d'informations peuvent être trouvées ici",
"logs": "Journaux",
"groupingField": "Champ de regroupement",
"insertAfter": "Insérer après",
"insertBefore": "Insérer avant",
"hideField": "Masquer le champ",
"sortAsc": "Trier par ordre croissant",
"sortDesc": "Trier par ordre décroissant"
},
"objects": {
"project": "Projet",
@ -197,21 +197,21 @@
"teamAndSettings": "Équipe & paramètres",
"apiDocs": "Docs API",
"importFromAirtable": "Importer depuis Airtable",
"generateToken": "Generate Token",
"APIsAndSupport": "Les API et la prise en charge",
"helpCenter": "Help center",
"swaggerDocumentation": "Swagger Documentation",
"generateToken": "Générer un jeton",
"APIsAndSupport": "API & Assistance",
"helpCenter": "Centre d'aide",
"swaggerDocumentation": "Documentation de Swagger",
"quickImportFrom": "Quick Import From",
"quickImport": "Quick Import",
"advancedSettings": "Advanced Settings",
"codeSnippet": "Code Snippet",
"keyboardShortcut": "Keyboard Shortcuts"
"quickImport": "Importation rapide",
"advancedSettings": "Paramètres avancés",
"codeSnippet": "Extrait de code",
"keyboardShortcut": "Raccourcis clavier"
},
"labels": {
"createdBy": "Created By",
"createdBy": "Créé par",
"notifyVia": "Notifier via",
"projName": "Nom du projet",
"tableName": "Nom de la table",
"tableName": "Nom du tableau",
"viewName": "Vue",
"viewLink": "Lien de vue",
"columnName": "Nom de la colonne",
@ -241,17 +241,17 @@
"chat": "Discussion",
"email": "Courriel",
"storage": "Stockage",
"uiAcl": "UI-ACL",
"uiAcl": "IU ACL",
"models": "Modèles",
"syncState": "État de synchronisation",
"created": "Créé",
"sqlOutput": "Sortie SQL",
"addOption": "Ajouter une option",
"qrCodeValueColumn": "Column with QR code value",
"barcodeValueColumn": "Column with Barcode value",
"barcodeFormat": "Barcode format",
"qrCodeValueColumn": "Colonne avec une valeur code QR",
"barcodeValueColumn": "Colonne avec une valeur code-barres",
"barcodeFormat": "Format du code-barres",
"qrCodeValueTooLong": "Trop de caractères pour un code QR",
"barcodeValueTooLong": "Too many characters for a barcode",
"barcodeValueTooLong": "Trop de caractères pour un code-barres",
"aggregateFunction": "Fonction agrégée",
"dbCreateIfNotExists": "Base de données : la créer si elle n'existe pas",
"clientKey": "Clé client",
@ -264,9 +264,9 @@
"columnName": "Inflexion - Nom de la colonne"
},
"community": {
"starUs1": "Étoile",
"starUs1": "Mettre en favoris",
"starUs2": "sur Github",
"bookDemo": "Planifier une démonstration gratuite",
"bookDemo": "Réserver une démonstration gratuite",
"getAnswered": "Obtenir des réponses à vos questions",
"joinDiscord": "Rejoindre le serveur Discord",
"joinCommunity": "Rejoindre la communauté NocoDB",
@ -277,40 +277,41 @@
"selectUserRole": "Sélectionner le rôle d'utilisateur",
"childTable": "Table enfant",
"childColumn": "Colonne enfant",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Mise à jour en cours",
"onDelete": "Suppression en cours",
"account": "Compte",
"language": "Language",
"primaryColor": "Primary Color",
"accentColor": "Accent Color",
"language": "Langue",
"primaryColor": "Couleur primaire",
"accentColor": "Couleur secondaire",
"customTheme": "Thème personnalisé",
"requestDataSource": "Request a data source you need?",
"apiKey": "Clé d'API",
"sharedBase": "Shared Base",
"importData": "Import Data",
"importData": "Importer des données",
"importSecondaryViews": "Importer des vues secondaires",
"importRollupColumns": "Import Rollup Columns",
"importLookupColumns": "Import Lookup Columns",
"importAttachmentColumns": "Import Attachment Columns",
"importFormulaColumns": "Import Formula Columns",
"noData": "No Data",
"goToDashboard": "Go to Dashboard",
"noData": "Aucune donnée",
"goToDashboard": "Accéder au tableau de bord",
"importing": "Importing",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
"weAreHiring": "We are Hiring!",
"downloadAllowed": "Téléchargement autorisé",
"weAreHiring": "Nous recrutons !",
"primaryKey": "Primary key",
"hasMany": "has many",
"belongsTo": "belongs to",
"hasMany": "a plusieurs",
"belongsTo": "appartient à",
"manyToMany": "have many to many relation",
"extraConnectionParameters": "Extra connection parameters",
"commentsOnly": "Comments only",
"commentsOnly": "Commentaires uniquement",
"documentation": "Documentation",
"subscribeNewsletter": "Subscribe to our weekly newsletter",
"subscribeNewsletter": "Abonnez-vous à notre newsletter hebdomadaire",
"signUpWithGoogle": "S’enregistrer avec Google",
"signInWithGoogle": "Se connecter avec Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!",
"agreeToTos": "En continuant, vous acceptez les Conditions d'Utilisation",
"welcomeToNc": "Bienvenue sur NocoDB !",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
@ -339,7 +340,7 @@
"translate": "Aider à la traduction",
"account": {
"authToken": "Copier le jeton d'authentification",
"swagger": "Swagger : les API REST",
"swagger": "Swagger : API REST",
"projInfo": "Copier les informations du projet",
"themes": "Thèmes"
},
@ -350,13 +351,13 @@
"share": "Partager",
"shareBase": {
"disable": "Désactiver la base partagée",
"enable": "N'importe qui avec le lien",
"enable": "N'importe qui disposant du lien",
"link": "Partager le lien de la base"
},
"invite": "Inviter",
"inviteMore": "Inviter plus",
"inviteTeam": "Inviter une équipe",
"inviteUser": "Invite User",
"inviteUser": "Inviter un utilisateur",
"inviteToken": "Inviter via un jeton",
"newUser": "Nouvel utilisateur",
"editUser": "Modifier l'utilisateur",
@ -375,17 +376,17 @@
"refreshTable": "Actualiser le tableau",
"renameTable": "Renommer le tableau",
"deleteTable": "Supprimer le tableau",
"addField": "Ajouter un nouveau champ à cette table",
"addField": "Ajouter un nouveau champ à ce tableau",
"setPrimary": "Définir comme valeur primaire",
"addRow": "Ajouter une nouvelle ligne",
"saveRow": "Enregistrer la ligne",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"saveAndExit": "Enregistrer et quitter",
"saveAndStay": "Enregistrer et rester",
"insertRow": "Insérer une nouvelle ligne",
"deleteRow": "Supprimer la ligne",
"deleteSelectedRow": "Supprimer les lignes sélectionnées",
"importExcel": "Importer depuis Excel",
"importCSV": "Import CSV",
"importCSV": "Importer un fichier CSV",
"downloadCSV": "Télécharger comme CSV",
"downloadExcel": "Télécharger comme XLSX",
"uploadCSV": "Téléverser un CSV",
@ -424,14 +425,14 @@
"editConnJson": "Éditer le JSON de connexion",
"sponsorUs": "Nous Parrainer",
"sendEmail": "ENVOYER UN EMAIL",
"addUserToProject": "Add user to project",
"addUserToProject": "Ajouter un utilisateur au projet",
"getApiSnippet": "Récupérer le Snippet API",
"clearCell": "Clear cell",
"addFilterGroup": "Add Filter Group",
"clearCell": "Vider la cellule",
"addFilterGroup": "Ajouter un groupe de filtres",
"linkRecord": "Link record",
"addNewRecord": "Add new record",
"useConnectionUrl": "Use Connection URL",
"toggleCommentsDraw": "Toggle comments draw",
"toggleCommentsDraw": "Afficher ou masquer le panneau des commentaires",
"expandRecord": "Expand Record",
"deleteRecord": "Delete Record",
"erd": {
@ -445,7 +446,7 @@
"collapseStack": "Collapse Stack",
"deleteStack": "Delete Stack",
"stackedBy": "Stacked By",
"chooseGroupingField": "Choose a Grouping Field",
"chooseGroupingField": "Choisir un champ de regroupement",
"addOrEditStack": "Add / Edit Stack"
}
},
@ -459,9 +460,9 @@
"dark": "Nuit (^⇧B)",
"light": "Jour (^⇧B)"
},
"addTable": "Ajouter une nouvelle table",
"addTable": "Ajouter un nouveau tableau",
"inviteMore": "Inviter plus d'utilisateurs",
"toggleNavDraw": "Basculer le tiroir de navigation",
"toggleNavDraw": "Afficher ou masquer le panneau de navigation",
"reloadApiToken": "Recharger les jetons API",
"generateNewApiToken": "Générer de nouveaux jetons d'API",
"addRole": "Ajouter un nouveau rôle",
@ -493,18 +494,18 @@
"searchModels": "Chercher un modèle",
"noItemsFound": "Aucun élément trouvé",
"defaultValue": "Valeur par défaut",
"filterByEmail": "Filtrer par courriel",
"filterQuery": "Filter query",
"selectField": "Select field"
"filterByEmail": "Rechercher un courriel",
"filterQuery": "Rechercher",
"selectField": "Sélectionner un champ"
},
"msg": {
"warning": {
"barcode": {
"renderError": "Barcode error - please check compatibility between input and barcode type"
"renderError": "Erreur de code-barres - veuillez vérifier la compatibiltié entre la donnée d'entrée et le type de code-barres"
},
"nonEditableFields": {
"computedFieldUnableToClear": "Warning: Computed field - unable to clear text",
"qrFieldsCannotBeDirectlyChanged": "Warning: QR fields cannot be directly changed."
"qrFieldsCannotBeDirectlyChanged": "Attention : les champs QR code ne peuvent pas être modifiés directement."
}
},
"info": {
@ -518,7 +519,7 @@
"upload_sub": "ou glisser-déposer un fichier",
"excelSupport": "Pris en charge: .xls, .xlsx, .xlsm, .ods, .ots",
"excelURL": "Définir l'URL du fichier Excel",
"csvURL": "Définir l'URL du fichier CSV",
"csvURL": "URL vers le fichier CSV",
"footMsg": "Nombre de lignes à analyser pour déduire le type de données",
"excelImport": "Les tableaux sont disponibles pour l'import",
"exportMetadata": "Voulez-vous exporter des métadonnées des méta-tables?",
@ -536,19 +537,19 @@
"formInput": "Entrer le libellé du formulaire",
"formHelpText": "Ajouter un texte d'aide",
"onlyCreator": "Visible uniquement pour les créateurs",
"formDesc": "Ajouter une description du formulaire",
"formDesc": "Ajouter une description au formulaire",
"beforeEnablePwd": "Restreindre l’accès à l’aide d’un mot de passe",
"afterEnablePwd": "L’accès est restreint par un mot de passe",
"privateLink": "Cette vue est partagée avec un lien privé",
"privateLinkAdditionalInfo": "Les personnes ayant le lien privé peuvent voir uniquement les cellules visibles de cette vue",
"afterFormSubmitted": "Après que le formulaire est soumis",
"afterFormSubmitted": "Après que le formulaire ait été soumis",
"apiOptions": "Accéder au projet via",
"submitAnotherForm": "Afficher le bouton \"Soumettre un autre formulaire\"",
"showBlankForm": "Montrer un formulaire vierge après 5 secondes",
"emailForm": "Écrivez-moi à",
"showSysFields": "Afficher les champs système",
"filterAutoApply": "Appliquer automatiquement",
"showMessage": "Montrer ce message",
"showMessage": "Montrer ce message ",
"viewNotShared": "La vue actuelle n'est pas partagée!",
"showAllViews": "Montrer toutes les vues partagées sur cette table",
"collabView": "Les collaborateurs avec des autorisations d'édition ou plus peuvent modifier la configuration de la vue.",
@ -574,7 +575,7 @@
},
"sponsor": {
"header": "Vous pouvez nous aider !",
"message": "Nous sommes une petite équipe travaillant à plein temps pour rendre Nocodb Open-Source. Nous croyons qu'un outil comme Nocodb devrait être disponible librement à chaque solveur de problèmes sur Internet."
"message": "Nous sommes une petite équipe travaillant à plein temps pour rendre NocoDB open-Source. Nous croyons qu'un outil comme NocoDB devrait être disponible librement à chaque solutionneur de problème sur Internet."
},
"loginMsg": "Se connecter à NocoDB",
"passwordRecovery": {
@ -583,7 +584,7 @@
"success": "Veuillez vérifier votre email pour réinitialiser le mot de passe"
},
"signUp": {
"superAdmin": "Vous serez le 'super admin'",
"superAdmin": "Vous serez le « super administrateur »",
"alreadyHaveAccount": "Avez-vous déjà un compte ?",
"workEmail": "Saisir votre adresse mail professionnelle",
"enterPassword": "Saisir votre mot de passe",
@ -599,33 +600,33 @@
},
"tablesMetadataInSync": "Les métadonnées de tables sont en synchronisation",
"addMultipleUsers": "Vous pouvez ajouter plusieurs courriels séparés par des virgules (,)",
"enterTableName": "Entrer le nom de la table",
"enterTableName": "Entrez le nom du tableau",
"addDefaultColumns": "Ajouter des colonnes par défaut",
"tableNameInDb": "Nom de la table tel qu'enregistré dans la base de données",
"airtable": {
"credentials": "Where to find this?"
"credentials": "Où trouver ceci ?"
},
"import": {
"clickOrDrag": "Click or drag file to this area to upload"
"clickOrDrag": "Cliquez ou glissez un fichier dans cette zone pour téléverser"
},
"metaDataRecreated": "Table metadata recreated successfully",
"invalidCredentials": "Invalid credentials",
"downloadingMoreFiles": "Downloading more files",
"copiedToClipboard": "Copied to clipboard",
"requriedFieldsCantBeMoved": "Required field can't be moved",
"invalidCredentials": "Identifiants invalides",
"downloadingMoreFiles": "Téléchargement de fichiers supplémentaires",
"copiedToClipboard": "Copié dans le presse-papier",
"requriedFieldsCantBeMoved": "Les champs requis ne peuvent pas être déplacés",
"updateNotAllowedWithoutPK": "Update not allowed for table which doesn't have primary key",
"autoIncFieldNotEditable": "Auto increment field is not editable",
"editingPKnotSupported": "Modification de la clé primaire non prise en charge",
"deletedCache": "Deleted cache successfully",
"cacheEmpty": "Cache is empty",
"cacheEmpty": "Le cache est vide",
"exportedCache": "Exported Cache Successfully",
"valueAlreadyInList": "This value is already in the list",
"noColumnsToUpdate": "No columns to update",
"tableDeleted": "Deleted table successfully",
"generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base",
"noColumnsToUpdate": "Aucune colonne à mettre à jour",
"tableDeleted": "Tableau supprimé avec succès",
"generatePublicShareableReadonlyBase": "Génère une base publique partagée en lecture seule",
"deleteViewConfirmation": "Êtes-vous sûr de vouloir effacer cette vue ?",
"deleteTableConfirmation": "Do you want to delete the table",
"showM2mTables": "Show M2M Tables",
"deleteTableConfirmation": "Voulez-vous supprimer ce tableau",
"showM2mTables": "Afficher les tables plusieurs à plusieurs",
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack.",
"computedFieldEditWarning": "Computed field: contents are read-only. Use column edit menu to reconfigure",
"computedFieldDeleteWarning": "Computed field: contents are read-only. Unable to clear content."
@ -640,26 +641,26 @@
"dbConnectionFailed": "Echec de connexion :",
"signUpRules": {
"emailReqd": "Adresse mail requise",
"emailInvalid": "L'adresse mail doit être valide",
"emailInvalid": "L'adresse électronique doit être valide",
"passwdRequired": "Mot de passe requis",
"passwdLength": "Votre mot de passe doit contenir au moins 8 caractères",
"passwdMismatch": "Les mots de passe ne correspondent pas",
"completeRuleSet": "At least 8 characters with one Uppercase, one number and one special character",
"atLeast8Char": "At least 8 characters",
"atLeastOneUppercase": "One Uppercase letter",
"atLeastOneNumber": "One Number",
"atLeastOneSpecialChar": "One special character",
"completeRuleSet": "Au moins 8 caractères avec une majuscule, un chiffre et un caractère spécial",
"atLeast8Char": "Au moins 8 caractères",
"atLeastOneUppercase": "Une lettre majuscule",
"atLeastOneNumber": "Un chiffre",
"atLeastOneSpecialChar": "Un caractère spécial",
"allowedSpecialCharList": "Allowed special character list"
},
"invalidURL": "Invalid URL",
"internalError": "Some internal error occurred",
"invalidURL": "URL invalide",
"internalError": "Une erreur interne est survenue",
"templateGeneratorNotFound": "Template Generator cannot be found!",
"fileUploadFailed": "Failed to upload file",
"fileUploadFailed": "Échec du téléversement du fichier",
"primaryColumnUpdateFailed": "Failed to update primary column",
"formDescriptionTooLong": "Data too long for Form Description",
"columnsRequired": "Following columns are required",
"selectAtleastOneColumn": "At least one column has to be selected",
"columnDescriptionNotFound": "Cannot find the destination column for",
"selectAtleastOneColumn": "Au moins une colonne doit être sélectionnée",
"columnDescriptionNotFound": "Impossible de trouver la colonne de destination pour",
"duplicateMappingFound": "Duplicate mapping found, please remove one of the mapping",
"nullValueViolatesNotNull": "Null value violates not-null constraint",
"sourceHasInvalidNumbers": "Source data contains some invalid numbers",
@ -675,14 +676,14 @@
"deleteRowFailed": "Failed to delete row",
"setFormDataFailed": "Failed to set form data",
"formViewUpdateFailed": "Échec de la mise à jour de la vue du formulaire",
"tableNameRequired": "Table name is required",
"nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _",
"followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required",
"projectNameExceeds50Characters": "Project name exceeds 50 characters",
"projectNameCannotStartWithSpace": "Project name cannot start with space",
"requiredField": "Required field",
"ipNotAllowed": "IP not allowed",
"tableNameRequired": "Nom du tableau requis",
"nameShouldStartWithAnAlphabetOr_": "Le nom doit commencer par une lettre de l'alphabet ou _",
"followingCharactersAreNotAllowed": "Les caractères suivants ne sont pas autorisés",
"columnNameRequired": "Nom de la colonne requis",
"projectNameExceeds50Characters": "Le nom du projet dépasse les 50 caractères",
"projectNameCannotStartWithSpace": "Le nom du projet ne peut pas commencer par un espace",
"requiredField": "Champ requis",
"ipNotAllowed": "Adresse IP non autorisée",
"targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type",
"theAcceptedFileTypeIsCsv": "The accepted file type is .csv",
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
@ -704,7 +705,7 @@
"projInfo": "Informations de projet copiées dans le presse-papier",
"inviteUrlCopy": "URL d'invitation copiée dans le presse-papier",
"createView": "Vue créée avec succès",
"formEmailSMTP": "Veuillez activer le plugin SMTP dans l'App Store pour permettre la notification par courrier électronique",
"formEmailSMTP": "Veuillez activer le plugin SMTP dans le magasin d'applications pour permettre la notification par courriel",
"collabView": "Vous êtes bien dans la vue collaborative",
"lockedView": "Vous êtes bien dans la vue vérouillée",
"futureRelease": "Bientôt disponible !"
@ -715,15 +716,15 @@
"pluginUninstalled": "Plugin uninstalled successfully",
"pluginSettingsSaved": "Plugin settings saved successfully",
"pluginTested": "Successfully tested plugin settings",
"tableRenamed": "Table renamed successfully",
"tableRenamed": "Tableau renommé avec succès",
"viewDeleted": "Vue effacée avec succès",
"primaryColumnUpdated": "Successfully updated as primary column",
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"tableDataExported": "Toutes les données du tableau ont été exportées avec succès",
"updated": "Mise à jour réussie",
"sharedViewDeleted": "Vue partagée effacée avec succès",
"userDeleted": "User deleted successfully",
"userDeleted": "Utilisateur supprimé avec succès",
"viewRenamed": "Vue renommée avec succès",
"tokenGenerated": "Token generated successfully",
"tokenGenerated": "Jeton généré avec succès",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
@ -734,14 +735,14 @@
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
"tableDataImported": "Successfully imported table data",
"tableDataImported": "Données du tableau importées avec succès",
"webhookUpdated": "Webhook details updated successfully",
"webhookDeleted": "Hook deleted successfully",
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"columnUpdated": "Colonne mise à jour",
"columnCreated": "Colonne créée",
"passwordChanged": "Mot de passe modifié avec succès. Veuillez vous reconnecter.",
"settingsSaved": "Paramètres modifiés avec succès",
"roleUpdated": "Role updated successfully"
}
}

1
packages/nc-gui/lang/he.json

@ -277,6 +277,7 @@
"selectUserRole": "בחר תפקיד משתמש",
"childTable": "טבלת ילדים",
"childColumn": "טור ילדים",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "על עדכון",
"onDelete": "על מחיקה",
"account": "Account",

1
packages/nc-gui/lang/hi.json

@ -277,6 +277,7 @@
"selectUserRole": "Select User Role",
"childTable": "Child table",
"childColumn": "Child column",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "On Update",
"onDelete": "On Delete",
"account": "Account",

1
packages/nc-gui/lang/hr.json

@ -277,6 +277,7 @@
"selectUserRole": "Odaberite korisničku ulogu",
"childTable": "Dječji stol",
"childColumn": "Dječji stupac",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Na ažuriranje",
"onDelete": "Na brisanje",
"account": "Account",

1
packages/nc-gui/lang/id.json

@ -277,6 +277,7 @@
"selectUserRole": "Pilih Peran Pengguna",
"childTable": "Tabel Anak",
"childColumn": "Kolom anak.",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Pada pembaruan",
"onDelete": "Hapus",
"account": "Account",

1
packages/nc-gui/lang/it.json

@ -277,6 +277,7 @@
"selectUserRole": "Seleziona il ruolo utente",
"childTable": "Sottotabella",
"childColumn": "Sottocolonna",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "All'aggiornamento",
"onDelete": "All'eliminazione",
"account": "Account",

1
packages/nc-gui/lang/ja.json

@ -277,6 +277,7 @@
"selectUserRole": "ユーザーのロールを選択してください",
"childTable": "子テーブル",
"childColumn": "子カラム",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "更新中",
"onDelete": "削除中",
"account": "アカウント",

1
packages/nc-gui/lang/ko.json

@ -277,6 +277,7 @@
"selectUserRole": "사용자 역할을 선택하세요",
"childTable": "자식 테이블",
"childColumn": "자식 컬럼",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "업데이트 시 ",
"onDelete": "삭제 시",
"account": "Account",

1
packages/nc-gui/lang/lv.json

@ -277,6 +277,7 @@
"selectUserRole": "Izvēlēties lietotāja lomu",
"childTable": "Apakštabula",
"childColumn": "Apakškolonna",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Atjaunojot",
"onDelete": "Dzēšot",
"account": "Account",

1
packages/nc-gui/lang/nl.json

@ -277,6 +277,7 @@
"selectUserRole": "Selecteer de rol van de gebruiker",
"childTable": "Child Tabel",
"childColumn": "Child Kolom",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Bij update",
"onDelete": "Bij verwijdering",
"account": "Account",

1
packages/nc-gui/lang/no.json

@ -277,6 +277,7 @@
"selectUserRole": "Velg Brukerrolle",
"childTable": "Barnbord",
"childColumn": "Barn kolonne",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "På oppdatering",
"onDelete": "På slette",
"account": "Account",

1
packages/nc-gui/lang/pl.json

@ -277,6 +277,7 @@
"selectUserRole": "Wybierz rolę użytkownika",
"childTable": "Stół dziecka.",
"childColumn": "Kolumna dla dzieci",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Na aktualizacji",
"onDelete": "Na delete.",
"account": "Konto",

1
packages/nc-gui/lang/pt.json

@ -277,6 +277,7 @@
"selectUserRole": "Selecione a função do usuário",
"childTable": "Mesa de criança",
"childColumn": "Coluna de criança",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Na atualização",
"onDelete": "Em excluir",
"account": "Account",

1
packages/nc-gui/lang/pt_BR.json

@ -277,6 +277,7 @@
"selectUserRole": "Selecione a função do usuário",
"childTable": "Mesa de criança",
"childColumn": "Coluna de criança",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Na atualização",
"onDelete": "Em excluir",
"account": "Account",

1
packages/nc-gui/lang/ru.json

@ -277,6 +277,7 @@
"selectUserRole": "Выберите роль пользователя",
"childTable": "Дочерняя таблица",
"childColumn": "Дочерний столбец",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "При обновлении",
"onDelete": "При удалении",
"account": "Учётная запись",

1
packages/nc-gui/lang/sk.json

@ -277,6 +277,7 @@
"selectUserRole": "Select User Role",
"childTable": "Child table",
"childColumn": "Child column",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "On Update",
"onDelete": "On Delete",
"account": "Account",

1
packages/nc-gui/lang/sl.json

@ -277,6 +277,7 @@
"selectUserRole": "Izberite vlogo uporabnika",
"childTable": "Otroška miza",
"childColumn": "Otroška stolpec",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Na posodobitvi",
"onDelete": "Na izbrisu",
"account": "Account",

1
packages/nc-gui/lang/sv.json

@ -277,6 +277,7 @@
"selectUserRole": "Välj användarroll",
"childTable": "Barnbord",
"childColumn": "Barnkolonn",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Vid uppdatering",
"onDelete": "På radera",
"account": "Account",

1
packages/nc-gui/lang/th.json

@ -277,6 +277,7 @@
"selectUserRole": "เลอกบทบาทผใช",
"childTable": "โตะเดก",
"childColumn": "คอลมนเดก",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "เมออปเดต",
"onDelete": "ในการลบ",
"account": "Account",

1
packages/nc-gui/lang/tr.json

@ -277,6 +277,7 @@
"selectUserRole": "Kullanıcı Rolü Seç",
"childTable": "Alt tablo",
"childColumn": "Alt sütun",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Güncellenince",
"onDelete": "Silinince",
"account": "Account",

1
packages/nc-gui/lang/uk.json

@ -277,6 +277,7 @@
"selectUserRole": "Виберіть Роль користувача",
"childTable": "Дитячий стіл",
"childColumn": "Дитяча колонка",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "На оновлення",
"onDelete": "На видалі",
"account": "Обліковий запис",

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

@ -277,6 +277,7 @@
"selectUserRole": "Chọn vai trò người dùng",
"childTable": "Bảng con",
"childColumn": "Cột trẻ con.",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "Trên bản cập nhật",
"onDelete": "Trên xóa",
"account": "Tài khoản",

1
packages/nc-gui/lang/zh-Hans.json

@ -277,6 +277,7 @@
"selectUserRole": "选择用户角色",
"childTable": "子表",
"childColumn": "子列",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "更新",
"onDelete": "删除",
"account": "帐户",

1
packages/nc-gui/lang/zh-Hant.json

@ -277,6 +277,7 @@
"selectUserRole": "選擇使用者角色",
"childTable": "子表格",
"childColumn": "子欄",
"linkToAnotherRecord": "Link to another record",
"onUpdate": "更新",
"onDelete": "在刪除",
"account": "Account",

4
packages/nc-gui/nuxt.config.ts

@ -139,8 +139,8 @@ export default defineNuxtConfig({
}),
monacoEditorPlugin({
languageWorkers: ['json'],
customDistPath: (root: string, buildOutDir: string, base: string) => {
return buildOutDir + '/' + 'monacoeditorwork'
customDistPath: (root: string, buildOutDir: string) => {
return `${buildOutDir}/` + `monacoeditorwork`
},
}),
PurgeIcons({

26
packages/nc-gui/package-lock.json generated

@ -101,7 +101,7 @@
}
},
"../nocodb-sdk": {
"version": "0.101.0-beta.0",
"version": "0.101.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.21.1",
@ -10549,9 +10549,9 @@
"dev": true
},
"node_modules/json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
@ -15752,9 +15752,9 @@
}
},
"node_modules/tsconfig-paths/node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.0"
@ -25416,9 +25416,9 @@
"dev": true
},
"json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"jsonc-eslint-parser": {
@ -29317,9 +29317,9 @@
},
"dependencies": {
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"

2
packages/nc-lib-gui/package.json

@ -1,6 +1,6 @@
{
"name": "nc-lib-gui",
"version": "0.101.0-beta.0",
"version": "0.101.2",
"description": "NocoDB GUI",
"author": {
"name": "NocoDB",

24
packages/nc-plugin/package-lock.json generated

@ -84,9 +84,9 @@
}
},
"node_modules/@babel/core/node_modules/json5": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
@ -7629,9 +7629,9 @@
"dev": true
},
"node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.0"
@ -12312,9 +12312,9 @@
},
"dependencies": {
"json5": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
"integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"semver": {
@ -18140,9 +18140,9 @@
"dev": true
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"

112
packages/noco-docs/content/en/getting-started/installation.md

@ -298,7 +298,6 @@ npm start
<alert>
If your service fails to start, you may check the logs in ECS console or in Cloudwatch. Generally it fails due to the connection between ECS container and NC_DB. Make sure the security groups have the correct inbound and outbound rules.
</alert>
```
</details>
@ -444,62 +443,61 @@ npm start
See [here](https://gist.github.com/Zamana/e9281d736f9e9ce5882c6f4b140a590e) provided by [C. R. Zamana](https://github.com/Zamana).
## Production Setup
It is mandatory to configure `NC_DB` environment variables for production usecases.
### Environment variables
| Variable | Mandatory | Comments | If absent | |
|------------------------------------|-----------|-------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|---|
| NC_DB | Yes | See our database URLs | A local SQLite will be created in root folder | |
| NC_DB_JSON | Yes | Can be used instead of `NC_DB` and value should be valid knex connection JSON | | |
| NC_DB_JSON_FILE | Yes | Can be used instead of `NC_DB` and value should be a valid path to knex connection JSON | | |
| DATABASE_URL | No | JDBC URL Format. Can be used instead of NC_DB. | | |
| DATABASE_URL_FILE | No | Can be used instead of DATABASE_URL: path to file containing JDBC URL Format. | | |
| NC_AUTH_JWT_SECRET | Yes | JWT secret used for auth and storing other secrets | A Random secret will be generated | |
| PORT | No | For setting app running port | `8080` | |
| DB_QUERY_LIMIT_DEFAULT | No | Default pagination limit | 25 | |
| DB_QUERY_LIMIT_MAX | No | Maximum allowed pagination limit | 1000 | |
| DB_QUERY_LIMIT_MIN | No | Minimum allowed pagination limit | 1 | |
| NC_TOOL_DIR | No | App directory to keep metadata and app related files | Defaults to current working directory. In docker maps to `/usr/app/data/` for mounting volume. | |
| NC_PUBLIC_URL | Yes | Used for sending Email invitations | Best guess from http request params | |
| NC_JWT_EXPIRES_IN | No | JWT token expiry time | `10h` | |
| NC_CONNECT_TO_EXTERNAL_DB_DISABLED | No | Disable Project creation with external database | | |
| NC_INVITE_ONLY_SIGNUP | No | <strong>Removed</strong> since version <kbd>0.99.0</kbd> and now it's recommended to use [super admin settings menu](/setup-and-usages/account-settings#enable--disable-signup). <br><br>Allow users to signup only via invite url, value should be any non-empty string. | | |
| NUXT_PUBLIC_NC_BACKEND_URL | No | Custom Backend URL | ``http://localhost:8080`` will be used | |
| NC_REQUEST_BODY_SIZE | No | Request body size [limit](https://expressjs.com/en/resources/middleware/body-parser.html#limit) | `1048576` | |
| NC_EXPORT_MAX_TIMEOUT | No | After NC_EXPORT_MAX_TIMEOUT csv gets downloaded in batches | Default value 5000(in millisecond) will be used | |
| NC_DISABLE_TELE | No | Disable telemetry | | |
| NC_DASHBOARD_URL | No | Custom dashboard url path | `/dashboard` | |
| NC_GOOGLE_CLIENT_ID | No | Google client id to enable google authentication | | |
| NC_GOOGLE_CLIENT_SECRET | No | Google client secret to enable google authentication | | |
| NC_MIGRATIONS_DISABLED | No | Disable NocoDB migration | | |
| NC_MIN | No | If set to any non-empty string the default splash screen(initial welcome animation) and matrix screensaver will disable | | |
| NC_SENTRY_DSN | No | For Sentry monitoring | | |
| NC_REDIS_URL | No | Custom Redis URL. Example: `redis://:authpassword@127.0.0.1:6380/4` | Meta data will be stored in memory | |
| NC_DISABLE_ERR_REPORT | No | Disable error reporting | | |
| NC_DISABLE_CACHE | No | To be used only while debugging. On setting this to `true` - meta data be fetched from db instead of redis/cache. | `false` | |
| NC_BASEURL_INTERNAL | No | Used as base url for internal(server) API calls | Default value in docker will be `http://localhost:$PORT` and in all other case it's populated from request object | |
| AWS_ACCESS_KEY_ID | No | For Litestream - S3 access key id | If Litestream is configured and NC_DB is not present. SQLite gets backed up to S3 | |
| AWS_SECRET_ACCESS_KEY | No | For Litestream - S3 secret access key | If Litestream is configured and NC_DB is not present. SQLite gets backed up to S3 | |
| AWS_BUCKET | No | For Litestream - S3 bucket | If Litestream is configured and NC_DB is not present. SQLite gets backed up to S3 | |
| AWS_BUCKET_PATH | No | For Litestream - S3 bucket path (like folder within S3 bucket) | If Litestream is configured and NC_DB is not present. SQLite gets backed up to S3 | |
| NC_SMTP_FROM | No | For SMTP plugin - Email sender address | | |
| NC_SMTP_HOST | No | For SMTP plugin - SMTP host value | | |
| NC_SMTP_PORT | No | For SMTP plugin - SMTP port value | | |
| NC_SMTP_USERNAME | No | For SMTP plugin (Optional) - SMTP username value for authentication | | |
| NC_SMTP_PASSWORD | No | For SMTP plugin (Optional) - SMTP password value for authentication | | |
| NC_SMTP_SECURE | No | For SMTP plugin (Optional) - To enable secure set value as `true` any other value treated as false | | |
| NC_SMTP_IGNORE_TLS | No | For SMTP plugin (Optional) - To ignore tls set value as `true` any other value treated as false. For more info visit https://nodemailer.com/smtp/ | | |
| NC_S3_BUCKET_NAME | No | For S3 storage plugin - AWS S3 bucket name | | |
| NC_S3_REGION | No | For S3 storage plugin - AWS S3 region | | |
| NC_S3_ACCESS_KEY | No | For S3 storage plugin - AWS access key credential for accessing resource | | |
| NC_S3_ACCESS_SECRET | No | For S3 storage plugin - AWS access secret credential for accessing resource | | |
| NC_ADMIN_EMAIL | No | For updating/creating super admin with provided email and password | | |
| NC_ATTACHMENT_FIELD_SIZE | No | For setting the attachment field size(in Bytes) | Defaults to 20MB | |
| NC_ADMIN_PASSWORD | No | For updating/creating super admin with provided email and password. Your password should have at least 8 letters with one uppercase, one number and one special letter(Allowed special chars <code>$&+,:;=?@#&#124;'.^*()%!_-"</code> ) | | |
| NODE_OPTIONS | No | For passing Node.js [options](https://nodejs.org/api/cli.html#node_optionsoptions) to instance | | |
| NC_MINIMAL_DBS | No | Create a new SQLite file for each project. All the db files are stored in `nc_minimal_dbs` folder in current working directory. (This option restricts project creation on external sources) | | |
## Environment Variables
Here is the list of the environment variables that you can use. Even though they are optional, it is **recommended** to configure `NC_DB`, `NC_AUTH_JWT_SECRET`, and `NC_PUBLIC_URL` for production use cases.
| Variable | Comments | If absent | |
|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|---|
| NC_DB | See our database URLs | A local SQLite will be created in root folder if `NC_DB` is not provided | |
| NC_DB_JSON | Can be used instead of `NC_DB` and value should be valid knex connection JSON | | |
| NC_DB_JSON_FILE | Can be used instead of `NC_DB` and value should be a valid path to knex connection JSON | | |
| DATABASE_URL | JDBC URL Format. Can be used instead of NC_DB. | | |
| DATABASE_URL_FILE | Can be used instead of DATABASE_URL: path to file containing JDBC URL Format. | | |
| NC_AUTH_JWT_SECRET | JWT secret used for auth and storing other secrets | A random secret will be generated | |
| PORT | For setting app running port | `8080` | |
| DB_QUERY_LIMIT_DEFAULT | Default pagination limit | 25 | |
| DB_QUERY_LIMIT_MAX | Maximum allowed pagination limit | 1000 | |
| DB_QUERY_LIMIT_MIN | Minimum allowed pagination limit | 1 | |
| NC_TOOL_DIR | App directory to keep metadata and app related files | Defaults to current working directory. In docker maps to `/usr/app/data/` for mounting volume. | |
| NC_PUBLIC_URL | Used for sending Email invitations | Best guess from http request params | |
| NC_JWT_EXPIRES_IN | JWT token expiry time | `10h` | |
| NC_CONNECT_TO_EXTERNAL_DB_DISABLED | Disable Project creation with external database | | |
| NC_INVITE_ONLY_SIGNUP | Removed since version 0.99.0 and now it's recommended to use [super admin settings menu](/setup-and-usages/account-settings#enable--disable-signup). Allow users to signup only via invite url, value should be any non-empty string. | | |
| NUXT_PUBLIC_NC_BACKEND_URL | Custom Backend URL | ``http://localhost:8080`` will be used | |
| NC_REQUEST_BODY_SIZE | Request body size [limit](https://expressjs.com/en/resources/middleware/body-parser.html#limit) | `1048576` | |
| NC_EXPORT_MAX_TIMEOUT | After NC_EXPORT_MAX_TIMEOUT csv gets downloaded in batches | Default value 5000(in millisecond) will be used | |
| NC_DISABLE_TELE | Disable telemetry | | |
| NC_DASHBOARD_URL | Custom dashboard url path | `/dashboard` | |
| NC_GOOGLE_CLIENT_ID | Google client id to enable google authentication | | |
| NC_GOOGLE_CLIENT_SECRET | Google client secret to enable google authentication | | |
| NC_MIGRATIONS_DISABLED | Disable NocoDB migration | | |
| NC_MIN | If set to any non-empty string the default splash screen(initial welcome animation) and matrix screensaver will disable | | |
| NC_SENTRY_DSN | For Sentry monitoring | | |
| NC_REDIS_URL | Custom Redis URL. Example: `redis://:authpassword@127.0.0.1:6380/4` | Meta data will be stored in memory | |
| NC_DISABLE_ERR_REPORT | Disable error reporting | | |
| NC_DISABLE_CACHE | To be used only while debugging. On setting this to `true` - meta data be fetched from db instead of redis/cache. | `false` | |
| NC_BASEURL_INTERNAL | Used as base url for internal(server) API calls | Default value in docker will be `http://localhost:$PORT` and in all other case it's populated from request object | |
| AWS_ACCESS_KEY_ID | For Litestream - S3 access key id | If Litestream is configured and `NC_DB` is not present. SQLite gets backed up to S3 | |
| AWS_SECRET_ACCESS_KEY | For Litestream - S3 secret access key | If Litestream is configured and `NC_DB` is not present. SQLite gets backed up to S3 | |
| AWS_BUCKET | For Litestream - S3 bucket | If Litestream is configured and `NC_DB` is not present. SQLite gets backed up to S3 | |
| AWS_BUCKET_PATH | For Litestream - S3 bucket path (like folder within S3 bucket) | If Litestream is configured and `NC_DB` is not present. SQLite gets backed up to S3 | |
| NC_SMTP_FROM | For SMTP plugin - Email sender address | | |
| NC_SMTP_HOST | For SMTP plugin - SMTP host value | | |
| NC_SMTP_PORT | For SMTP plugin - SMTP port value | | |
| NC_SMTP_USERNAME | For SMTP plugin (Optional) - SMTP username value for authentication | | |
| NC_SMTP_PASSWORD | For SMTP plugin (Optional) - SMTP password value for authentication | | |
| NC_SMTP_SECURE | For SMTP plugin (Optional) - To enable secure set value as `true` any other value treated as false | | |
| NC_SMTP_IGNORE_TLS | For SMTP plugin (Optional) - To ignore tls set value as `true` any other value treated as false. For more info visit https://nodemailer.com/smtp/ | | |
| NC_S3_BUCKET_NAME | For S3 storage plugin - AWS S3 bucket name | | |
| NC_S3_REGION | For S3 storage plugin - AWS S3 region | | |
| NC_S3_ACCESS_KEY | For S3 storage plugin - AWS access key credential for accessing resource | | |
| NC_S3_ACCESS_SECRET | For S3 storage plugin - AWS access secret credential for accessing resource | | |
| NC_ADMIN_EMAIL | For updating/creating super admin with provided email and password | | |
| NC_ATTACHMENT_FIELD_SIZE | For setting the attachment field size(in Bytes) | Defaults to 20MB | |
| NC_ADMIN_PASSWORD | For updating/creating super admin with provided email and password. Your password should have at least 8 letters with one uppercase, one number and one special letter(Allowed special chars $&+,:;=?@#\|'.^*()%!_-" ) | | |
| NODE_OPTIONS | For passing Node.js [options](https://nodejs.org/api/cli.html#node_optionsoptions) to instance | | |
| NC_MINIMAL_DBS | Create a new SQLite file for each project. All the db files are stored in `nc_minimal_dbs` folder in current working directory. (This option restricts project creation on external sources) | | |
## Sample Demos

8
packages/noco-docs/content/en/setup-and-usages/account-settings.md

@ -45,4 +45,10 @@ Signup without an invitation is disabled by default and can be managed from UI b
You can also manage the app store plugins here.
![image](https://user-images.githubusercontent.com/35857179/203267619-24a8f5f5-1c8c-4419-a7a1-be4377fe6216.png)
![image](https://user-images.githubusercontent.com/35857179/203267619-24a8f5f5-1c8c-4419-a7a1-be4377fe6216.png)
## License
You can configure NocoDB Enterprise `License key` here
![Screenshot 2023-01-12 at 2 30 57 PM](https://user-images.githubusercontent.com/86527202/212023989-0129af0a-689d-465e-bdda-3d54399ea6b7.png)

34571
packages/noco-docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

2
packages/noco-docs/package.json

@ -23,6 +23,6 @@
},
"dependencies": {
"@nuxt/content-theme-docs": "0.7.2",
"nuxt": "^3.0.0"
"nuxt": "^2.15.2"
}
}

17
packages/nocodb-sdk/package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "nocodb-sdk",
"version": "0.101.0-beta.0",
"version": "0.101.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nocodb-sdk",
"version": "0.101.0-beta.0",
"version": "0.101.2",
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.21.1",
@ -2653,11 +2653,10 @@
"license": "MIT"
},
"node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.0"
},
@ -5818,9 +5817,9 @@
"dev": true
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"

2
packages/nocodb-sdk/package.json

@ -1,6 +1,6 @@
{
"name": "nocodb-sdk",
"version": "0.101.0-beta.0",
"version": "0.101.2",
"description": "NocoDB SDK",
"main": "build/main/index.js",
"typings": "build/main/index.d.ts",

2
packages/nocodb-sdk/src/lib/Api.ts

@ -4083,6 +4083,7 @@ export class Api<
* @response `200` `(ViewType & {
relatedMetas?: any,
client?: string,
base_id?: string,
columns?: ((GridColumnType | FormColumnType | GalleryColumnType) & ColumnType),
model?: TableType,
@ -4096,6 +4097,7 @@ export class Api<
ViewType & {
relatedMetas?: any;
client?: string;
base_id?: string;
columns?: (GridColumnType | FormColumnType | GalleryColumnType) &
ColumnType;
model?: TableType;

17
packages/nocodb-sdk/src/lib/formulaHelpers.ts

@ -8,21 +8,28 @@ export const jsepCurlyHook = {
jsep.hooks.add('gobble-token', function gobbleCurlyLiteral(env) {
const OCURLY_CODE = 123; // {
const CCURLY_CODE = 125; // }
let start = -1;
const { context } = env;
if (
!jsep.isIdentifierStart(context.code) &&
context.code === OCURLY_CODE
) {
if (start == -1) {
start = context.index;
}
context.index += 1;
const nodes = context.gobbleExpressions(CCURLY_CODE);
context.gobbleExpressions(CCURLY_CODE);
if (context.code === CCURLY_CODE) {
context.index += 1;
env.node = {
type: jsep.IDENTIFIER,
// column name with space would break it down to jsep.IDENTIFIER + jsep.LITERAL
// either take node.name for jsep.IDENTIFIER
// or take node.value for jsep.LITERAL
name: nodes.map((node) => node.name || node.value).join(' '),
name: /{{(.*?)}}/.test(context.expr)
? // start would be the position of the first curly bracket
// add 2 to point to the first character for expressions like {{col1}}
context.expr.slice(start + 2, context.index - 1)
: // start would be the position of the first curly bracket
// add 1 to point to the first character for expressions like {col1}
context.expr.slice(start + 1, context.index - 1),
};
return env.node;
} else {

1
packages/nocodb/README.md

@ -19,3 +19,4 @@ If you wish to combine the frontend and backend together in your local developme
```
In this case, whenever there are any changes made in the frontend, you need to run ``npm run build:copy`` under ``packages/nc-gui/``.

80
packages/nocodb/package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "nocodb",
"version": "0.101.0-beta.0",
"version": "0.101.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nocodb",
"version": "0.101.0-beta.0",
"version": "0.101.2",
"license": "AGPL-3.0-or-later",
"dependencies": {
"@google-cloud/storage": "^5.7.2",
@ -65,7 +65,7 @@
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
"nc-help": "0.2.85",
"nc-lib-gui": "0.101.0-beta.0",
"nc-lib-gui": "0.101.2",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk",
@ -153,7 +153,7 @@
}
},
"../nocodb-sdk": {
"version": "0.101.0-beta.0",
"version": "0.101.1",
"license": "AGPL-3.0-or-later",
"dependencies": {
"axios": "^0.21.1",
@ -9427,9 +9427,9 @@
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"node_modules/json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
@ -9904,9 +9904,9 @@
}
},
"node_modules/luxon": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw==",
"engines": {
"node": "*"
}
@ -11161,9 +11161,9 @@
}
},
"node_modules/nc-lib-gui": {
"version": "0.101.0-beta.0",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.101.0-beta.0.tgz",
"integrity": "sha512-7NuNn16fYm397ZcdqDY21f2smydPzk6gog9qZm8UdDn5i3Y49OnlGwpUWkqFX+ZM4T18uJ3rd+1Fe2T2sKWrWg==",
"version": "0.101.2",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.101.2.tgz",
"integrity": "sha512-mwvSo+pTirZ8XIL3pe9tq2zdOLeigrzdYFHadsJ/xEIx+0U0NNlJglKtnKzr+Zy1wH0LceQCjOZV28v45AtfqQ==",
"dependencies": {
"express": "^4.17.1"
}
@ -16508,9 +16508,9 @@
}
},
"node_modules/tsconfig-paths/node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.0"
@ -17782,9 +17782,9 @@
}
},
"node_modules/webpack-cli/node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.0"
@ -18197,9 +18197,9 @@
}
},
"node_modules/webpack/node_modules/json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"dependencies": {
"minimist": "^1.2.0"
@ -26279,9 +26279,9 @@
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
},
"json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"jsonfile": {
@ -26663,9 +26663,9 @@
}
},
"luxon": {
"version": "1.28.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ=="
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw=="
},
"mailersend": {
"version": "1.3.2",
@ -27687,9 +27687,9 @@
}
},
"nc-lib-gui": {
"version": "0.101.0-beta.0",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.101.0-beta.0.tgz",
"integrity": "sha512-7NuNn16fYm397ZcdqDY21f2smydPzk6gog9qZm8UdDn5i3Y49OnlGwpUWkqFX+ZM4T18uJ3rd+1Fe2T2sKWrWg==",
"version": "0.101.2",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.101.2.tgz",
"integrity": "sha512-mwvSo+pTirZ8XIL3pe9tq2zdOLeigrzdYFHadsJ/xEIx+0U0NNlJglKtnKzr+Zy1wH0LceQCjOZV28v45AtfqQ==",
"requires": {
"express": "^4.17.1"
}
@ -31858,9 +31858,9 @@
},
"dependencies": {
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
@ -32898,9 +32898,9 @@
}
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
@ -33195,9 +33195,9 @@
"dev": true
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"

4
packages/nocodb/package.json

@ -1,6 +1,6 @@
{
"name": "nocodb",
"version": "0.101.0-beta.0",
"version": "0.101.2",
"description": "NocoDB Backend",
"main": "dist/bundle.js",
"author": {
@ -105,7 +105,7 @@
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
"nc-help": "0.2.85",
"nc-lib-gui": "0.101.0-beta.0",
"nc-lib-gui": "0.101.2",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk",

2
packages/nocodb/src/lib/Noco.ts

@ -104,7 +104,7 @@ export default class Noco {
constructor() {
process.env.PORT = process.env.PORT || '8080';
// todo: move
process.env.NC_VERSION = '0098005';
process.env.NC_VERSION = '0100002';
// if env variable NC_MINIMAL_DBS is set, then disable project creation with external sources
if (process.env.NC_MINIMAL_DBS) {

132
packages/nocodb/src/lib/db/sql-client/lib/mysql/mysql.queries.ts

@ -212,7 +212,7 @@ AND t.table_name=?;`,
kcu.TABLE_NAME as tn,
kcu.COLUMN_NAME as cn,
kcu.POSITION_IN_UNIQUE_CONSTRAINT as puc,
kcu.REFERENCED_TABLE_NAME as rtn,
kcu.REFERENCED_TABLE_NAME as rtn,
kcu.REFERENCED_COLUMN_NAME as rcn,
rc.MATCH_OPTION as mo,
rc.UPDATE_RULE as ur,
@ -222,7 +222,8 @@ AND t.table_name=?;`,
information_schema.KEY_COLUMN_USAGE AS kcu
INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc ON
kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
Group by kcu.CONSTRAINT_NAME,
Group by
kcu.CONSTRAINT_NAME,
kcu.TABLE_NAME,
kcu.COLUMN_NAME,
kcu.POSITION_IN_UNIQUE_CONSTRAINT,
@ -230,71 +231,90 @@ AND t.table_name=?;`,
kcu.REFERENCED_COLUMN_NAME,
rc.MATCH_OPTION,
rc.UPDATE_RULE,
rc.DELETE_RULE ,kcu.table_schema
rc.DELETE_RULE ,
kcu.table_schema
Having
kcu.table_schema = ?
AND kcu.referenced_column_name IS NOT NULL
AND kcu.table_name=?`,
AND kcu.table_name =?`,
paramsHints: ['database', 'tn'],
},
},
relationListAll: {
default: {
sql: `
sql: `SELECT
kcu.constraint_name AS cstn,
kcu.table_name AS tn,
kcu.column_name AS cn,
kcu.position_in_unique_constraint AS puc,
kcu.referenced_table_name AS rtn,
kcu.referenced_column_name AS rcn,
rc.match_option AS mo,
rc.update_rule AS ur,
rc.delete_rule AS dr,
kcu.table_schema AS ts
FROM
(
SELECT
table_schema,
constraint_name,
table_name,
column_name,
position_in_unique_constraint,
referenced_table_name,
referenced_column_name
FROM
information_schema.KEY_COLUMN_USAGE
WHERE
table_schema = :databaseName) AS kcu
INNER JOIN
(
SELECT
constraint_schema,
match_option,
update_rule,
delete_rule,
constraint_name
FROM
information_schema.REFERENTIAL_CONSTRAINTS
WHERE
constraint_schema = :databaseName) AS rc ON
kcu.constraint_name = rc.constraint_name
AND kcu.table_schema = rc.constraint_schema
INNER JOIN
(
SELECT
table_schema,
table_name,
column_name
FROM
information_schema.COLUMNS
WHERE
table_schema = :databaseName
AND table_name IN (
SELECT
kcu.CONSTRAINT_NAME AS cstn,
kcu.TABLE_NAME AS tn,
kcu.COLUMN_NAME AS cn,
kcu.POSITION_IN_UNIQUE_CONSTRAINT AS puc,
kcu.REFERENCED_TABLE_NAME AS rtn,
kcu.REFERENCED_COLUMN_NAME AS rcn,
rc.MATCH_OPTION AS mo,
rc.UPDATE_RULE AS ur,
rc.DELETE_RULE AS dr,
kcu.table_schema AS ts
table_name
FROM
(SELECT
table_schema,
CONSTRAINT_NAME,
TABLE_NAME,
COLUMN_NAME,
POSITION_IN_UNIQUE_CONSTRAINT,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM
information_schema.KEY_COLUMN_USAGE
WHERE
table_schema = :databaseName) AS kcu
INNER JOIN
(SELECT
CONSTRAINT_SCHEMA,
MATCH_OPTION,
UPDATE_RULE,
DELETE_RULE,
CONSTRAINT_NAME
FROM
information_schema.REFERENTIAL_CONSTRAINTS
WHERE
CONSTRAINT_SCHEMA = :databaseName) AS rc ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
AND kcu.table_schema = rc.CONSTRAINT_SCHEMA
INNER JOIN
(SELECT
table_schema, TABLE_NAME, COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
table_schema = :databaseName AND TABLE_NAME IN (SELECT
TABLE_NAME
FROM
INFORMATION_SCHEMA.TABLES
WHERE
table_schema = :databaseName
AND LOWER(TABLE_TYPE) = 'base table')) AS col ON col.table_schema = kcu.table_schema
AND col.table_name = kcu.TABLE_NAME
AND kcu.REFERENCED_COLUMN_NAME IS NOT NULL
GROUP BY cstn , tn , rcn , cn , puc , rtn ,cn, mo , ur , dr , ts`,
information_schema.TABLES
WHERE
table_schema = :databaseName
AND Lower(table_type) = 'base table')) AS col ON
col.table_schema = kcu.table_schema
AND col.table_name = kcu.table_name
AND kcu.referenced_column_name IS NOT NULL
GROUP BY
cstn ,
tn ,
rcn ,
cn ,
puc ,
rtn ,
cn,
mo ,
ur ,
dr ,
ts`,
paramsHints: ['database'],
},
},

14
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/BaseModelSqlv2.ts

@ -1206,14 +1206,14 @@ class BaseModelSqlv2 {
private async getSelectQueryBuilderForFormula(column: Column<any>) {
const formula = await column.getColOptions<FormulaColumn>();
if (formula.error) throw new Error(`Formula error: ${formula.error}`);
const selectQb = await formulaQueryBuilderv2(
const qb = await formulaQueryBuilderv2(
formula.formula,
null,
this.dbDriver,
this.model
this.model,
column
);
return selectQb;
return qb;
}
async getProto() {
@ -1502,7 +1502,6 @@ class BaseModelSqlv2 {
const selectQb = await this.getSelectQueryBuilderForFormula(
column
);
// todo: verify syntax of as ? / ??
qb.select(
this.dbDriver.raw(`?? as ??`, [
selectQb.builder,
@ -1510,7 +1509,10 @@ class BaseModelSqlv2 {
])
);
} catch {
continue;
// return dummy select
qb.select(
this.dbDriver.raw(`'ERR' as ??`, [sanitize(column.title)])
);
}
}
break;

2
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/conditionV2.ts

@ -246,7 +246,7 @@ const parseConditionV2 = async (
const model = await column.getModel();
const formula = await column.getColOptions<FormulaColumn>();
const builder = (
await formulaQueryBuilderv2(formula.formula, null, knex, model)
await formulaQueryBuilderv2(formula.formula, null, knex, model, column)
).builder;
return parseConditionV2(
new Filter({ ...filter, value: knex.raw('?', [filter.value]) } as any),

86
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2.ts

@ -1,6 +1,7 @@
import jsep from 'jsep';
import mapFunctionName from '../mapFunctionName';
import Model from '../../../../../models/Model';
import Column from '../../../../../models/Column';
import genRollupSelectv2 from '../genRollupSelectv2';
import RollupColumn from '../../../../../models/RollupColumn';
import FormulaColumn from '../../../../../models/FormulaColumn';
@ -9,6 +10,8 @@ import LinkToAnotherRecordColumn from '../../../../../models/LinkToAnotherRecord
import LookupColumn from '../../../../../models/LookupColumn';
import { jsepCurlyHook, UITypes } from 'nocodb-sdk';
import { validateDateWithUnknownFormat } from '../helpers/formulaFnHelper';
import { CacheGetType, CacheScope } from '../../../../../utils/globals';
import NocoCache from '../../../../../cache/NocoCache';
// todo: switch function based on database
@ -45,16 +48,16 @@ const getAggregateFn: (fnName: string) => (args: { qb; knex?; cn }) => any = (
}
};
export default async function formulaQueryBuilderv2(
async function _formulaQueryBuilder(
_tree,
alias,
knex: XKnex,
model: Model,
aliasToColumn = {}
) {
// register jsep curly hook
jsep.plugins.register(jsepCurlyHook);
const tree = jsep(_tree);
// formula may include double curly brackets in previous version
// convert to single curly bracket here for compatibility
const tree = jsep(_tree.replaceAll('{{', '{').replaceAll('}}', '}'));
const columnIdToUidt = {};
@ -66,7 +69,7 @@ export default async function formulaQueryBuilderv2(
case UITypes.Formula:
{
const formulOption = await col.getColOptions<FormulaColumn>();
const { builder } = await formulaQueryBuilderv2(
const { builder } = await _formulaQueryBuilder(
formulOption.formula,
alias,
knex,
@ -340,7 +343,7 @@ export default async function formulaQueryBuilderv2(
const formulaOption =
await lookupColumn.getColOptions<FormulaColumn>();
const lookupModel = await lookupColumn.getModel();
const { builder } = await formulaQueryBuilderv2(
const { builder } = await _formulaQueryBuilder(
formulaOption.formula,
'',
knex,
@ -771,3 +774,74 @@ export default async function formulaQueryBuilderv2(
};
return { builder: fn(tree, alias) };
}
function getTnPath(tb: Model, knex) {
const schema = knex.searchPath?.();
if (knex.clientType() === 'mssql' && schema) {
return knex.raw('??.??', [schema, tb.table_name]);
} else if (knex.clientType() === 'snowflake') {
return [
knex.client.config.connection.database,
knex.client.config.connection.schema,
tb.table_name,
].join('.');
} else {
return tb.table_name;
}
}
export default async function formulaQueryBuilderv2(
_tree,
alias,
knex: XKnex,
model: Model,
column?: Column,
aliasToColumn = {}
) {
// register jsep curly hook once only
jsep.plugins.register(jsepCurlyHook);
// generate qb
const qb = await _formulaQueryBuilder(
_tree,
alias,
knex,
model,
aliasToColumn
);
try {
// dry run qb.builder to see if it will break the grid view or not
// if so, set formula error and show empty selectQb instead
await knex(getTnPath(model, knex)).select(qb.builder).as('dry-run-only');
// if column is provided, i.e. formula has been created
if (column) {
const formula = await column.getColOptions<FormulaColumn>();
// clean the previous formula error if the formula works this time
if (formula.error) {
await FormulaColumn.update(formula.id, {
error: null,
});
}
}
} catch (e) {
console.error(e);
if (column) {
const formula = await column.getColOptions<FormulaColumn>();
// add formula error to show in UI
await FormulaColumn.update(formula.id, {
error: e.message,
});
// update cache to reflect the error in UI
const key = `${CacheScope.COL_FORMULA}:${column.id}`;
let o = await NocoCache.get(key, CacheGetType.TYPE_OBJECT);
if (o) {
o = { ...o, error: e.message };
// set cache
await NocoCache.set(key, o);
}
}
throw new Error(`Formula error: ${e.message}`);
}
return qb;
}

17
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/pg.ts

@ -91,7 +91,7 @@ const pg = {
DATE_PART('year', AGE(${datetime_expr2}, '1900/01/01')) * 4) - 1)`;
break;
case 'year':
sql = `DATE_PART('year', ${datetime_expr1}::TIMESTAMP) - DATE_PART('year', ${datetime_expr2}::TIMESTAMP)`;
sql = `DATE_PART('year', AGE(${datetime_expr1}, ${datetime_expr2}))`;
break;
case 'day':
sql = `DATE_PART('day', ${datetime_expr1}::TIMESTAMP - ${datetime_expr2}::TIMESTAMP)`;
@ -138,6 +138,21 @@ const pg = {
.toQuery()} THEN 1 ELSE 0 END ${args.colAlias}`
);
},
SUBSTR: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
const str = fn(pt.arguments[0]);
const positionFrom = fn(pt.arguments[1] ?? 1);
const numberOfCharacters = fn(pt.arguments[2] ?? '');
return knex.raw(
`SUBSTR(${str}::TEXT, ${positionFrom}${
numberOfCharacters ? ', ' + numberOfCharacters : ''
})${colAlias}`
);
},
MOD: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
const x = fn(pt.arguments[0]);
const y = fn(pt.arguments[1]);
return knex.raw(`MOD((${x})::NUMERIC, (${y})::NUMERIC) ${colAlias}`);
},
};
export default pg;

69
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/functionMappings/sqlite.ts

@ -83,19 +83,25 @@ const sqlite3 = {
);
},
DATETIME_DIFF: ({ fn, knex, pt, colAlias }: MapFnArgs) => {
let datetime_expr1 = fn(pt.arguments[0]).bindings[0];
let datetime_expr2 = fn(pt.arguments[1]).bindings[0];
let datetime_expr1 = fn(pt.arguments[0]);
let datetime_expr2 = fn(pt.arguments[1]);
// JULIANDAY takes YYYY-MM-DD
datetime_expr1 = convertToTargetFormat(
datetime_expr1,
getDateFormat(datetime_expr1),
'YYYY-MM-DD'
);
datetime_expr2 = convertToTargetFormat(
datetime_expr2,
getDateFormat(datetime_expr2),
'YYYY-MM-DD'
);
if (datetime_expr1.sql === '?' && datetime_expr1.bindings?.[0]) {
datetime_expr1 = `'${convertToTargetFormat(
datetime_expr1.bindings[0],
getDateFormat(datetime_expr1.bindings[0]),
'YYYY-MM-DD'
)}'`;
}
if (datetime_expr2.sql === '?' && datetime_expr2.bindings?.[0]) {
datetime_expr2 = `'${convertToTargetFormat(
datetime_expr2.bindings[0],
getDateFormat(datetime_expr2.bindings[0]),
'YYYY-MM-DD'
)}'`;
}
const rawUnit = pt.arguments[2]
? fn(pt.arguments[2]).bindings[0]
: 'seconds';
@ -103,36 +109,47 @@ const sqlite3 = {
const unit = convertUnits(rawUnit, 'sqlite');
switch (unit) {
case 'seconds':
sql = `ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) * 86400)`;
sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2}))`;
break;
case 'minutes':
sql = `ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) * 1440)`;
sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) / 60`;
break;
case 'hours':
sql = `ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) * 24)`;
sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) / 3600`;
break;
case 'milliseconds':
sql = `ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) * 86400000)`;
sql = `(strftime('%s', ${datetime_expr1}) - strftime('%s', ${datetime_expr2})) * 1000`;
break;
case 'weeks':
sql = `ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) / 7)`;
sql = `ROUND((JULIANDAY(${datetime_expr1}) - JULIANDAY(${datetime_expr2})) / 7)`;
break;
case 'months':
sql = `(ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) / 365))
* 12 + (ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) / 365 / 12))`;
sql = `(strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) * 12 + (strftime('%m', ${datetime_expr1}) - strftime('%m', ${datetime_expr2})) `;
break;
case 'quarters':
sql = `
ROUND((JULIANDAY('${datetime_expr1}')) / 365 / 4) -
ROUND((JULIANDAY('${datetime_expr2}')) / 365 / 4) +
(ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) / 365)) * 4
`;
sql = `(strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2})) * 4 + (strftime('%m', ${datetime_expr1}) - strftime('%m', ${datetime_expr2})) / 3`;
break;
case 'years':
sql = `ROUND((JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')) / 365)`;
sql = `CASE
WHEN (${datetime_expr2} < ${datetime_expr1}) THEN
(
(strftime('%Y', ${datetime_expr1}) - strftime('%Y', ${datetime_expr2}))
- (strftime('%m', ${datetime_expr1}) < strftime('%m', ${datetime_expr2})
OR (strftime('%m', ${datetime_expr1}) = strftime('%m', ${datetime_expr2})
AND strftime('%d', ${datetime_expr1}) < strftime('%d', ${datetime_expr2})))
)
WHEN (${datetime_expr2} > ${datetime_expr1}) THEN
-1 * (
(strftime('%Y', ${datetime_expr2}) - strftime('%Y', ${datetime_expr1}))
- (strftime('%m', ${datetime_expr2}) < strftime('%m', ${datetime_expr1})
OR (strftime('%m', ${datetime_expr2}) = strftime('%m', ${datetime_expr1})
AND strftime('%d', ${datetime_expr2}) < strftime('%d', ${datetime_expr1})))
)
ELSE 0
END`;
break;
case 'days':
sql = `JULIANDAY('${datetime_expr1}') - JULIANDAY('${datetime_expr2}')`;
sql = `JULIANDAY(${datetime_expr1}) - JULIANDAY(${datetime_expr2})`;
break;
default:
sql = '';

6
packages/nocodb/src/lib/db/sql-data-mapper/lib/sql/sortV2.ts

@ -51,7 +51,8 @@ export default async function sortV2(
).formula,
null,
knex,
model
model,
column
)
).builder;
qb.orderBy(builder, sort.direction || 'asc');
@ -161,7 +162,8 @@ export default async function sortV2(
).formula,
null,
knex,
model
model,
column
)
).builder;

21
packages/nocodb/src/lib/meta/api/columnApis.ts

@ -40,6 +40,7 @@ import { metaApiMetrics } from '../helpers/apiMetrics';
import FormulaColumn from '../../models/FormulaColumn';
import KanbanView from '../../models/KanbanView';
import { MetaTable } from '../../utils/globals';
import formulaQueryBuilderv2 from '../../db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2';
const randomID = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 10);
@ -523,6 +524,16 @@ export async function columnAdd(
colBody.formula_raw || colBody.formula,
table.columns
);
try {
// test the query to see if it is valid in db level
const dbDriver = NcConnectionMgrv2.get(base);
await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
} catch (e) {
console.error(e);
NcError.badRequest('Invalid Formula');
}
await Column.insert({
...colBody,
fk_model_id: table.id,
@ -759,6 +770,16 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
colBody.formula_raw || colBody.formula,
table.columns
);
try {
// test the query to see if it is valid in db level
const dbDriver = NcConnectionMgrv2.get(base);
await formulaQueryBuilderv2(colBody.formula, null, dbDriver, table);
} catch (e) {
console.error(e);
NcError.badRequest('Invalid Formula');
}
await Column.update(column.id, {
// title: colBody.title,
...column,

2
packages/nocodb/src/lib/meta/api/swagger/swaggerHtml.ts

File diff suppressed because one or more lines are too long

13
packages/nocodb/src/lib/meta/api/sync/helpers/job.ts

@ -246,7 +246,7 @@ export default async (
count: UITypes.Count,
lookup: UITypes.Lookup,
autoNumber: UITypes.AutoNumber,
barcode: UITypes.Barcode,
barcode: UITypes.SingleLineText,
button: UITypes.Button,
};
@ -1401,10 +1401,6 @@ export default async (
} else rec[key] = `${value?.name} <${value?.email}>`;
break;
case UITypes.Barcode:
rec[key] = value.text;
break;
case UITypes.Button:
rec[key] = `${value?.label} <${value?.url}>`;
break;
@ -1473,6 +1469,13 @@ export default async (
}
break;
case UITypes.SingleLineText:
// Barcode data
if (value?.text) {
rec[key] = value.text;
}
break;
default:
break;
}

6
packages/nocodb/src/lib/meta/helpers/populateSamplePayload.ts

@ -84,12 +84,12 @@ async function getSampleColumnValue(column: Column): Promise<any> {
break;
case UITypes.SingleLineText:
{
return 'Text';
return 'Sample Text';
}
break;
case UITypes.LongText:
{
return 'Long text';
return 'Sample Long text';
}
break;
case UITypes.Attachment:
@ -189,7 +189,7 @@ async function getSampleColumnValue(column: Column): Promise<any> {
break;
case UITypes.Formula:
{
return 'output';
return 'Sample Output';
}
break;
case UITypes.Rollup:

2
packages/nocodb/src/lib/models/Base.ts

@ -278,7 +278,7 @@ export default class Base implements BaseType {
ncMeta
);
for (const model of models) {
await model.delete(ncMeta);
await model.delete(ncMeta, true);
}
await NocoCache.deepDel(
CacheScope.BASE,

13
packages/nocodb/src/lib/models/Filter.ts

@ -1,6 +1,7 @@
import Noco from '../Noco';
import Model from './Model';
import Column from './Column';
import Hook from './Hook';
import {
CacheDelDirection,
CacheGetType,
@ -10,6 +11,7 @@ import {
import View from './View';
import { FilterType, UITypes } from 'nocodb-sdk';
import NocoCache from '../cache/NocoCache';
import { NcError } from '../meta/helpers/catchError';
export default class Filter {
id: string;
@ -90,7 +92,16 @@ export default class Filter {
}),
};
if (!(filter.project_id && filter.base_id)) {
const model = await Column.get({ colId: filter.fk_column_id }, ncMeta);
let model: { project_id?: string; base_id?: string };
if (filter.fk_view_id) {
model = await View.get(filter.fk_view_id, ncMeta);
} else if (filter.fk_hook_id) {
model = await Hook.get(filter.fk_hook_id, ncMeta);
} else if (filter.fk_column_id) {
model = await Column.get({ colId: filter.fk_column_id }, ncMeta);
} else {
NcError.badRequest('Invalid filter');
}
insertObj.project_id = model.project_id;
insertObj.base_id = model.base_id;
}

27
packages/nocodb/src/lib/models/Model.ts

@ -345,7 +345,7 @@ export default class Model implements TableType {
});
}
async delete(ncMeta = Noco.ncMeta): Promise<boolean> {
async delete(ncMeta = Noco.ncMeta, force = false): Promise<boolean> {
await Audit.deleteRowComments(this.id);
for (const view of await this.getViews(true)) {
@ -391,6 +391,31 @@ export default class Model implements TableType {
}
}
if (force) {
const leftOverColumns = await ncMeta.metaList2(
null,
null,
MetaTable.COL_RELATIONS,
{
condition: {
fk_related_model_id: this.id,
},
}
);
for (const col of leftOverColumns) {
await NocoCache.deepDel(
CacheScope.COL_RELATION,
`${CacheScope.COL_RELATION}:${col.fk_column_id}`,
CacheDelDirection.CHILD_TO_PARENT
);
}
await ncMeta.metaDelete(null, null, MetaTable.COL_RELATIONS, {
fk_related_model_id: this.id,
});
}
await NocoCache.deepDel(
CacheScope.COLUMN,
`${CacheScope.COLUMN}:${this.id}`,

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save