diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 787bbe9cbf..902a7c7218 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -18,7 +18,10 @@ on:
- "packages/nc-gui/**"
- "packages/nocodb/**"
- ".github/workflows/ci-cd.yml"
+ - ".github/workflows/playwright-test-workflow.yml"
- "tests/playwright/**"
+ # Triggered manually
+ workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -89,6 +92,10 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
+ - name: Set CI env
+ run: export CI=true
+ - name: Set NC Edition
+ run: export EE=true
- name: setup pg
working-directory: ./
run: docker-compose -f ./tests/playwright/scripts/docker-compose-playwright-pg.yml up -d &
diff --git a/.github/workflows/playwright-test-workflow.yml b/.github/workflows/playwright-test-workflow.yml
index eb2ec03eca..60b62f90ff 100644
--- a/.github/workflows/playwright-test-workflow.yml
+++ b/.github/workflows/playwright-test-workflow.yml
@@ -13,21 +13,9 @@ on:
jobs:
playwright:
- runs-on: ubuntu-20.04
- timeout-minutes: 40
+ runs-on: [self-hosted, v2]
+ timeout-minutes: 100
steps:
- # Reference: https://github.com/pierotofy/set-swap-space/blob/master/action.yml
- - name: Set 5gb swap
- shell: bash
- # Delete the swap file, allocate a new one, and activate it
- run: |
- export SWAP_FILE=$(swapon --show=NAME | tail -n 1)
- sudo swapoff $SWAP_FILE
- sudo rm $SWAP_FILE
- sudo fallocate -l 5G $SWAP_FILE
- sudo chmod 600 $SWAP_FILE
- sudo mkswap $SWAP_FILE
- sudo swapon $SWAP_FILE
- name: Setup Node
uses: actions/setup-node@v3
with:
@@ -42,57 +30,87 @@ jobs:
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
- key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
+ key: ${{ runner.os }}-v2-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
- ${{ runner.os }}-build-${{ env.cache-name }}-
- ${{ runner.os }}-build-
- ${{ runner.os }}-
+ ${{ runner.os }}-v2-build-${{ env.cache-name }}-
+ ${{ runner.os }}-v2-build-
+ ${{ runner.os }}-v2
+ - name: setup pg
+ if: ${{ inputs.db == 'pg' || ( inputs.db == 'sqlite' && inputs.shard == '1' ) }}
+ working-directory: ./
+ run: |
+ service postgresql start
+ cd /var/lib/postgresql/ && sudo -u postgres psql -c "SELECT 'dropdb '||datname||'' FROM pg_database WHERE datistemplate = false AND datallowconn = true And datname NOT IN ('postgres')" |grep ' dropdb ' | sudo -u postgres /bin/bash ; cd
+ sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD 'password';"
+ sudo -u postgres psql -c "ALTER USER postgres WITH SUPERUSER;"
+ service postgresql restart
+ - name: Set CI env
+ run: export CI=true
+ - name: Kill stale servers
+ run: |
+ # export NODE_OPTIONS=\"--max_old_space_size=16384\";
+ kill -9 $(lsof -t -i:8080) || echo "no process running on 8080"
+ kill -9 $(lsof -t -i:3000) || echo "no process running on 3000"
+ - name: Set CI env
+ run: export CI=true
+ - name: Set NC Edition
+ run: export EE=true
- name: install dependencies nocodb-sdk
working-directory: ./packages/nocodb-sdk
run: npm install
- - name: build nocodb-sdk
+ - name: Build nocodb-sdk
working-directory: ./packages/nocodb-sdk
run: npm run build
- - name: setup mysql
+ - name: Setup mysql
if: ${{ inputs.db == 'mysql' }}
- working-directory: ./
- run: docker-compose -f ./tests/playwright/scripts/docker-compose-mysql-playwright.yml up -d &
- - name: setup pg
- if: ${{ inputs.db == 'pg' }}
- working-directory: ./
- run: docker-compose -f ./tests/playwright/scripts/docker-compose-playwright-pg.yml up -d &
- - name: setup pg for quick tests
+ working-directory: ./packages/nocodb/tests/mysql-sakila-db
+ run: |
+ # Get a list of non-system databases and construct the DROP DATABASE statement for each
+ service mysql start
+ mysql -u'root' -p'password' -e "SHOW DATABASES" --skip-column-names | grep -Ev "(information_schema|mysql|performance_schema|sys)" | while read db; do
+ mysql -u'root' -p'password' -e "DROP DATABASE IF EXISTS \`$db\`";
+ done
+ # keep sql_mode default except remove "STRICT_TRANS_TABLES"
+ mysql -u'root' -p'password' -e "SET GLOBAL sql_mode = 'ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';"
+ # this is only needed for connecting to sakila db as its refeferred in multiple places in test code
+ mysql -u'root' -p'password' < 01-mysql-sakila-schema.sql
+ mysql -u'root' -p'password' < 02-mysql-sakila-insert-data.sql
+ - name: Setup pg for quick tests
if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }}
- working-directory: ./
- run: docker-compose -f ./tests/playwright/scripts/docker-compose-pg-pw-quick.yml up -d &
+ working-directory: ./packages/nocodb/tests/pg-cy-quick/
+ run: |
+ sudo -u postgres psql -U postgres -f 01-cy-quick.sql
- name: run frontend
working-directory: ./packages/nc-gui
run: npm run ci:run
+ timeout-minutes: 20
- name: Run backend
if: ${{ inputs.db == 'sqlite' }}
working-directory: ./packages/nocodb
run: |
npm install
- npm run watch:run:playwright > ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
+ npm run watch:run:playwright &> ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
- name: Run backend:mysql
if: ${{ inputs.db == 'mysql' }}
working-directory: ./packages/nocodb
run: |
npm install
- npm run watch:run:playwright:mysql > ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
+ npm run watch:run:playwright:mysql &> ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
- name: Run backend:pg
if: ${{ inputs.db == 'pg' }}
working-directory: ./packages/nocodb
run: |
npm install
- npm run watch:run:playwright:pg > ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
+ npm run watch:run:playwright:pg &> ${{ inputs.db }}_${{ inputs.shard }}_test_backend.log &
- name: Cache playwright npm modules
uses: actions/cache@v3
id: playwright-cache
with:
path: |
**/tests/playwright/node_modules
- key: cache-nc-playwright-${{ hashFiles('**/tests/playwright/package-lock.json') }}
+ key: cache-v2-nc-playwright-${{ hashFiles('**/tests/playwright/package-lock.json') }}
+ restore-keys: |
+ cache-v2-nc-playwright-
- name: Install dependencies
if: steps.playwright-cache.outputs.cache-hit != 'true'
working-directory: ./tests/playwright
@@ -106,11 +124,11 @@ jobs:
printf '.'
sleep 2
done
-
- - name: Run Playwright tests
+ timeout-minutes: 2
+ - name: Run Playwright Tests
working-directory: ./tests/playwright
run: E2E_DB_TYPE=${{ inputs.db }} npm run ci:test:shard:${{ inputs.shard }}
-
+ timeout-minutes: 60
# Stress test added/modified tests
- name: Fetch develop branch
working-directory: ./tests/playwright
@@ -119,28 +137,32 @@ jobs:
working-directory: ./tests/playwright
run: E2E_DB_TYPE=${{ inputs.db }} node ./scripts/stressTestNewlyAddedTest.js
- # Quick tests (pg on sqlite shard 0 and sqlite on sqlite shard 1)
- - name: Run quick server and tests (pg)
- if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }}
- working-directory: ./packages/nocodb
- run: |
- kill -9 $(lsof -t -i:8080)
- npm run watch:run:playwright:pg:cyquick &
- - name: Run quick server and tests (sqlite)
- if: ${{ inputs.db == 'sqlite' && inputs.shard == '2' }}
- working-directory: ./packages/nocodb
- run: |
- kill -9 $(lsof -t -i:8080)
- npm run watch:run:playwright:quick > quick_${{ inputs.shard }}_test_backend.log &
- - name: Wait for backend & run quick tests
- if: ${{ inputs.db == 'sqlite' }}
- working-directory: ./tests/playwright
- run: |
- while ! curl --output /dev/null --silent --head --fail http://localhost:8080; do
- printf '.'
- sleep 2
- done
- PLAYWRIGHT_HTML_REPORT=playwright-report-quick npm run test:quick
+# # Quick tests (pg on sqlite shard 0 and sqlite on sqlite shard 1)
+# - name: Run quick server and tests (pg)
+# if: ${{ inputs.db == 'sqlite' && inputs.shard == '1' }}
+# working-directory: ./packages/nocodb
+# run: |
+# kill -9 $(lsof -t -i:8080)
+# npm run watch:run:playwright:pg:cyquick > quick_${{ inputs.shard }}_test_backend.log &
+# - name: Run quick server and tests (sqlite)
+# if: ${{ inputs.db == 'sqlite' && inputs.shard == '2' }}
+# working-directory: ./packages/nocodb
+# run: |
+# kill -9 $(lsof -t -i:8080)
+# npm run watch:run:playwright:quick > quick_${{ inputs.shard }}_test_backend.log &
+# - name: Wait for backend for sqlite-tests
+# if: ${{ inputs.db == 'sqlite' }}
+# working-directory: ./tests/playwright
+# run: |
+# while ! curl --output /dev/null --silent --head --fail http://localhost:8080; do
+# printf '.'
+# sleep 2
+# done
+# timeout-minutes: 1
+# - name: Run quick tests
+# if: ${{ inputs.db == 'sqlite' }}
+# working-directory: ./tests/playwright
+# run: PLAYWRIGHT_HTML_REPORT=playwright-report-quick npm run test:quick
- uses: actions/upload-artifact@v3
if: ${{ inputs.db == 'sqlite' }}
with:
@@ -172,3 +194,9 @@ jobs:
name: backend-logs-${{ inputs.db }}-${{ inputs.shard }}
path: ./packages/nocodb/${{ inputs.db }}_${{ inputs.shard }}_test_backend.log
retention-days: 2
+ - name: stop database servers
+ if: always()
+ working-directory: ./packages/nocodb
+ run: |
+ service postgresql stop
+ service mysql stop
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2d333cbc36..94d7be5041 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,8 +2,10 @@
# ===========
.DS_Store
ehthumbs.db
-Icon?
Thumbs.db
+Icon
+
+
# Node and related ecosystem
# ==========================
@@ -92,3 +94,4 @@ test_noco.db
# ngrok config
httpbin
+.run/test-debug.run.xml
diff --git a/README.md b/README.md
index a545c8a23e..82b32c8ad1 100644
--- a/README.md
+++ b/README.md
@@ -68,27 +68,6 @@ Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadshe
# Quick try
-## NPX
-
-You can run the below command if you need an interactive configuration.
-
-```
-npx create-nocodb-app
-```
-
-
-
-## Node Application
-
-We provide a simple NodeJS Application for getting started.
-
-```bash
-git clone https://github.com/nocodb/nocodb-seed
-cd nocodb-seed
-npm install
-npm start
-```
-
## Docker
```bash
@@ -186,6 +165,28 @@ docker-compose up -d
> If you plan to input some special characters, you may need to change the character set and collation yourself when creating the database. Please check out the examples for [MySQL Docker Compose](https://github.com/nocodb/nocodb/issues/1313#issuecomment-1046625974).
+## NPX
+
+You can run the below command if you need an interactive configuration.
+
+```
+npx create-nocodb-app
+```
+
+
+
+## Node Application
+
+We provide a simple NodeJS Application for getting started.
+
+```bash
+git clone https://github.com/nocodb/nocodb-seed
+cd nocodb-seed
+npm install
+npm start
+```
+
+
# GUI
Access Dashboard using: [http://localhost:8080/dashboard](http://localhost:8080/dashboard)
diff --git a/docker-compose/traefik/docker-compose.yml b/docker-compose/traefik/docker-compose.yml
index 34cd04d510..b02b03bad7 100644
--- a/docker-compose/traefik/docker-compose.yml
+++ b/docker-compose/traefik/docker-compose.yml
@@ -62,7 +62,7 @@ services:
- "--certificatesResolvers.letsencrypt.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53"
container_name: traefik
environment:
- - "CF_DNS_API_TOKEN=${CLOUDFLARE_TOKEN}"
+ - "CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}"
healthcheck:
retries: 3
test:
diff --git a/markdown/readme/languages/indonesian.md b/markdown/readme/languages/indonesian.md
index a3fff11549..b615bc2b04 100644
--- a/markdown/readme/languages/indonesian.md
+++ b/markdown/readme/languages/indonesian.md
@@ -1,17 +1,22 @@
+
-
- NocoDB
-
+
✨ Sebuah Alternatif AirTable Open Source ✨
Mengubah MySQL, PostgreSQL, SQL Server, SQLite & MariaDB apapun menjadi spreadsheet pintar.
-
-[![Build Status](https://travis-ci.org/dwyl/esta.svg?branch=master)](https://travis-ci.com/github/NocoDB/NocoDB)
-[![Node version](https://img.shields.io/badge/node-%3E%3D%2014.18.0-brightgreen)](http://nodejs.org/download/)
+
+
+[![Node version](https://img.shields.io/badge/node-%3E%3D%2016.14.0-brightgreen)](http://nodejs.org/download/)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-green.svg)](https://conventionalcommits.org)
@@ -19,12 +24,13 @@ Mengubah MySQL, PostgreSQL, SQL Server, SQLite & MariaDB apapun menjadi spreadsh
Website •
Discord •
+ Komunitas •
Twitter •
Reddit •
- Documentation
+ Dokumentasi
-![OpenSourceAirtableAlternative](https://user-images.githubusercontent.com/5435402/133762127-e94da292-a1c3-4458-b09a-02cd5b57be53.png)
+![All Views](https://user-images.githubusercontent.com/35857179/194825053-3aa3373d-3e0f-4b42-b3f1-42928332054a.gif)
@@ -32,49 +38,134 @@ Mengubah MySQL, PostgreSQL, SQL Server, SQLite & MariaDB apapun menjadi spreadsh
-# Mulai Cepat
+# Join Tim Kami
+
+
+
+# Join Komunitas Kami
+
+
+
+
+
+
+
+[![Stargazers repo roster for @nocodb/nocodb](https://reporoster.com/stars/nocodb/nocodb)](https://github.com/nocodb/nocodb/stargazers)
+
+# Coba singkat
+
+## Docker
+
+```bash
+# for SQLite
+docker run -d --name nocodb \
+-v "$(pwd)"/nocodb:/usr/app/data/ \
+-p 8080:8080 \
+nocodb/nocodb:latest
+
+# for MySQL
+docker run -d --name nocodb-mysql \
+-v "$(pwd)"/nocodb:/usr/app/data/ \
+-p 8080:8080 \
+-e NC_DB="mysql2://host.docker.internal:3306?u=root&p=password&d=d1" \
+-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
+nocodb/nocodb:latest
+
+# for PostgreSQL
+docker run -d --name nocodb-postgres \
+-v "$(pwd)"/nocodb:/usr/app/data/ \
+-p 8080:8080 \
+-e NC_DB="pg://host.docker.internal:5432?u=root&p=password&d=d1" \
+-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
+nocodb/nocodb:latest
+
+# for MSSQL
+docker run -d --name nocodb-mssql \
+-v "$(pwd)"/nocodb:/usr/app/data/ \
+-p 8080:8080 \
+-e NC_DB="mssql://host.docker.internal:1433?u=root&p=password&d=d1" \
+-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
+nocodb/nocodb:latest
+```
+
+> Untuk menyimpan data di dalam Docker, Anda dapat melakukan mount volume di direktori /usr/app/data/ mulai dari versi 0.10.6. Jika tidak, data Anda akan hilang setelah mengulang pembuatan kontainer.
-### Menggunakan Docker
+> Jika Anda berencana untuk memasukkan beberapa karakter khusus, Anda perlu mengubah set karakter dan kolasi sendiri saat membuat basis data. Silakan lihat contoh-contoh untuk [MySQL Docker](https://github.com/nocodb/nocodb/issues/1340#issuecomment-1049481043).
+
+## Binaries
+
+##### MacOS (x64)
```bash
-docker run -d --name nocodb -p 8080:8080 nocodb/nocodb:latest
+curl http://get.nocodb.com/macos-x64 -o nocodb -L && chmod +x nocodb && ./nocodb
```
-- NocoDB needs a database as input : See [Production Setup](https://github.com/nocodb/nocodb/blob/master/README.md#production-setup).
-- If this input is absent, we fallback to SQLite. In order too persist sqlite, you can mount `/usr/app/data/`.
+##### MacOS (arm64)
+
+```bash
+curl http://get.nocodb.com/macos-arm64 -o nocodb -L && chmod +x nocodb && ./nocodb
+```
+
+##### Linux (x64)
+
+```bash
+curl http://get.nocodb.com/linux-x64 -o nocodb -L && chmod +x nocodb && ./nocodb
+```
- Example:
+##### Linux (arm64)
- ```
- docker run -d -p 8080:8080 --name nocodb -v "$(pwd)"/nocodb:/usr/app/data/ nocodb/nocodb:latest
- ```
+```bash
+curl http://get.nocodb.com/linux-arm64 -o nocodb -L && chmod +x nocodb && ./nocodb
+```
-### Menggunakan NPM
+##### Windows (x64)
+```bash
+iwr http://get.nocodb.com/win-x64.exe -o Noco-win-x64.exe
+.\Noco-win-x64.exe
```
-npx create-nocodb-app
+
+##### Windows (arm64)
+
+```bash
+iwr http://get.nocodb.com/win-arm64.exe -o Noco-win-arm64.exe
+.\Noco-win-arm64.exe
```
-### Menggunakan git
+## Docker Compose
+
+Kami menyediakan berbagai file docker-compose.yml di [bawah direktori](https://github.com/nocodb/nocodb/tree/master/docker-compose) ini. Berikut beberapa contohnya:
+```bash
+git clone https://github.com/nocodb/nocodb
+# for MySQL
+cd nocodb/docker-compose/mysql
+# for PostgreSQL
+cd nocodb/docker-compose/pg
+# for MSSQL
+cd nocodb/docker-compose/mssql
+docker-compose up -d
```
+
+> Untuk menyimpan data dalam Docker, Anda dapat melakukan mount volume pada direktori /usr/app/data/ mulai dari versi 0.10.6. Jika tidak, data Anda akan hilang setelah mengulang pembuatan kontainer.
+
+> Jika Anda berencana untuk memasukkan beberapa karakter khusus, Anda perlu mengubah set karakter dan kolasi sendiri saat membuat basis data. Silakan lihat contoh-contoh untuk [MySQL Docker Compose](https://github.com/nocodb/nocodb/issues/1313#issuecomment-1046625974).
+
+
+```bash
git clone https://github.com/nocodb/nocodb-seed
cd nocodb-seed
npm install
npm start
```
-### GUI
+# GUI
Akses dasbor menggunakan : [http://localhost:8080/dashboard](http://localhost:8080/dashboard)
-# Bergabunglah dengan komunitas kami
-
-
-
-
-
-
# Tangkapan Layar
![1](https://user-images.githubusercontent.com/86527202/136074228-f52e181b-e65d-44ce-afca-0447eb506e90.png)
@@ -110,6 +201,32 @@ Akses dasbor menggunakan : [http://localhost:8080/dashboard](http://localhost:80
![11](https://user-images.githubusercontent.com/86527202/136074285-b5d1dc5c-fac3-43af-b9fc-1a5d1d41d071.png)
+# Table of Contents
+
+# Daftar Isi
+
+- [Coba Singkat](#coba-singkat)
+ - [Docker](#docker)
+ - [Docker Compose](#docker-compose)
+- [Antarmuka Grafis Pengguna (GUI)](#gui)
+- [Bergabunglah dengan Komunitas Kami](#join-komunitas-kami)
+- [Screenshots](#tangkapan-layar)
+- [Daftar Isi](#daftar-isi)
+- [Fitur-fitur](#fitur)
+ - [Antarmuka Lembar Kerja yang Kaya](#antarmuka-spreadsheet-yang-kaya)
+ - [Toko Aplikasi untuk Otomasi Alur Kerja](#app-store-untuk-automasi-alur-kerja)
+ - [Akses Programatik](#akses-api-programmatik-melalui)
+ - [Sinkronisasi Skema](#sinkronisasi-skema)
+ - [Audit](#audit)
+- [Pengaturan Produksi](#pengaturan-produksi)
+ - [Variabel Lingkungan](#environment-variables)
+- [Pengaturan Pengembangan](#pengaturan-pengembangan)
+- [Berkontribusi](#berkontribusi)
+- [Mengapa kami membangun ini?](#kenapa-kita-membangun-ini)
+- [Misi Kami](#misi-kami)
+- [Lisensi](#lisensi)
+
+
# Fitur
### Antarmuka spreadsheet yang kaya
@@ -137,65 +254,38 @@ Akses dasbor menggunakan : [http://localhost:8080/dashboard](http://localhost:80
- ⚡ Termasuk Otentikasi JWT & Auth Sosial
- ⚡ Token API untuk berintegrasi dengan Zapier, Integromat.
-# Pengaturan Produksi
-
-NOCODB membutuhkan database untuk menyimpan metadata tampilan spreadsheet dan database eksternal. Dan params koneksi untuk basis data ini dapat ditentukan dalam variabel lingkungan NC_DB.
-
-## Docker
-
-#### Contoh MySQL
+### Sinkronisasi Skema
-```
-docker run -d -p 8080:8080 \
- -e NC_DB="mysql2://host.docker.internal:3306?u=root&p=password&d=d1" \
- -e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
- nocodb/nocodb:latest
-```
+Kami memungkinkan Anda untuk menyinkronkan perubahan skema jika Anda telah melakukan perubahan di luar antarmuka NocoDB GUI. Namun, perlu diperhatikan bahwa Anda harus menyediakan migrasi skema sendiri untuk berpindah dari satu lingkungan ke lingkungan lainnya. Lihat [Sinkronisasi Skema](https://docs.nocodb.com/setup-and-usages/sync-schema/) untuk detail lebih lanjut.
-#### Contoh Postgres
+### Audit
-```
-docker run -d -p 8080:8080 \
- -e NC_DB="pg://host:port?u=user&p=password&d=database" \
- -e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
- nocodb/nocodb:latest
-```
-
-#### Contoh SQL Server
-
-```
-docker run -d -p 8080:8080 \
- -e NC_DB="mssql://host:port?u=user&p=password&d=database" \
- -e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
- nocodb/nocodb:latest
-```
+Kami menyimpan semua log operasi pengguna di satu tempat. Lihat [Audit](https://docs.nocodb.com/setup-and-usages/audit) untuk detail lebih lanjut.
-## Docker Compose
+# Pengaturan Produksi
-```
-git clone https://github.com/nocodb/nocodb
-cd nocodb
-cd docker-compose
-cd mysql or pg or mssql
-docker-compose up -d
-```
+Secara default, SQLite digunakan untuk menyimpan metadata. Namun, Anda dapat menentukan basis data Anda sendiri. Parameter koneksi untuk basis data ini dapat ditentukan dalam variabel lingkungan `NC_DB`. Selain itu, kami juga menyediakan
## Environment variables
-Please refer to [Environment variables](https://docs.nocodb.com/getting-started/environment-variables)
+Silakan lihat [Environment Variables](https://docs.nocodb.com/getting-started/environment-variables) untuk informasi lebih lanjut.
# Pengaturan Pengembangan
-Please refer to [Development Setup](https://docs.nocodb.com/engineering/development-setup)
+Silakan lihat [Pengaturan Development](https://docs.nocodb.com/engineering/development-setup) untuk informasi lebih lanjut.
# Berkontribusi
-
-Please refer to [Contribution Guide](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md).
+Silakan lihat [Panduan Kontribusi](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md) untuk informasi lebih lanjut.
# Kenapa kita membangun ini?
-Sebagian besar bisnis internet melengkapi diri mereka dengan spreadsheet atau database untuk menyelesaikan kebutuhan bisnis mereka. Spreadsheet digunakan oleh satu miliar + manusia secara kolaboratif setiap hari. Namun, kami jauh bekerja dengan kecepatan yang sama pada basis data yang merupakan alat yang lebih kuat ketika datang ke komputasi. Upaya untuk menyelesaikan ini dengan persembahan SaaS berarti kontrol akses yang mengerikan, vendor lockin, data lockin, perubahan harga mendadak & paling penting plafon kaca pada apa yang mungkin di masa depan.
+Sebagian besar bisnis internet melengkapi diri mereka dengan spreadsheet atau database untuk menyelesaikan kebutuhan bisnis mereka. Spreadsheet digunakan oleh satu miliar+ manusia secara kolaboratif setiap hari. Namun, kami jauh bekerja dengan kecepatan yang sama pada basis data yang merupakan alat yang lebih kuat ketika datang ke komputasi. Upaya untuk menyelesaikan ini dengan persembahan SaaS berarti kontrol akses yang mengerikan, vendor lockin, data lockin, perubahan harga mendadak & paling penting plafon kaca pada apa yang mungkin di masa depan.
-# Misi kita
+# Misi Kami
-Misi kami adalah menyediakan antarmuka tanpa kode yang paling kuat untuk basis data yang merupakan sumber terbuka untuk setiap bisnis internet di dunia. Ini tidak hanya akan mendemokratisasi akses ke alat komputasi yang kuat tetapi juga memunculkan satu miliar + orang yang akan memiliki kemampuan mengotori-dan membangun radikal di Internet.
+Misi kami adalah menyediakan antarmuka no-code yang paling kuat untuk basis data yang merupakan sumber terbuka untuk setiap bisnis internet di dunia. Ini tidak hanya akan mendemokratisasi akses ke alat komputasi yang kuat tetapi juga memunculkan satu miliar+ orang yang akan memiliki kemampuan membangun di Internet.
+
+# Lisensi
+
+Proyek ini dilisensikan di bawah AGPLv3.
+
diff --git a/packages/nc-gui/app.vue b/packages/nc-gui/app.vue
index dfd4c809a6..4c57910a95 100644
--- a/packages/nc-gui/app.vue
+++ b/packages/nc-gui/app.vue
@@ -1,12 +1,15 @@
-
-
-
+
+
+
-
-
- |
-
+ |
Header Name
|
-
Value
|
-
-
-
- |
+ |
-
+
-
-
+
+
|
-
-
-
+
+
+
|
- |
| |
-
-
-
+
+
+
+
|
|
+
+
diff --git a/packages/nc-gui/components/api-client/Params.vue b/packages/nc-gui/components/api-client/Params.vue
index 35da74a0a5..61aa3f1f2c 100644
--- a/packages/nc-gui/components/api-client/Params.vue
+++ b/packages/nc-gui/components/api-client/Params.vue
@@ -11,69 +11,77 @@ const vModel = useVModel(props, 'modelValue', emits)
const addParamRow = () => vModel.value.push({})
-const deleteParamRow = (i: number) => vModel.value.splice(i, 1)
+const deleteParamRow = (i: number) => {
+ if (vModel.value.length === 1) return
+
+ vModel.value.splice(i, 1)
+}
-
-
-
+
+
+
-
- |
-
-
- Param Name
+ Parameter Name
|
Value
|
-
+ |
|
-
+
-
-
+
+
|
-
-
-
- |
-
-
-
-
+
+
|
- |
-
-
-
+
+
+
+
|
|
+
+
diff --git a/packages/nc-gui/components/cell/Checkbox.vue b/packages/nc-gui/components/cell/Checkbox.vue
index 94a87c5a0c..c95b66cd1e 100644
--- a/packages/nc-gui/components/cell/Checkbox.vue
+++ b/packages/nc-gui/components/cell/Checkbox.vue
@@ -35,7 +35,7 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj)
-const checkboxMeta = $computed(() => {
+const checkboxMeta = computed(() => {
return {
icon: {
checked: 'mdi-check-circle-outline',
@@ -46,7 +46,7 @@ const checkboxMeta = $computed(() => {
}
})
-let vModel = $computed({
+const vModel = computed({
get: () => !!props.modelValue && props.modelValue !== '0' && props.modelValue !== 0,
set: (val: any) => emits('update:modelValue', isMssql(column?.value?.base_id) ? +val : val),
})
@@ -59,7 +59,7 @@ function onClick(force?: boolean, event?: MouseEvent) {
return
}
if (!readOnly?.value && (force || active.value)) {
- vModel = !vModel
+ vModel.value = !vModel.value
}
}
diff --git a/packages/nc-gui/components/cell/ClampedText.vue b/packages/nc-gui/components/cell/ClampedText.vue
index 88b4ab046d..d01c6fb0f4 100644
--- a/packages/nc-gui/components/cell/ClampedText.vue
+++ b/packages/nc-gui/components/cell/ClampedText.vue
@@ -13,7 +13,7 @@ const props = defineProps<{
'-webkit-line-clamp': props.lines || 1,
'-webkit-box-orient': 'vertical',
'overflow': 'hidden',
- 'white-space': 'pre',
+ 'word-break': 'break-all',
}"
>
{{ props.value || '' }}
diff --git a/packages/nc-gui/components/cell/Currency.vue b/packages/nc-gui/components/cell/Currency.vue
index 51aa023d4a..799e90592b 100644
--- a/packages/nc-gui/components/cell/Currency.vue
+++ b/packages/nc-gui/components/cell/Currency.vue
@@ -59,7 +59,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
const submitCurrency = () => {
if (lastSaved.value !== vModel.value) {
- lastSaved.value = vModel.value
+ vModel.value = lastSaved.value = vModel.value ?? null
emit('save')
}
editEnabled.value = false
diff --git a/packages/nc-gui/components/cell/DatePicker.vue b/packages/nc-gui/components/cell/DatePicker.vue
index 5cde5ec01f..ca3dac123e 100644
--- a/packages/nc-gui/components/cell/DatePicker.vue
+++ b/packages/nc-gui/components/cell/DatePicker.vue
@@ -30,22 +30,24 @@ const columnMeta = inject(ColumnInj, null)!
const readOnly = inject(ReadonlyInj, ref(false))
+const isLockedMode = inject(IsLockedInj, ref(false))
+
const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false))
-let isDateInvalid = $ref(false)
+const isDateInvalid = ref(false)
-const dateFormat = $computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD')
+const dateFormat = computed(() => parseProp(columnMeta?.value?.meta)?.date_format ?? 'YYYY-MM-DD')
-let localState = $computed({
+const localState = computed({
get() {
if (!modelValue) {
return undefined
}
if (!dayjs(modelValue).isValid()) {
- isDateInvalid = true
+ isDateInvalid.value = true
return undefined
}
@@ -77,7 +79,7 @@ watch(
{ flush: 'post' },
)
-const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid ? 'Invalid date' : ''))
+const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid.value ? 'Invalid date' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) {
@@ -110,7 +112,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
}
break
case 'ArrowLeft':
- if (!localState) {
+ if (!localState.value) {
;(document.querySelector('.nc-picker-date.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
} else {
const prevEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
@@ -133,7 +135,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
}
break
case 'ArrowRight':
- if (!localState) {
+ if (!localState.value) {
;(document.querySelector('.nc-picker-date.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
} else {
const nextEl = document.querySelector('.nc-picker-date.active .ant-picker-cell-selected')
@@ -156,15 +158,15 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
}
break
case 'ArrowUp':
- if (!localState)
+ if (!localState.value)
(document.querySelector('.nc-picker-date.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click()
break
case 'ArrowDown':
- if (!localState)
+ if (!localState.value)
(document.querySelector('.nc-picker-date.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click()
break
case ';':
- localState = dayjs(new Date())
+ localState.value = dayjs(new Date())
break
}
})
@@ -206,7 +208,7 @@ const clickHandler = () => {
:allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true"
:dropdown-class-name="`${randomClass} nc-picker-date ${open ? 'active' : ''}`"
- :open="(readOnly || (localState && isPk)) && !active && !editable ? false : open"
+ :open="((readOnly || (localState && isPk)) && !active && !editable) || isLockedMode ? false : open"
@click="clickHandler"
@update:open="updateOpen"
>
diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue
index 397ceda16f..38c0d1417d 100644
--- a/packages/nc-gui/components/cell/DateTimePicker.vue
+++ b/packages/nc-gui/components/cell/DateTimePicker.vue
@@ -36,11 +36,13 @@ const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false))
+const isLockedMode = inject(IsLockedInj, ref(false))
+
const column = inject(ColumnInj)!
-let isDateInvalid = $ref(false)
+const isDateInvalid = ref(false)
-const dateTimeFormat = $computed(() => {
+const dateTimeFormat = computed(() => {
const dateFormat = parseProp(column?.value?.meta)?.date_format ?? dateFormats[0]
const timeFormat = parseProp(column?.value?.meta)?.time_format ?? timeFormats[0]
return `${dateFormat} ${timeFormat}`
@@ -48,14 +50,14 @@ const dateTimeFormat = $computed(() => {
let localModelValue = modelValue ? dayjs(modelValue).utc().local() : undefined
-let localState = $computed({
+const localState = computed({
get() {
if (!modelValue) {
return undefined
}
if (!dayjs(modelValue).isValid()) {
- isDateInvalid = true
+ isDateInvalid.value = true
return undefined
}
@@ -129,7 +131,7 @@ watch(
{ flush: 'post' },
)
-const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid ? 'Invalid date' : ''))
+const placeholder = computed(() => (modelValue === null && showNull.value ? 'NULL' : isDateInvalid.value ? 'Invalid date' : ''))
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) {
@@ -158,7 +160,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
}
break
case 'ArrowLeft':
- if (!localState) {
+ if (!localState.value) {
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-prev-btn') as HTMLButtonElement)?.click()
} else {
const prevEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
@@ -181,7 +183,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
}
break
case 'ArrowRight':
- if (!localState) {
+ if (!localState.value) {
;(document.querySelector('.nc-picker-datetime.active .ant-picker-header-next-btn') as HTMLButtonElement)?.click()
} else {
const nextEl = document.querySelector('.nc-picker-datetime.active .ant-picker-cell-selected')
@@ -204,15 +206,15 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
}
break
case 'ArrowUp':
- if (!localState)
+ if (!localState.value)
(document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-prev-btn') as HTMLButtonElement)?.click()
break
case 'ArrowDown':
- if (!localState)
+ if (!localState.value)
(document.querySelector('.nc-picker-datetime.active .ant-picker-header-super-next-btn') as HTMLButtonElement)?.click()
break
case ';':
- localState = dayjs(new Date())
+ localState.value = dayjs(new Date())
break
}
})
@@ -248,7 +250,7 @@ const clickHandler = () => {
:allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true"
:dropdown-class-name="`${randomClass} nc-picker-datetime ${open ? 'active' : ''}`"
- :open="readOnly || (localState && isPk) ? false : open && (active || editable)"
+ :open="readOnly || (localState && isPk) || isLockedMode ? false : open && (active || editable)"
:disabled="readOnly || (localState && isPk)"
@click="clickHandler"
@ok="open = !open"
diff --git a/packages/nc-gui/components/cell/Decimal.vue b/packages/nc-gui/components/cell/Decimal.vue
index 562a4ac858..87b2170325 100644
--- a/packages/nc-gui/components/cell/Decimal.vue
+++ b/packages/nc-gui/components/cell/Decimal.vue
@@ -21,8 +21,24 @@ const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj)
+const column = inject(ColumnInj, null)!
+
+const domRef = ref()
+
+const meta = computed(() => {
+ return typeof column?.value.meta === 'string' ? JSON.parse(column.value.meta) : column?.value.meta ?? {}
+})
+
const _vModel = useVModel(props, 'modelValue', emits)
+const displayValue = computed(() => {
+ if (_vModel.value === null) return null
+
+ if (isNaN(Number(_vModel.value))) return null
+
+ return Number(_vModel.value).toFixed(meta.value.precision ?? 1)
+})
+
const vModel = computed({
get: () => _vModel.value,
set: (value) => {
@@ -36,9 +52,39 @@ const vModel = computed({
},
})
+const precision = computed(() => {
+ const meta = typeof column?.value.meta === 'string' ? JSON.parse(column.value.meta) : column?.value.meta ?? {}
+ const _precision = meta.precision ?? 1
+
+ return Number(0.1 ** _precision).toFixed(_precision)
+})
+
const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
+// Handle the arrow keys as its default behavior is to increment/decrement the value
+const onKeyDown = (e: any) => {
+ if (e.key === 'ArrowDown') {
+ e.preventDefault()
+ // Move the cursor to the end of the input
+ e.target.type = 'text'
+ e.target?.setSelectionRange(e.target.value.length, e.target.value.length)
+ e.target.type = 'number'
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault()
+
+ e.target.type = 'text'
+ e.target?.setSelectionRange(0, 0)
+ e.target.type = 'number'
+ }
+}
+
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
+
+watch(isExpandedFormOpen, () => {
+ if (!isExpandedFormOpen.value) {
+ domRef.value?.focus()
+ }
+})
@@ -46,14 +92,15 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
v-if="editEnabled"
:ref="focus"
v-model="vModel"
- class="outline-none px-2 border-none w-full h-full text-sm"
+ class="outline-none !p-0 border-none w-full h-full text-sm"
type="number"
- step="0.1"
+ :step="precision"
+ style="letter-spacing: 0.06rem"
@blur="editEnabled = false"
- @keydown.down.stop
+ @keydown.down.stop="onKeyDown"
@keydown.left.stop
@keydown.right.stop
- @keydown.up.stop
+ @keydown.up.stop="onKeyDown"
@keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@@ -61,11 +108,23 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
@mousedown.stop
/>
NULL
- {{ vModel }}
+ {{ displayValue }}
diff --git a/packages/nc-gui/components/cell/Duration.vue b/packages/nc-gui/components/cell/Duration.vue
index f9c9f80eb1..6c579311a9 100644
--- a/packages/nc-gui/components/cell/Duration.vue
+++ b/packages/nc-gui/components/cell/Duration.vue
@@ -15,7 +15,7 @@ import {
interface Props {
modelValue: number | string | null | undefined
- showValidationError: boolean
+ showValidationError?: boolean
}
const { modelValue, showValidationError = true } = defineProps()
diff --git a/packages/nc-gui/components/cell/Email.vue b/packages/nc-gui/components/cell/Email.vue
index b15b6d4e52..2c4669d118 100644
--- a/packages/nc-gui/components/cell/Email.vue
+++ b/packages/nc-gui/components/cell/Email.vue
@@ -10,6 +10,8 @@ const { modelValue: value } = defineProps()
const emit = defineEmits(['update:modelValue'])
+const rowHeight = inject(RowHeightInj, ref(undefined))
+
const { t } = useI18n()
const { showNull } = useGlobal()
@@ -73,8 +75,8 @@ watch(
NULL
- {{ vModel }}
+
- {{ vModel }}
+
diff --git a/packages/nc-gui/components/cell/GeoData.vue b/packages/nc-gui/components/cell/GeoData.vue
index 68a3cdcf11..042f5bd838 100644
--- a/packages/nc-gui/components/cell/GeoData.vue
+++ b/packages/nc-gui/components/cell/GeoData.vue
@@ -16,17 +16,17 @@ const emits = defineEmits()
const vModel = useVModel(props, 'modelValue', emits)
-let isExpanded = $ref(false)
+const isExpanded = ref(false)
-let isLoading = $ref(false)
+const isLoading = ref(false)
-let isLocationSet = $ref(false)
+const isLocationSet = ref(false)
const [latitude, longitude] = (vModel.value || '').split(';')
const latLongStr = computed(() => {
const [latitude, longitude] = (vModel.value || '').split(';')
- if (latitude) isLocationSet = true
+ if (latitude) isLocationSet.value = true
return latitude && longitude ? `${latitude}; ${longitude}` : 'Set location'
})
@@ -37,28 +37,28 @@ const formState = reactive({
const handleFinish = () => {
vModel.value = latLongToJoinedString(parseFloat(formState.latitude), parseFloat(formState.longitude))
- isExpanded = false
+ isExpanded.value = false
}
const clear = () => {
- isExpanded = false
+ isExpanded.value = false
formState.latitude = latitude
formState.longitude = longitude
}
const onClickSetCurrentLocation = () => {
- isLoading = true
+ isLoading.value = true
const onSuccess: PositionCallback = (position: GeolocationPosition) => {
const crd = position.coords
formState.latitude = `${crd.latitude}`
formState.longitude = `${crd.longitude}`
- isLoading = false
+ isLoading.value = false
}
const onError: PositionErrorCallback = (err: GeolocationPositionError) => {
console.error(`ERROR(${err.code}): ${err.message}`)
- isLoading = false
+ isLoading.value = false
}
const options = {
diff --git a/packages/nc-gui/components/cell/Integer.vue b/packages/nc-gui/components/cell/Integer.vue
index 0800c09823..793d9ed5c3 100644
--- a/packages/nc-gui/components/cell/Integer.vue
+++ b/packages/nc-gui/components/cell/Integer.vue
@@ -23,6 +23,14 @@ const editEnabled = inject(EditModeInj)
const _vModel = useVModel(props, 'modelValue', emits)
+const displayValue = computed(() => {
+ if (_vModel.value === null) return null
+
+ if (isNaN(Number(_vModel.value))) return null
+
+ return Number(_vModel.value)
+})
+
const vModel = computed({
get: () => _vModel.value,
set: (value) => {
@@ -40,17 +48,33 @@ const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
-function onKeyDown(evt: KeyboardEvent) {
- const cmdOrCtrl = isMac() ? evt.metaKey : evt.ctrlKey
- if (cmdOrCtrl && !evt.altKey) {
- switch (evt.keyCode) {
+function onKeyDown(e: any) {
+ const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
+ if (cmdOrCtrl && !e.altKey) {
+ switch (e.keyCode) {
case 90: {
- evt.stopPropagation()
+ e.stopPropagation()
break
}
}
}
- return evt.key === '.' && evt.preventDefault()
+ if (e.key === '.') {
+ return e.preventDefault()
+ }
+
+ if (e.key === 'ArrowDown') {
+ e.preventDefault()
+ // Move the cursor to the end of the input
+ e.target.type = 'text'
+ e.target?.setSelectionRange(e.target.value.length, e.target.value.length)
+ e.target.type = 'number'
+ } else if (e.key === 'ArrowUp') {
+ e.preventDefault()
+
+ e.target.type = 'text'
+ e.target?.setSelectionRange(0, 0)
+ e.target.type = 'number'
+ }
}
@@ -61,6 +85,7 @@ function onKeyDown(evt: KeyboardEvent) {
v-model="vModel"
class="outline-none p-0 border-none w-full h-full text-sm"
type="number"
+ style="letter-spacing: 0.06rem"
@blur="editEnabled = false"
@keydown="onKeyDown"
@keydown.down.stop
@@ -72,11 +97,23 @@ function onKeyDown(evt: KeyboardEvent) {
@mousedown.stop
/>
NULL
- {{ vModel }}
+ {{ displayValue }}
diff --git a/packages/nc-gui/components/cell/Json.vue b/packages/nc-gui/components/cell/Json.vue
index 3042946ff4..1206b4b5b2 100644
--- a/packages/nc-gui/components/cell/Json.vue
+++ b/packages/nc-gui/components/cell/Json.vue
@@ -39,25 +39,25 @@ const vModel = useVModel(props, 'modelValue', emits)
const localValueState = ref()
-let error = $ref()
+const error = ref()
-let isExpanded = $ref(false)
+const isExpanded = ref(false)
const localValue = computed | undefined>({
get: () => localValueState.value,
set: (val: undefined | string | Record) => {
localValueState.value = typeof val === 'object' ? JSON.stringify(val, null, 2) : val
/** if form and not expanded then sync directly */
- if (isForm.value && !isExpanded) {
+ if (isForm.value && !isExpanded.value) {
vModel.value = val
}
},
})
const clear = () => {
- error = undefined
+ error.value = undefined
- isExpanded = false
+ isExpanded.value = false
editEnabled.value = false
@@ -66,44 +66,59 @@ const clear = () => {
const formatJson = (json: string) => {
try {
- return JSON.stringify(JSON.parse(json), null, 2)
+ json = json
+ .trim()
+ .replace(/^\{\s*|\s*\}$/g, '')
+ .replace(/\n\s*/g, '')
+ json = `{${json}}`
+
+ return json
} catch (e) {
+ console.log(e)
return json
}
}
const onSave = () => {
- isExpanded = false
+ isExpanded.value = false
editEnabled.value = false
- localValue.value = localValue ? formatJson(localValue.value as string) : localValue
+ vModel.value = localValue ? formatJson(localValue.value as string) : localValue
+}
- vModel.value = localValue.value
+const setLocalValue = (val: any) => {
+ try {
+ localValue.value = typeof val === 'string' ? JSON.stringify(JSON.parse(val), null, 2) : val
+ } catch (e) {
+ localValue.value = val
+ }
}
watch(
vModel,
(val) => {
- localValue.value = val
+ setLocalValue(val)
},
{ immediate: true },
)
-watch(localValue, (val) => {
+watch([localValue, editEnabled], () => {
try {
- JSON.parse(val as string)
+ JSON.parse(localValue.value as string)
- error = undefined
+ error.value = undefined
} catch (e: any) {
- error = e
+ if (localValue.value === undefined) return
+
+ error.value = e
}
})
watch(editEnabled, () => {
- isExpanded = false
+ isExpanded.value = false
- localValue.value = vModel.value
+ setLocalValue(vModel.value)
})
useSelectedCellKeyupListener(active, (e) => {
diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue
index 7a22788276..69d7628c6b 100644
--- a/packages/nc-gui/components/cell/MultiSelect.vue
+++ b/packages/nc-gui/components/cell/MultiSelect.vue
@@ -4,6 +4,7 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk'
+import { WorkspaceUserRoles } from 'nocodb-sdk'
import {
ActiveCellInj,
CellClickHookInj,
@@ -46,6 +47,8 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)!
+const isLockedMode = inject(IsLockedInj, ref(false))
+
const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false))
@@ -99,7 +102,15 @@ 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 hasEditRoles = computed(
+ () =>
+ hasRole('owner', true) ||
+ hasRole('creator', true) ||
+ hasRole('editor', true) ||
+ hasRole(WorkspaceUserRoles.OWNER, true) ||
+ hasRole(WorkspaceUserRoles.CREATOR, true) ||
+ hasRole(WorkspaceUserRoles.EDITOR, true),
+)
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
@@ -334,7 +345,11 @@ const selectedOpts = computed(() => {
-
+
{
:bordered="false"
clear-icon
show-search
- :show-arrow="editAllowed && !readOnly"
+ :show-arrow="editAllowed && !(readOnly || isLockedMode)"
:open="isOpen && editAllowed"
- :disabled="readOnly || !editAllowed"
+ :disabled="readOnly || !editAllowed || isLockedMode"
:class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`"
@search="search"
@@ -409,7 +424,10 @@ const selectedOpts = computed(() => {
isOptionMissing &&
!isPublic &&
!disableOptionCreation &&
- (hasRole('owner', true) || hasRole('creator', true))
+ (hasRole('owner', true) ||
+ hasRole('creator', true) ||
+ hasRole(WorkspaceUserRoles.OWNER, true) ||
+ hasRole(WorkspaceUserRoles.CREATOR, true))
"
:key="searchVal"
:value="searchVal"
@@ -481,6 +499,12 @@ const selectedOpts = computed(() => {
color: rgba(0, 0, 0, 0.45);
}
+.read-only {
+ .ms-close-icon {
+ display: none;
+ }
+}
+
.rounded-tag {
@apply py-0 px-[12px] rounded-[12px];
}
diff --git a/packages/nc-gui/components/cell/Rating.vue b/packages/nc-gui/components/cell/Rating.vue
index aab416582d..3e6299b212 100644
--- a/packages/nc-gui/components/cell/Rating.vue
+++ b/packages/nc-gui/components/cell/Rating.vue
@@ -11,6 +11,8 @@ const emits = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)!
+const readonly = inject(ReadonlyInj, ref(false))
+
const ratingMeta = computed(() => {
return {
icon: {
@@ -37,7 +39,12 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
-
+
diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue
index d05eab3966..b1485c9bab 100644
--- a/packages/nc-gui/components/cell/SingleSelect.vue
+++ b/packages/nc-gui/components/cell/SingleSelect.vue
@@ -4,6 +4,7 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType } from 'nocodb-sdk'
+import { WorkspaceUserRoles } from 'nocodb-sdk'
import {
ActiveCellInj,
CellClickHookInj,
@@ -40,6 +41,8 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)!
+const isLockedMode = inject(IsLockedInj, ref(false))
+
const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false))
@@ -73,7 +76,13 @@ const { isPg, isMysql } = useProject()
const tempSelectedOptState = ref()
const isNewOptionCreateEnabled = computed(
- () => !isPublic.value && !disableOptionCreation && (hasRole('owner', true) || hasRole('creator', true)),
+ () =>
+ !isPublic.value &&
+ !disableOptionCreation &&
+ (hasRole('owner', true) ||
+ hasRole('creator', true) ||
+ hasRole(WorkspaceUserRoles.OWNER, true) ||
+ hasRole(WorkspaceUserRoles.CREATOR, true)),
)
const options = computed<(SelectOptionType & { value: string })[]>(() => {
@@ -94,7 +103,15 @@ 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 hasEditRoles = computed(
+ () =>
+ hasRole('owner', true) ||
+ hasRole('creator', true) ||
+ hasRole('editor', true) ||
+ hasRole(WorkspaceUserRoles.OWNER, true) ||
+ hasRole(WorkspaceUserRoles.CREATOR, true) ||
+ hasRole(WorkspaceUserRoles.EDITOR, true),
+)
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
@@ -256,7 +273,11 @@ const selectedOpt = computed(() => {
-
+
{
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && editAllowed"
- :disabled="readOnly || !editAllowed"
- :show-arrow="hasEditRoles && !readOnly && active && vModel === null"
+ :disabled="readOnly || !editAllowed || isLockedMode"
+ :show-arrow="hasEditRoles && !(readOnly || isLockedMode) && active && vModel === null"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`"
:show-search="isOpen && active"
@select="onSelect"
diff --git a/packages/nc-gui/components/cell/TextArea.vue b/packages/nc-gui/components/cell/TextArea.vue
index 6b7d2b1406..81782d1bd7 100644
--- a/packages/nc-gui/components/cell/TextArea.vue
+++ b/packages/nc-gui/components/cell/TextArea.vue
@@ -8,6 +8,8 @@ const props = defineProps<{
const emits = defineEmits(['update:modelValue'])
+const column = inject(ColumnInj)
+
const editEnabled = inject(EditModeInj)
const rowHeight = inject(RowHeightInj, ref(undefined))
@@ -19,35 +21,111 @@ const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLTextAreaElement)?.focus()
+
+const height = computed(() => {
+ if (!rowHeight.value) return 60
+
+ return rowHeight.value * 60
+})
+
+const isVisible = ref(false)
+const inputWrapperRef = ref(null)
+const inputRef = ref(null)
+
+watch(isVisible, () => {
+ if (isVisible.value) {
+ setTimeout(() => {
+ inputRef.value?.focus()
+ }, 100)
+ }
+})
+
+onClickOutside(inputWrapperRef, (e) => {
+ if ((e.target as HTMLElement)?.className.includes('nc-long-text-toggle-expand')) return
+
+ isVisible.value = false
+})
-
-
- NULL
-
-
-
- {{ vModel }}
+
+
+
+
+ NULL
+
+
+
+ {{ vModel }}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeView.vue b/packages/nc-gui/components/dashboard/TreeView.vue
index 671d84e075..9e7151cba7 100644
--- a/packages/nc-gui/components/dashboard/TreeView.vue
+++ b/packages/nc-gui/components/dashboard/TreeView.vue
@@ -1,11 +1,11 @@
+
+
+
+
+
+
{{ $t('tooltip.addTable') }}
+
+
+
+
+
+
+
+
+
+ Noco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeViewNew/BaseOptions.vue b/packages/nc-gui/components/dashboard/TreeViewNew/BaseOptions.vue
new file mode 100644
index 0000000000..1d0346177f
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/TreeViewNew/BaseOptions.vue
@@ -0,0 +1,179 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeViewNew/ProjectNode.vue b/packages/nc-gui/components/dashboard/TreeViewNew/ProjectNode.vue
new file mode 100644
index 0000000000..8168f11f00
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/TreeViewNew/ProjectNode.vue
@@ -0,0 +1,739 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeViewNew/ProjectWrapper.vue b/packages/nc-gui/components/dashboard/TreeViewNew/ProjectWrapper.vue
new file mode 100644
index 0000000000..7637353ce4
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/TreeViewNew/ProjectWrapper.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeViewNew/TableList.vue b/packages/nc-gui/components/dashboard/TreeViewNew/TableList.vue
new file mode 100644
index 0000000000..6f8beef505
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/TreeViewNew/TableList.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeViewNew/TableNode.vue b/packages/nc-gui/components/dashboard/TreeViewNew/TableNode.vue
new file mode 100644
index 0000000000..4a6b9b6ce2
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/TreeViewNew/TableNode.vue
@@ -0,0 +1,230 @@
+
+
+
+
+
+ {{ table.table_name }}
+
+
+
+
+
+
+
+ {{ 'Change icon' }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ table.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeViewNew/index.vue b/packages/nc-gui/components/dashboard/TreeViewNew/index.vue
new file mode 100644
index 0000000000..99453e31ad
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/TreeViewNew/index.vue
@@ -0,0 +1,432 @@
+
+
+
+
+
+
+
+
+ Star
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/settings/AppStore.vue b/packages/nc-gui/components/dashboard/settings/AppStore.vue
index 49b140119d..afc6747b14 100644
--- a/packages/nc-gui/components/dashboard/settings/AppStore.vue
+++ b/packages/nc-gui/components/dashboard/settings/AppStore.vue
@@ -5,19 +5,19 @@ const { t } = useI18n()
const { $api, $e } = useNuxtApp()
-let apps = $ref(null)
+const apps = ref(null)
-let showPluginUninstallModal = $ref(false)
+const showPluginUninstallModal = ref(false)
-let showPluginInstallModal = $ref(false)
+const showPluginInstallModal = ref(false)
-let pluginApp = $ref(null)
+const pluginApp = ref(null)
const fetchPluginApps = async () => {
try {
const plugins = (await $api.plugin.list()).list ?? []
- apps = plugins.map((p) => ({
+ apps.value = plugins.map((p) => ({
...p,
tags: p.tags ? p.tags.split(',') : [],
parsedInput: p.input && JSON.parse(p.input as string),
@@ -29,41 +29,41 @@ const fetchPluginApps = async () => {
const resetPlugin = async () => {
try {
- await $api.plugin.update(pluginApp.id, {
+ await $api.plugin.update(pluginApp.value.id, {
input: null,
active: false,
})
// Plugin uninstalled successfully
message.success(t('msg.success.pluginUninstalled'))
- showPluginUninstallModal = false
+ showPluginUninstallModal.value = false
await fetchPluginApps()
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
- $e('a:appstore:reset', { app: pluginApp.title })
+ $e('a:appstore:reset', { app: pluginApp.value.title })
}
const saved = async () => {
- showPluginInstallModal = false
+ showPluginInstallModal.value = false
await fetchPluginApps()
- $e('a:appstore:install', { app: pluginApp.title })
+ $e('a:appstore:install', { app: pluginApp.value.title })
}
const showInstallPluginModal = async (app: any) => {
- showPluginInstallModal = true
- pluginApp = app
+ showPluginInstallModal.value = true
+ pluginApp.value = app
$e('c:appstore:install', { app: app.title })
}
const showResetPluginModal = async (app: any) => {
- showPluginUninstallModal = true
- pluginApp = app
+ showPluginUninstallModal.value = true
+ pluginApp.value = app
}
onMounted(async () => {
- if (apps === null) {
+ if (apps.value === null) {
await fetchPluginApps()
}
})
@@ -81,7 +81,7 @@ onMounted(async () => {
wrap-class-name="nc-modal-plugin-install"
v-bind="$attrs"
>
-
import { Tooltip as ATooltip, Empty } from 'ant-design-vue'
import type { AuditType } from 'nocodb-sdk'
-import { h, iconMap, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useProject } from '#imports'
+import { ProjectIdInj, h, iconMap, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useProject } from '#imports'
const { $api } = useNuxtApp()
const { project } = storeToRefs(useProject())
+const _projectId = inject(ProjectIdInj, undefined)
+const projectId = computed(() => _projectId.value ?? project.value?.id)
+
const { t } = useI18n()
-let isLoading = $ref(false)
+const isLoading = ref(false)
-let audits = $ref>(null)
+const audits = ref>(null)
-let totalRows = $ref(0)
+const totalRows = ref(0)
-const currentPage = $ref(1)
+const currentPage = ref(1)
-const currentLimit = $ref(25)
+const currentLimit = ref(25)
const { appInfo } = useGlobal()
-async function loadAudits(page = currentPage, limit = currentLimit) {
+async function loadAudits(page = currentPage.value, limit = currentLimit.value) {
try {
if (!project.value?.id) return
- isLoading = true
+ isLoading.value = true
- const { list, pageInfo } = await $api.project.auditList(project.value?.id, {
+ const { list, pageInfo } = await $api.project.auditList(projectId.value, {
offset: limit * (page - 1),
limit,
})
- audits = list
- totalRows = pageInfo.totalRows ?? 0
+ audits.value = list
+ totalRows.value = pageInfo.totalRows ?? 0
} catch (e) {
console.error(e)
} finally {
- isLoading = false
+ isLoading.value = false
}
}
onMounted(async () => {
- if (audits === null) {
- await loadAudits(currentPage, currentLimit)
+ if (audits.value === null) {
+ await loadAudits(currentPage.value, currentLimit.value)
}
})
@@ -120,7 +123,7 @@ const columns = [
diff --git a/packages/nc-gui/components/dashboard/settings/BaseAudit.vue b/packages/nc-gui/components/dashboard/settings/BaseAudit.vue
new file mode 100644
index 0000000000..d526aac2ea
--- /dev/null
+++ b/packages/nc-gui/components/dashboard/settings/BaseAudit.vue
@@ -0,0 +1,153 @@
+
+
+
+
+
Audit logs are currently disabled by administrators.
+
+
Audit : {{ project.title }}
+
+
+
+
+ {{ $t('general.reload') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/nc-gui/components/dashboard/settings/DataSources.vue b/packages/nc-gui/components/dashboard/settings/DataSources.vue
index af5938af24..3319fc36a6 100644
--- a/packages/nc-gui/components/dashboard/settings/DataSources.vue
+++ b/packages/nc-gui/components/dashboard/settings/DataSources.vue
@@ -6,12 +6,12 @@ import EditBase from './data-sources/EditBase.vue'
import Metadata from './Metadata.vue'
import UIAcl from './UIAcl.vue'
import Erd from './Erd.vue'
-import { ClientType, DataSourcesSubTab } from '~/lib'
-import { storeToRefs, useNuxtApp, useProject } from '#imports'
+import BaseAudit from './BaseAudit.vue'
+import { ClientType, DataSourcesSubTab, storeToRefs, useCommandPalette, useNuxtApp, useProject } from '#imports'
interface Props {
state: string
- reload: boolean
+ reload?: boolean
}
const props = defineProps()
@@ -23,50 +23,59 @@ const vState = useVModel(props, 'state', emits)
const vReload = useVModel(props, 'reload', emits)
const { $api, $e } = useNuxtApp()
+
+const { loadProject } = useProjects()
+
const projectStore = useProject()
-const { loadProject } = projectStore
const { project } = storeToRefs(projectStore)
-let sources = $ref([])
+const { refreshCommandPalette } = useCommandPalette()
+
+const sources = ref([])
+
+const activeBaseId = ref('')
+
+const metadiffbases = ref([])
-let activeBaseId = $ref('')
+const clientType = ref(ClientType.MYSQL)
-let metadiffbases = $ref([])
+const isReloading = ref(false)
-let clientType = $ref(ClientType.MYSQL)
+const forceAwakened = ref(false)
-let isReloading = $ref(false)
+const dataSourcesAwakened = ref(false)
-let forceAwakened = $ref(false)
+const isDeleteBaseModalOpen = ref(false)
+const toBeDeletedBase = ref()
-async function loadBases() {
+async function loadBases(changed?: boolean) {
try {
- if (!project.value?.id) return
+ if (changed) refreshCommandPalette()
- isReloading = true
+ isReloading.value = true
vReload.value = true
- const baseList = await $api.base.list(project.value?.id)
+ const baseList = await $api.base.list(project.value.id as string)
if (baseList.list && baseList.list.length) {
- sources = baseList.list
+ sources.value = baseList.list
}
+
+ await loadMetaDiff()
} catch (e) {
console.error(e)
} finally {
vReload.value = false
- isReloading = false
+ isReloading.value = false
}
}
async function loadMetaDiff() {
try {
- if (!project.value?.id) return
+ metadiffbases.value = []
- metadiffbases = []
-
- const metadiff = await $api.project.metaDiffGet(project.value?.id)
+ const metadiff = await $api.project.metaDiffGet(project.value.id as string)
for (const model of metadiff) {
if (model.detectedChanges?.length > 0) {
- metadiffbases.push(model.base_id)
+ metadiffbases.value.push(model.base_id)
}
}
} catch (e) {
@@ -76,38 +85,34 @@ async function loadMetaDiff() {
const baseAction = (baseId?: string, action?: string) => {
if (!baseId) return
- activeBaseId = baseId
+ activeBaseId.value = baseId
vState.value = action || ''
}
-const deleteBase = (base: BaseType) => {
+const openDeleteBase = (base: BaseType) => {
$e('c:base:delete')
+ isDeleteBaseModalOpen.value = true
+ toBeDeletedBase.value = base
+}
- Modal.confirm({
- title: `Do you want to delete '${base.alias}' project?`,
- wrapClassName: 'nc-modal-base-delete',
- okText: 'Yes',
- okType: 'danger',
- cancelText: 'No',
- async onOk() {
- try {
- await $api.base.delete(base.project_id as string, base.id as string)
-
- $e('a:base:delete')
-
- sources.splice(sources.indexOf(base), 1)
- await loadProject()
- } catch (e: any) {
- message.error(await extractSdkResponseErrorMsg(e))
- }
- },
- style: 'top: 30%!important',
- })
+const deleteBase = async () => {
+ if (!toBeDeletedBase.value) return
+
+ try {
+ await $api.base.delete(toBeDeletedBase.value.project_id as string, toBeDeletedBase.value.id as string)
+
+ $e('a:base:delete')
+
+ sources.value.splice(sources.value.indexOf(toBeDeletedBase.value), 1)
+ await loadProject(project.value.id as string, true)
+ } catch (e: any) {
+ message.error(await extractSdkResponseErrorMsg(e))
+ }
}
const toggleBase = async (base: BaseType, state: boolean) => {
try {
- if (!state && sources.filter((src) => src.enabled).length < 2) {
+ if (!state && sources.value.filter((src) => src.enabled).length < 2) {
message.info('There should be at least one enabled base!')
return
}
@@ -117,7 +122,7 @@ const toggleBase = async (base: BaseType, state: boolean) => {
project_id: base.project_id,
enabled: base.enabled,
})
- await loadProject()
+ await loadProject(project.value.id as string, true)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@@ -127,7 +132,7 @@ const moveBase = async (e: any) => {
try {
if (e.oldIndex === e.newIndex) return
// sources list is mutated so we have to get the new index and mirror it to backend
- const base = sources[e.newIndex]
+ const base = sources.value[e.newIndex]
if (base) {
if (!base.order) {
// empty update call to reorder bases (migration)
@@ -144,7 +149,7 @@ const moveBase = async (e: any) => {
})
}
}
- await loadProject()
+ await loadProject(project.value.id as string, true)
await loadBases()
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
@@ -152,12 +157,13 @@ const moveBase = async (e: any) => {
}
const forceAwaken = () => {
- forceAwakened = !forceAwakened
- emits('awaken', forceAwakened)
+ forceAwakened.value = !forceAwakened.value
+ dataSourcesAwakened.value = forceAwakened.value
+ emits('awaken', forceAwakened.value)
}
onMounted(async () => {
- if (sources.length === 0) {
+ if (sources.value.length === 0) {
await loadBases()
await loadMetaDiff()
}
@@ -166,7 +172,7 @@ onMounted(async () => {
watch(
() => props.reload,
async (reload) => {
- if (reload && !isReloading) {
+ if (reload && !isReloading.value) {
await loadBases()
await loadMetaDiff()
}
@@ -174,11 +180,13 @@ watch(
)
watch(
- () => sources.length,
+ () => sources.value.length,
(l) => {
- if (l > 1 && !forceAwakened) {
+ if (l > 1 && !forceAwakened.value) {
+ dataSourcesAwakened.value = false
emits('awaken', false)
} else {
+ dataSourcesAwakened.value = true
emits('awaken', true)
}
},
@@ -188,32 +196,32 @@ watch(
watch(
vState,
async (newState) => {
- if (!sources.length) {
+ if (!sources.value.length) {
await loadBases()
}
switch (newState) {
case ClientType.MYSQL:
- clientType = ClientType.MYSQL
+ clientType.value = ClientType.MYSQL
vState.value = DataSourcesSubTab.New
break
case ClientType.PG:
- clientType = ClientType.PG
+ clientType.value = ClientType.PG
vState.value = DataSourcesSubTab.New
break
case ClientType.SQLITE:
- clientType = ClientType.SQLITE
+ clientType.value = ClientType.SQLITE
vState.value = DataSourcesSubTab.New
break
case ClientType.MSSQL:
- clientType = ClientType.MSSQL
+ clientType.value = ClientType.MSSQL
vState.value = DataSourcesSubTab.New
break
case ClientType.SNOWFLAKE:
- clientType = ClientType.SNOWFLAKE
+ clientType.value = ClientType.SNOWFLAKE
vState.value = DataSourcesSubTab.New
break
case DataSourcesSubTab.New:
- if (sources.length > 1 && !forceAwakened) {
+ if (sources.value.length > 1 && !forceAwakened.value) {
vState.value = ''
}
break
@@ -221,38 +229,144 @@ watch(
},
{ immediate: true },
)
+
+const isNewBaseModalOpen = computed({
+ get: () => {
+ return [DataSourcesSubTab.New].includes(vState.value as any)
+ },
+ set: (val) => {
+ if (!val) {
+ vState.value = ''
+ }
+ },
+})
+
+const isErdModalOpen = computed({
+ get: () => {
+ return [DataSourcesSubTab.ERD].includes(vState.value as any)
+ },
+ set: (val) => {
+ if (!val) {
+ vState.value = ''
+ }
+ },
+})
+
+const isMetaDataModal = computed({
+ get: () => {
+ return [DataSourcesSubTab.Metadata].includes(vState.value as any)
+ },
+ set: (val) => {
+ if (!val) {
+ vState.value = ''
+ }
+ },
+})
+
+const isUIAclModalOpen = computed({
+ get: () => {
+ return [DataSourcesSubTab.UIAcl].includes(vState.value as any)
+ },
+ set: (val) => {
+ if (!val) {
+ vState.value = ''
+ }
+ },
+})
+const isBaseAuditModalOpen = computed({
+ get: () => {
+ return [DataSourcesSubTab.Audit].includes(vState.value as any)
+ },
+ set: (val) => {
+ if (!val) {
+ vState.value = ''
+ }
+ },
+})
+
+const isEditBaseModalOpen = computed({
+ get: () => {
+ return [DataSourcesSubTab.Edit].includes(vState.value as any)
+ },
+ set: (val) => {
+ if (!val) {
+ vState.value = ''
+ }
+ },
+})
-