diff --git a/packages/nc-gui/components/apiOverlay.vue b/packages/nc-gui/components/ApiOverlay.vue similarity index 100% rename from packages/nc-gui/components/apiOverlay.vue rename to packages/nc-gui/components/ApiOverlay.vue diff --git a/packages/nc-gui/components/authTab.vue b/packages/nc-gui/components/AuthTab.vue similarity index 93% rename from packages/nc-gui/components/authTab.vue rename to packages/nc-gui/components/AuthTab.vue index de92f93ac7..64a707254f 100644 --- a/packages/nc-gui/components/authTab.vue +++ b/packages/nc-gui/components/AuthTab.vue @@ -29,9 +29,9 @@ diff --git a/packages/nc-gui/components/projectLogs.vue b/packages/nc-gui/components/ProjectLogs.vue similarity index 100% rename from packages/nc-gui/components/projectLogs.vue rename to packages/nc-gui/components/ProjectLogs.vue diff --git a/packages/nc-gui/components/projectOutput.vue b/packages/nc-gui/components/ProjectOutput.vue similarity index 100% rename from packages/nc-gui/components/projectOutput.vue rename to packages/nc-gui/components/ProjectOutput.vue diff --git a/packages/nc-gui/components/projectTabs.vue b/packages/nc-gui/components/ProjectTabs.vue similarity index 61% rename from packages/nc-gui/components/projectTabs.vue rename to packages/nc-gui/components/ProjectTabs.vue index 2ce35f7b6e..bd9a422170 100644 --- a/packages/nc-gui/components/projectTabs.vue +++ b/packages/nc-gui/components/ProjectTabs.vue @@ -15,7 +15,7 @@ next-icon="mdi-arrow-right-bold-box-outline" prev-icon="mdi-arrow-left-bold-box-outline" show-arrows - :class="{ 'dark-them': $store.state.windows.darkTheme }" + :class="{ 'dark-them': $store.state.settings.darkTheme }" > @@ -42,14 +42,12 @@ max-width: 140px; text-overflow: ellipsis; " - >{{ tab.name }} + >{{ tab.name }} mdi-close -
- -
- - - mdi-plus-box - - - + + + + + + + + mdi-table + + + + {{ $t('tooltip.addTable') }} + + + + + + QUICK IMPORT FROM + + + + + mdi-table-large + + + + Airtable + + + + + + + mdi-file-document-outline + + + + CSV file + + + + + + + mdi-file-excel + + + + Microsoft Excel + + + + + + + + + + mdi-open-in-new + + + + Request a data source you need ? + + + + + + + + + - + diff --git a/packages/nc-gui/components/auth/userManagement.vue b/packages/nc-gui/components/auth/UserManagement.vue similarity index 78% rename from packages/nc-gui/components/auth/userManagement.vue rename to packages/nc-gui/components/auth/UserManagement.vue index 4a7b861ad3..86bbbcc5aa 100644 --- a/packages/nc-gui/components/auth/userManagement.vue +++ b/packages/nc-gui/components/auth/UserManagement.vue @@ -13,7 +13,9 @@ @keypress.enter="loadUsers" > @@ -29,7 +31,9 @@ @click="clickReload" @click.prevent > - refresh + + refresh + {{ $t("general.reload") }} @@ -45,7 +49,9 @@ :disabled="loading" @click="addUser" > - mdi-plus + + mdi-plus + {{ $t("activity.newUser") }} @@ -79,12 +85,16 @@ - mdi-email-outline + + mdi-email-outline + {{ $t("labels.email") }} - mdi-drama-masks + + mdi-drama-masks + {{ $t("objects.roles") }} @@ -223,7 +233,9 @@ - mdi-account-outline + + mdi-account-outline + - mdi-close - mdi-save + + mdi-close + + + mdi-save + - mdi-account-outline + + mdi-account-outline + @@ -254,7 +272,9 @@ - mdi-account-plus + + mdi-account-plus + @@ -285,9 +305,15 @@
- mdi-account-outline - - + + mdi-account-outline + + + diff --git a/packages/nc-gui/components/global/recursiveMenu.vue b/packages/nc-gui/components/global/RecursiveMenu.vue similarity index 100% rename from packages/nc-gui/components/global/recursiveMenu.vue rename to packages/nc-gui/components/global/RecursiveMenu.vue diff --git a/packages/nc-gui/components/global/xAutoComplete.vue b/packages/nc-gui/components/global/XAutoComplete.vue similarity index 100% rename from packages/nc-gui/components/global/xAutoComplete.vue rename to packages/nc-gui/components/global/XAutoComplete.vue diff --git a/packages/nc-gui/components/global/xBtn.vue b/packages/nc-gui/components/global/XBtn.vue similarity index 100% rename from packages/nc-gui/components/global/xBtn.vue rename to packages/nc-gui/components/global/XBtn.vue diff --git a/packages/nc-gui/components/global/xIcon.vue b/packages/nc-gui/components/global/XIcon.vue similarity index 100% rename from packages/nc-gui/components/global/xIcon.vue rename to packages/nc-gui/components/global/XIcon.vue diff --git a/packages/nc-gui/components/import/dropOrSelectFile.vue b/packages/nc-gui/components/import/DropOrSelectFile.vue similarity index 100% rename from packages/nc-gui/components/import/dropOrSelectFile.vue rename to packages/nc-gui/components/import/DropOrSelectFile.vue diff --git a/packages/nc-gui/components/import/dropOrSelectFileModal.vue b/packages/nc-gui/components/import/DropOrSelectFileModal.vue similarity index 97% rename from packages/nc-gui/components/import/dropOrSelectFileModal.vue rename to packages/nc-gui/components/import/DropOrSelectFileModal.vue index a6519e677d..624d6cb4c4 100644 --- a/packages/nc-gui/components/import/dropOrSelectFileModal.vue +++ b/packages/nc-gui/components/import/DropOrSelectFileModal.vue @@ -45,7 +45,7 @@ + + diff --git a/packages/nc-gui/components/import/excelImport.vue b/packages/nc-gui/components/import/QuickImport.vue similarity index 83% rename from packages/nc-gui/components/import/excelImport.vue rename to packages/nc-gui/components/import/QuickImport.vue index c9f172bb26..3ac14f1a68 100644 --- a/packages/nc-gui/components/import/excelImport.vue +++ b/packages/nc-gui/components/import/QuickImport.vue @@ -43,8 +43,8 @@ {{ $t('msg.info.upload_sub') }}

-

- +

+ {{ $t('msg.info.excelSupport') }}

@@ -60,7 +60,7 @@ v-model="url" hide-details="auto" type="url" - :label="$t('msg.info.excelURL')" + :label="quickImportType == 'excel' ? $t('msg.info.excelURL') : $t('msg.info.csvURL') " class="caption" outlined dense @@ -106,6 +106,7 @@ diff --git a/packages/nc-gui/components/project/auditTab/auditCE.vue b/packages/nc-gui/components/project/auditTab/AuditCE.vue similarity index 100% rename from packages/nc-gui/components/project/auditTab/auditCE.vue rename to packages/nc-gui/components/project/auditTab/AuditCE.vue diff --git a/packages/nc-gui/components/project/auditTab/db.vue b/packages/nc-gui/components/project/auditTab/Db.vue similarity index 100% rename from packages/nc-gui/components/project/auditTab/db.vue rename to packages/nc-gui/components/project/auditTab/Db.vue diff --git a/packages/nc-gui/components/project/dlgs/dlgAddRelation.vue b/packages/nc-gui/components/project/dlgs/DlgAddRelation.vue similarity index 100% rename from packages/nc-gui/components/project/dlgs/dlgAddRelation.vue rename to packages/nc-gui/components/project/dlgs/DlgAddRelation.vue diff --git a/packages/nc-gui/components/project/dlgs/dlgTriggerAddEdit.vue b/packages/nc-gui/components/project/dlgs/DlgTriggerAddEdit.vue similarity index 100% rename from packages/nc-gui/components/project/dlgs/dlgTriggerAddEdit.vue rename to packages/nc-gui/components/project/dlgs/DlgTriggerAddEdit.vue diff --git a/packages/nc-gui/components/project/functionTab/functionAcl.vue b/packages/nc-gui/components/project/functionTab/FunctionAcl.vue similarity index 100% rename from packages/nc-gui/components/project/functionTab/functionAcl.vue rename to packages/nc-gui/components/project/functionTab/FunctionAcl.vue diff --git a/packages/nc-gui/components/project/functionTab/functionQuery.vue b/packages/nc-gui/components/project/functionTab/FunctionQuery.vue similarity index 99% rename from packages/nc-gui/components/project/functionTab/functionQuery.vue rename to packages/nc-gui/components/project/functionTab/FunctionQuery.vue index 3a43b6e286..84538e528b 100644 --- a/packages/nc-gui/components/project/functionTab/functionQuery.vue +++ b/packages/nc-gui/components/project/functionTab/FunctionQuery.vue @@ -75,7 +75,7 @@ import { mapGetters, mapActions } from 'vuex' import { SqlUiFactory } from 'nocodb-sdk' import MonacoEditor from '../../monaco/Monaco' -import dlgLabelSubmitCancel from '../../utils/dlgLabelSubmitCancel' +import dlgLabelSubmitCancel from '../../utils/DlgLabelSubmitCancel' export default { components: { MonacoEditor, dlgLabelSubmitCancel }, diff --git a/packages/nc-gui/components/project/procedureTab/procedureAcl.vue b/packages/nc-gui/components/project/procedureTab/ProcedureAcl.vue similarity index 100% rename from packages/nc-gui/components/project/procedureTab/procedureAcl.vue rename to packages/nc-gui/components/project/procedureTab/ProcedureAcl.vue diff --git a/packages/nc-gui/components/project/procedureTab/procedureQuery.vue b/packages/nc-gui/components/project/procedureTab/ProcedureQuery.vue similarity index 99% rename from packages/nc-gui/components/project/procedureTab/procedureQuery.vue rename to packages/nc-gui/components/project/procedureTab/ProcedureQuery.vue index f8b3a819ab..967d12550c 100644 --- a/packages/nc-gui/components/project/procedureTab/procedureQuery.vue +++ b/packages/nc-gui/components/project/procedureTab/ProcedureQuery.vue @@ -71,7 +71,7 @@ import { mapGetters, mapActions } from 'vuex' import MonacoEditor from '../../monaco/Monaco' -import dlgLabelSubmitCancel from '../../utils/dlgLabelSubmitCancel' +import dlgLabelSubmitCancel from '../../utils/DlgLabelSubmitCancel' export default { components: { MonacoEditor, dlgLabelSubmitCancel }, diff --git a/packages/nc-gui/components/project/projectMetadata/disableOrEnableModels.vue b/packages/nc-gui/components/project/projectMetadata/DisableOrEnableModels.vue similarity index 88% rename from packages/nc-gui/components/project/projectMetadata/disableOrEnableModels.vue rename to packages/nc-gui/components/project/projectMetadata/DisableOrEnableModels.vue index 400e6e3159..1ffa987346 100644 --- a/packages/nc-gui/components/project/projectMetadata/disableOrEnableModels.vue +++ b/packages/nc-gui/components/project/projectMetadata/DisableOrEnableModels.vue @@ -1,15 +1,15 @@ diff --git a/packages/nc-gui/components/templates/categories.vue b/packages/nc-gui/components/templates/Categories.vue similarity index 100% rename from packages/nc-gui/components/templates/categories.vue rename to packages/nc-gui/components/templates/Categories.vue diff --git a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue b/packages/nc-gui/components/templates/CreateProjectFromTemplateBtn.vue similarity index 64% rename from packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue rename to packages/nc-gui/components/templates/CreateProjectFromTemplateBtn.vue index df70a0c05e..b5d31fa484 100644 --- a/packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue +++ b/packages/nc-gui/components/templates/CreateProjectFromTemplateBtn.vue @@ -20,7 +20,7 @@ export default { name: 'CreateProjectFromTemplateBtn', mixins: [colors], props: { - excelImport: Boolean, + quickImport: Boolean, loading: Boolean, importToProject: Boolean, templateData: [Array, Object], @@ -99,7 +99,8 @@ export default { this.$store.commit('loader/MutMessage', this.loaderMessages[this.loaderMessagesIndex]) }, 1000) - let project + const projectId = this.$store.state.project.project.id + let firstTable = null // Not available now if (this.importToProject) { @@ -122,45 +123,46 @@ export default { // projectId = this.$route.params.project_id // prefix = this.$store.getters['project/GtrProjectPrefix'] } else { - // Create an empty project - try { - this.$e("a:project:create:excel"); - - project = await this.$api.project.create({ - title: this.templateData.title, - external: false - }) - this.projectCreation = true - } catch (e) { - this.projectCreation = false - this.$toast - .error(await this._extractSdkResponseErrorMsg(e)) - .goAway(3000) - } finally { - clearInterval(interv) - } - - if (!this.projectCreation) { - // failed to create project - return - } - // Create tables try { for (const t of this.localTemplateData.tables) { // enrich system fields if not provided // e.g. id, created_at, updated_at const systemColumns = SqlUiFactory - .create({ client: 'sqlite3' }) + .create({ client: this.$store.state.project.project.bases[0].type }) .getNewTableColumns() - .filter(c => c.column_name != 'title') + .filter(c => c.column_name !== 'title') - const table = await this.$api.dbTable.create(project.id, { + for (const systemColumn of systemColumns) { + if (!t.columns.some(c => c.column_name.toLowerCase() === systemColumn.column_name.toLowerCase())) { + t.columns.push(systemColumn) + } + } + + // set pk & rqd if ID is provided + for (const column of t.columns) { + if (column.column_name.toLowerCase() === 'id' && !('pk' in column)) { + column.pk = true + column.rqd = true + break + } + } + + // create table + const table = await this.$api.dbTable.create(projectId, { table_name: t.table_name, title: '', - columns: [...t.columns, ...systemColumns] + columns: t.columns }) t.table_title = table.title + + // open the first table after import + if (firstTable === null) { + firstTable = table + } + + // set primary value + await this.$api.dbTableColumn.primaryColumnSet(table.columns[0].id) } this.tableCreation = true } catch (e) { @@ -181,30 +183,55 @@ export default { // Bulk import data if (this.importData) { this.$store.commit('loader/MutMessage', 'Importing excel data to project') - await this.importDataToProject(this.templateData.title) + await this.importDataToProject() } - this.$router.push({ - path: `/nc/${project.id}`, - query: { - new: 1 + // reload table list + this.$store.dispatch('project/_loadTables', { + dbKey: '0.projectJson.envs._noco.db.0', + key: '0.projectJson.envs._noco.db.0.tables', + _nodes: { + dbAlias: 'db', + env: '_noco', + type: 'tableDir' } + }).then(() => { + // add tab - choose the first one if multiple tables are created + this.$store.dispatch('tabs/loadFirstCreatedTableTab', { + title: firstTable.title + }).then((item) => { + // set active tab - choose the first one if multiple tables are created + this.$nextTick(() => { + this.$router.push({ + query: { + name: item.name || '', + dbalias: (item._nodes && item._nodes.dbAlias) || '', + type: (item._nodes && item._nodes.type) || 'table' + } + }) + }) + }) }) - this.$emit('success') + // confetti effect + this.simpleAnim() } catch (e) { console.log(e) - this.$toast.error(e.message).goAway(3000) + this.$toast + .error(await this._extractSdkResponseErrorMsg(e)) + .goAway(3000) } finally { clearInterval(interv) this.$store.commit('loader/MutMessage', null) this.projectCreation = false this.tableCreation = false + this.$emit('closeModal') } }, - async importDataToProject(projectName) { + async importDataToProject() { let total = 0 let progress = 0 + const projectName = this.$store.state.project.project.title await Promise.all(this.localTemplateData.tables.map(v => (async(tableMeta) => { const tableName = tableMeta.table_title const data = this.importData[tableMeta.ref_table_name] @@ -227,12 +254,46 @@ export default { remapColNames(batchData, columns) { return batchData.map(data => (columns || []).reduce((aggObj, col) => ({ ...aggObj, - [col.column_name]: data[col.ref_column_name] + [col.column_name]: data[col.ref_column_name || col.column_name] }), {}) ) + }, + simpleAnim() { + const count = 200 + const defaults = { + origin: { y: 0.7 } + } + + function fire(particleRatio, opts) { + window.confetti(Object.assign({}, defaults, opts, { + particleCount: Math.floor(count * particleRatio) + })) + } + + fire(0.25, { + spread: 26, + startVelocity: 55 + }) + fire(0.2, { + spread: 60 + }) + fire(0.35, { + spread: 100, + decay: 0.91, + scalar: 0.8 + }) + fire(0.1, { + spread: 120, + startVelocity: 25, + decay: 0.92, + scalar: 1.2 + }) + fire(0.1, { + spread: 120, + startVelocity: 45 + }) } } - } diff --git a/packages/nc-gui/components/templates/detailed.vue b/packages/nc-gui/components/templates/Detailed.vue similarity index 97% rename from packages/nc-gui/components/templates/detailed.vue rename to packages/nc-gui/components/templates/Detailed.vue index 6b4a75af4a..b5dffcd755 100644 --- a/packages/nc-gui/components/templates/detailed.vue +++ b/packages/nc-gui/components/templates/Detailed.vue @@ -91,8 +91,8 @@ - diff --git a/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts b/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts index c0ef59e8ed..d2765134af 100644 --- a/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts +++ b/packages/nocodb/src/lib/noco/upgrader/jobs/ncProjectUpgraderV2_0090000.ts @@ -436,6 +436,10 @@ async function migrateProjectModels( columnMeta.uidt = UITypes.ForeignKey; } + if (columnMeta.uidt === UITypes.Rating) { + columnMeta.uidt = UITypes.Number; + } + const column = await Column.insert( { ...columnMeta, @@ -777,8 +781,12 @@ async function migrateProjectModels( }); let orderCount = 1; for (const [_cn, column] of aliasColArr) { + const viewColumn = viewColumns.find( + c => column.id === c.fk_column_id + ); + if (!viewColumn) continue; await GridViewColumn.update( - viewColumns.find(c => column.id === c.fk_column_id).id, + viewColumn.id, { order: orderCount++, show: queryParams?.showFields @@ -947,7 +955,7 @@ async function migrateProjectModelViews( await FormViewColumn.update( viewColumn.id, { - help: columnParams?.help, + help: columnParams?.help?.slice(0, 254), label: columnParams?.label, required: columnParams?.required, description: columnParams?.description, @@ -957,8 +965,10 @@ async function migrateProjectModelViews( ncMeta ); } else if (viewData.show_as === 'grid') { + const viewColumn = viewColumns.find(c => column.id === c.fk_column_id); + if (!viewColumn) continue; await GridViewColumn.update( - viewColumns.find(c => column.id === c.fk_column_id).id, + viewColumn.id, { order, show, @@ -1025,9 +1035,9 @@ async function migrateViewsParams( { fk_column_id: sort.field ? ( - objModelColumnAliasRef[projectId][tn][sort.field] || - objModelColumnRef[projectId][tn][sort.field] - ).id + objModelColumnAliasRef[projectId]?.[tn]?.[sort.field] || + objModelColumnRef[projectId]?.[tn]?.[sort.field] + )?.id || null : null, fk_view_id: view.id, direction: sort.order === '-' ? 'desc' : 'asc' @@ -1041,7 +1051,10 @@ async function migrateViewsParams( await Filter.insert( { fk_column_id: filter.field - ? objModelColumnAliasRef[projectId][tn][filter.field].id + ? ( + objModelColumnAliasRef?.[projectId]?.[tn]?.[filter.field] || + objModelColumnRef?.[projectId]?.[tn]?.[filter.field] + )?.id || null : null, fk_view_id: view.id, comparison_op: filterV1toV2CompOpMap[filter.op], @@ -1071,7 +1084,7 @@ async function migrateUIAcl(ctx: MigrateCtxV1, ncMeta: any) { for (const acl of uiAclList) { // if missing model name skip the view acl migration - if (!acl.title) continue; + if (!acl?.title) continue; let fk_view_id; if (acl.type === 'vtable') { @@ -1231,7 +1244,10 @@ async function migrateWebhooks(ctx: MigrateCtxV1, ncMeta: any) { }> = await ncMeta.metaList(null, null, 'nc_hooks'); for (const hookMeta of hooks) { - if (!hookMeta.project_id) { + if ( + !hookMeta.project_id || + !ctx.objModelRef[hookMeta?.project_id]?.[hookMeta?.tn] + ) { continue; } const hook = await Hook.insert( diff --git a/packages/nocodb/src/lib/utils/globals.ts b/packages/nocodb/src/lib/utils/globals.ts index eada1fb767..e34c0ab296 100644 --- a/packages/nocodb/src/lib/utils/globals.ts +++ b/packages/nocodb/src/lib/utils/globals.ts @@ -34,7 +34,9 @@ export enum MetaTable { PLUGIN = 'nc_plugins_v2', PROJECT_USERS = 'nc_project_users_v2', MODEL_ROLE_VISIBILITY = 'nc_disabled_models_for_role_v2', - API_TOKENS = 'nc_api_tokens' + API_TOKENS = 'nc_api_tokens', + SYNC_SOURCE = 'nc_sync_source_v2', + SYNC_LOGS = 'nc_sync_logs_v2' } export enum CacheScope { diff --git a/scripts/cypress/integration/spec/roleValidation.spec.js b/scripts/cypress/integration/spec/roleValidation.spec.js index de509a4b07..2d86df82d9 100644 --- a/scripts/cypress/integration/spec/roleValidation.spec.js +++ b/scripts/cypress/integration/spec/roleValidation.spec.js @@ -78,7 +78,7 @@ export function _editSchema(roleType, previewMode) { cy.get(".add-btn").should(validationString); cy.get(".v-tabs-bar") .eq(0) - .find("button.mdi-plus-box") + .find(".add-btn.mdi-plus-box") .should(validationString); // delete table option diff --git a/scripts/cypress/support/commands.js b/scripts/cypress/support/commands.js index 176cab8b18..61171b8c20 100644 --- a/scripts/cypress/support/commands.js +++ b/scripts/cypress/support/commands.js @@ -258,6 +258,7 @@ Cypress.Commands.add("getActiveContentModal", () => { Cypress.Commands.add("createTable", (name) => { cy.get(".add-btn").click(); + cy.getActiveMenu().contains("Add new table").should('exist').click() cy.get('.nc-create-table-card .nc-table-name input[type="text"]') .first() .click() diff --git a/scripts/markdown/readme/languages/chinese.md b/scripts/markdown/readme/languages/chinese.md index cd672cff6e..bc032c6c6a 100644 --- a/scripts/markdown/readme/languages/chinese.md +++ b/scripts/markdown/readme/languages/chinese.md @@ -16,93 +16,150 @@

- Website • + 官网DiscordTwitterReddit • - Documentation -

+ 文档 +

![OpenSourceAirtableAlternative](https://user-images.githubusercontent.com/5435402/133762127-e94da292-a1c3-4458-b09a-02cd5b57be53.png)

- NocoDB - The Open Source Airtable alternative | Product Hunt + NocoDB - 开源的 Airtable 替代品 | Product Hunt

# 快速尝试 -### 一键式部署 + +### 一键部署 + +在部署之前,请确保你有一个 Heroku 账户。默认情况下,将使用一个附加的 Heroku Postgres 作为数据库。你可以通过访问 Heroku 应用程序设置并选择 Config Vars 来查看 DATABASE_URL 中定义的连接方式。 #### Heroku + Deploy NocoDB to Heroku with 1-Click
-### 使用Docker -```bash -docker run -d --name nocodb -p 8080:8080 nocodb/nocodb:latest -``` - -- NocoDB needs a database as input : See [Production Setup](https://github.com/nocodb/nocodb/blob/master/README.md#production-setup). -- 要使用数据持久化,你可以挂载到 `/usr/app/data/`。 +## NPX - 示例: +如果你需要一个交互式的配置,你可以运行下面的命令。 - ``` - docker run -d -p 8080:8080 --name nocodb -v "$(pwd)"/nocodb:/usr/app/data/ nocodb/nocodb:latest - ``` - -### 使用NPM ``` npx create-nocodb-app ``` -### 使用git -``` + + + +## Node 应用程序 + +我们提供了一个简单的 NodeJS 应用程序以供入门。 + +```bash git clone https://github.com/nocodb/nocodb-seed cd nocodb-seed npm install npm start ``` -### GUI +## Docker 部署 + +```bash +# 如果使用 SQLite 的话 +docker run -d --name nocodb \ +-v "$(pwd)"/nocodb:/usr/app/data/ \ +-p 8080:8080 \ +nocodb/nocodb:latest + +# 如果使用 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 + +# 如果使用 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 + +# 如果使用 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 +``` + +> 你可以通过在 0.10.6 以上的版本中挂载 `/usr/app/data/` 来持久化数据,否则你的数据会在重新创建容器后完全丢失。 -使用仪表板使用 : [http://localhost:8080/dashboard](http://localhost:8080/dashboard) +> 如果你打算输入一些特殊字符,你可能需要在创建数据库时改变字符集和排序。请查看[MySQL Docker](https://github.com/nocodb/nocodb/issues/1340#issuecomment-1049481043)的例子。 +## Docker Compose + +我们在[这个目录](https://github.com/nocodb/nocodb/tree/master/docker-compose)下提供了不同的 docker-compose.yml 文件。下面是一些例子: + +```bash +git clone https://github.com/nocodb/nocodb +# 如果使用 MySQL 的话 +cd nocodb/docker-compose/mysql +# 如果使用 PostgreSQL 的话 +cd nocodb/docker-compose/pg +# 如果使用 MSSQL 的话 +cd nocodb/docker-compose/mssql +docker-compose up -d +``` + +> 你可以通过在 0.10.6 以上的版本中挂载 `/usr/app/data/` 来持久化数据,否则你的数据会在重新创建容器后完全丢失。 + +> 如果你打算输入一些特殊字符,你可能需要在创建数据库时改变字符集和排序。请查看[MySQL Docker Compose](https://github.com/nocodb/nocodb/issues/1313#issuecomment-1046625974)的例子。 + +# GUI + +点击 [http://localhost:8080/dashboard](http://localhost:8080/dashboard) 打开仪表盘。 # 加入我们的社区 - + + -
-
+ + + # 截图 ![1](https://user-images.githubusercontent.com/86527202/136066713-5408634f-5469-40eb-94c9-7eafae5e179c.png)
-![2](https://user-images.githubusercontent.com/86527202/136066729-9b6a261a-231d-4d7f-9fc2-061c301d6192.png) +![2](https://user-images.githubusercontent.com/86527202/168545293-b48a4237-8646-4f9a-a56b-56d5c55adc79.jpg)
![5](https://user-images.githubusercontent.com/86527202/136066734-3f25aecc-bb7e-4db7-81c4-00b368d799d3.png)
-![6](https://user-images.githubusercontent.com/86527202/136066735-2d4cb656-02dc-4233-ac4a-1ba9bd8acdf0.png) +![6](https://user-images.githubusercontent.com/86527202/168545527-3948125d-1640-4c07-ac80-15db9a85f66f.jpg)
-![7](https://user-images.githubusercontent.com/86527202/136066737-eb6a56fb-5e2e-4423-912b-ced32e8b479c.png) +![7](https://user-images.githubusercontent.com/86527202/168545772-dfbffe13-bcf6-4a49-8a10-1bc8a933d77e.jpg)
-![8](https://user-images.githubusercontent.com/86527202/136066742-94c7eff7-d88e-4002-ad72-ffd23090847c.png) +![8](https://user-images.githubusercontent.com/86527202/168545839-0ba950a4-400f-45b2-b2db-b22b1853af4a.jpg)
-![9](https://user-images.githubusercontent.com/86527202/136066743-1b4030c5-042f-4338-99b0-06237878ce53.png) +![9](https://user-images.githubusercontent.com/86527202/168545872-c2a4b63a-9dc9-4c12-add7-69d5f4d0a6e1.jpg)
![9a](https://user-images.githubusercontent.com/86527202/136066745-9797775d-7db0-4681-ab10-d7ecbbd972ef.png) @@ -117,83 +174,112 @@ npm start ![11](https://user-images.githubusercontent.com/86527202/136066756-fc203c2c-570e-4514-b9f4-2a41ac24e5dd.png)
-# 特征 +# 功能 + ### 丰富的电子表格功能 -- ⚡ 搜索,排序,过滤,隐藏列 -- ⚡ 创建视图:网格,画廊,看板,甘特图,表单 -- ⚡ 分享视图:公开 或 密码保护 -- ⚡ 个人和锁定视图 -- ⚡ 将图像上传到单元格(使用S3,Minio,GCP,Azure,DigitalOcean,Linode,OVH,Backblaze) -- ⚡ 角色:所有者,创建者,编辑器,查看器,评论者,自定义角色。 -- ⚡ 访问控制:在 `数据库`、`表`、`列` 级别的访问控制。 - -### 工作流自动化应用商店: -- ⚡ 聊天:微软Teams,Slack,Discord,Mattermost -- ⚡ 电子邮件:SMTP,SES,MailChimp -- ⚡ 短信:Twilio -- ⚡ whatsapp -- ⚡ 任何第三方API - -### Programmatic API访问通过: -- ⚡ REST API (Swagger) -- ⚡ GraphQL API -- ⚡ JWT身份验证和社交验证 -- ⚡ 与Zapier,Integromat集成的API - -# 生产安装 -NoCodb 要求一个数据库用来存储电子表格视图和外部元数据。可以在`NC_DB`环境变量中指定此数据库的连接参数。 - -## Docker - -#### MySQL 示例 -```bash -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 -``` +- ⚡ 基本操作:对表、列和行进行增删改查 +- ⚡ 字段操作:排序、过滤、隐藏/取消隐藏列 +- ⚡ 多种视图:网格(默认)、画廊和表单视图 +- ⚡ 视图权限:协作视图和锁定的视图 +- ⚡ 分享基础/视图:公开或私人(有密码保护) +- ⚡ 多种单元格类型:ID、链接到另一记录、查找、滚动、单行文本、附件、货币、公式等 +- ⚡ 基于角色的访问控制:不同层次的精细化地控制访问 +- ⚡ 以及更多...... -#### Postgres 示例 -```bash -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 +### 工作流程自动化的应用商店 + +我们在三个主要类别中提供不同的集成。详见App Store。 + +- ⚡ 聊天:Slack、Discord、Mattermost 等 +- ⚡ 电子邮件: AWS SES,SMTP,MailerSend 等 +- ⚡ 存储:AWS S3,Google Cloud Storage,Minio 等 + +### 使用 API 编程访问 + +我们提供以下方式让用户以编程方式调用操作。 您可以使用 Token(JWT 或 Social Auth)来签署您对 NocoDB 授权的请求。 + +- ⚡  REST APIs +- ⚡  NocoDB SDK + +### 架构同步 + +如果您在 NocoDB GUI 之外进行了更改,我们允许您同步架构更改。 但是,必须注意的是,您必须附有自己的迁移架构才能从一个环境迁移到其他环境。 有关详细信息,请参阅 同步架构。 + +### 审计 + +我们将所有用户操作日志保存在一起。 有关详细信息,请参阅 审计。 + +# 生产部署 + +默认情况下使用 SQLite 存储元数据。你也可以指定你自己的数据库。这个数据库的连接参数可以在 `NC_DB` 环境变量中指定。此外,我们还提供以下环境变量进行配置: + +## 环境变量 + +参见[环境变量](https://docs.nocodb.com/getting-started/installation#environment-variables) + +# 开发 + +## 拉取项目 + +```shell +git clone https://github.com/nocodb/nocodb +cd nocodb ``` -#### SQL Server 示例 -```bash -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 +## 本地运行后端 + +```shell +cd packages/nocodb +npm install +npm run watch:run +# 在浏览器中打开 localhost:8080/dashboard ``` -## Docker Compose -```bash -git clone https://github.com/nocodb/nocodb -cd docker-compose -cd mysql or pg or mssql -docker-compose up -d +## 本地运行前端 + +```shell +cd packages/nc-gui +npm install +npm run dev +# 在浏览器中打开 localhost:3000/dashboard ``` -## 环境变量 +修改代码后会自动重新启动。 + +> nocodb/packages/nocodb 包括 nc-lib-gui,它是 npm 源中托管的 nc-gui 的预构建版本。如果您只想修改后端,则可以在本地启动后端后在浏览器中访问 localhost:8000/dashboard -Please refer to [Environment variables](https://docs.nocodb.com/getting-started/installation#environment-variables) +## 本地运行 Cypress 测试 + +```shell +# 安装依赖 (cypress) +npm install -# 开发安装 +# 使用 docker compose 运行带有所需数据库的 mysql 数据库 +docker-compose -f ./scripts/docker-compose-cypress.yml up -Please refer to [Development Setup](https://github.com/nocodb/nocodb/tree/master#development-setup) +# 使用以下命令运行后端 api +npm run start:api + +# 使用以下命令运行前端 Web UI +npm run start:web + +# 等待 3000 和 8080 端口都可用后 +# 使用以下命令运行 cypress 测试 +npm run cypress:run + +# 或运行以下命令以使用 GUI 运行它 +npm run cypress:open +``` # 贡献 -Please refer to [Contribution Guide](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md). +参见[贡献指南](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md). -# 为什么我们建立这个? +# 我们为什么要做这个? -大多数互联网业务都配备了电子表格或数据库以解决其业务需求,每天有上亿人使用电子表格。我们基于数据库运行更强大的工具能更高效地完成工作。用SaaS产品解决此问题的尝试已经意味着可怕的访问控制,供应商锁定,数据锁定,突然的价格变化,甚至是将来可能会阻碍发展。 +大多数互联网企业都为自己配备了电子表格或数据库来解决他们的业务需求。每天有十亿多人协作使用电子表格。然而,我们在数据库上以类似的效率工作还有很长的路要走,这些数据库在计算方面是更强大的工具。使用 SaaS 产品解决这个问题意味着可怕的访问控制、供应商锁定、数据锁定、突然的价格变化以及最重要的是未来可能出现的玻璃天花板。 # 我们的任务 -我们的使命是为数据库提供最强大的无码界面,为世界上每一个互联网业务的开源使用。这不仅将民主化带给强大的计算工具,还将为数亿人增强他们的创造力。 +我们的使命是为数据库提供最强大的无代码接口,并向世界上每一个互联网企业开放源代码。这不仅能让这个强大的计算工具开放给每个人,而且10亿或更多的人将会在互联网上拥有激进的修补和建设能力。