Browse Source

fix: bug fix and refactoring

re #755 , #747 , #732 , #767 , #739

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/797/head
Pranav C 3 years ago
parent
commit
7a0f65ab52
  1. 2
      packages/nc-cli/package.json
  2. 6
      packages/nc-cli/src/lib/mgr/NewMgr.ts
  3. 4
      packages/nc-cli/src/lib/util/Util.ts
  4. 7
      packages/nc-cli/webpack.config.js
  5. 5
      packages/nc-gui/components/ProjectTreeView.vue
  6. 39
      packages/nc-gui/components/auth/userManagement.vue
  7. 8
      packages/nc-gui/components/import/excelImport.vue
  8. 5
      packages/nc-gui/components/project/spreadsheet/components/editColumn.vue
  9. 11
      packages/nc-gui/components/project/spreadsheet/components/editVirtualColumn.vue
  10. 44
      packages/nc-gui/components/project/spreadsheet/components/extras.vue
  11. 40
      packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue
  12. 3
      packages/nc-gui/components/project/table.vue
  13. 8
      packages/nc-gui/components/projectTabs.vue
  14. 9
      packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue
  15. 22
      packages/nc-gui/components/templates/editor.vue
  16. 13
      packages/nc-gui/components/utils/dlgTableCreate.vue
  17. 5
      packages/nc-gui/components/utils/dlgTextSubmitCancel.vue
  18. 34
      packages/nc-gui/helpers/index.js
  19. 18
      packages/nc-gui/helpers/rightClickOptions.js
  20. 13
      packages/nc-gui/layouts/default.vue
  21. 4
      packages/nc-gui/pages/projects/index.vue
  22. 20
      packages/nc-gui/pages/user/authentication/signup/_token.vue
  23. 29
      packages/nc-gui/pages/user/authentication/signup/index.vue
  24. 1
      packages/nc-gui/store/project.js
  25. 21
      packages/nc-gui/store/tabs.js
  26. 4
      packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts
  27. 1
      packages/nocodb/src/lib/noco/NcProjectBuilder.ts
  28. 89
      packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts
  29. 48
      packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts
  30. 26
      packages/nocodb/src/lib/noco/rest/RestAuthCtrl.ts
  31. 2
      packages/nocodb/src/lib/noco/rest/RestAuthCtrlEE.ts
  32. 2
      packages/nocodb/src/lib/sqlMgr/SqlMgr.ts

2
packages/nc-cli/package.json

@ -1,6 +1,6 @@
{ {
"name": "create-nocodb-app", "name": "create-nocodb-app",
"version": "0.1.21", "version": "0.1.22",
"description": "nc-cli", "description": "nc-cli",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"module": "dist/bundle.js", "module": "dist/bundle.js",

6
packages/nc-cli/src/lib/mgr/NewMgr.ts

@ -231,7 +231,7 @@ class NewMgr {
args.folder = path.join(args.folder, args._[1]); args.folder = path.join(args.folder, args._[1]);
mkdirp.sync(args.folder); mkdirp.sync(args.folder);
process.chdir(args.folder); process.chdir(args.folder);
await Util.runCmd(`cd ${args.folder}`); await Util.runCmd(`cd ${Util.escapeShellArg(args.folder)}`);
await promisify(download)('direct:https://github.com/nocodb/nocodb-seed/archive/refs/heads/main.zip', args.folder); await promisify(download)('direct:https://github.com/nocodb/nocodb-seed/archive/refs/heads/main.zip', args.folder);
if (answers.type !== 'sqlite3') { if (answers.type !== 'sqlite3') {
@ -239,7 +239,7 @@ class NewMgr {
} }
if (os.type() === 'Windows_NT') { if (os.type() === 'Windows_NT') {
console.log(boxen(`# Project created successfully\n\n# Please run the following commands\n\n${('cd ' + args.folder + '\nnpm install \nnpm start\n').green.bold}`, { console.log(boxen(`# Project created successfully\n\n# Please run the following commands\n\n${('cd ' + Util.escapeShellArg(args.folder) + '\nnpm install \nnpm start\n').green.bold}`, {
borderColor: 'green', borderColor: 'green',
borderStyle: 'round', borderStyle: 'round',
margin: 1, margin: 1,
@ -490,7 +490,7 @@ class NewMgr {
args.folder = path.join(args.folder, args._[1]); args.folder = path.join(args.folder, args._[1]);
mkdirp.sync(args.folder); mkdirp.sync(args.folder);
process.chdir(args.folder); process.chdir(args.folder);
await Util.runCmd(`cd ${args.folder}`); await Util.runCmd(`cd ${Util.escapeShellArg(args.folder)}`);
await promisify(download)('gitlab:xc-public/test10', args.folder); await promisify(download)('gitlab:xc-public/test10', args.folder);
const config = { const config = {

4
packages/nc-cli/src/lib/util/Util.ts

@ -889,6 +889,10 @@ ${'VARIATIONS :'.bold}
} }
} }
public static escapeShellArg(cmd) {
return '"' + cmd.replace(/(["'$`\\])/g, '\\$1') + '"';
};
public async play(sound) { public async play(sound) {
switch (sound) { switch (sound) {

7
packages/nc-cli/webpack.config.js

@ -3,7 +3,7 @@ const nodeExternals = require('webpack-node-externals');
// //
// const TerserPlugin = require('terser-webpack-plugin'); // const TerserPlugin = require('terser-webpack-plugin');
const webpack = require('webpack'); const webpack = require('webpack');
const JavaScriptObfuscator = require('webpack-obfuscator'); // const JavaScriptObfuscator = require('webpack-obfuscator');
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
entry: './src/index.ts', entry: './src/index.ts',
@ -43,11 +43,6 @@ module.exports = {
// }, // },
plugins: [ plugins: [
new JavaScriptObfuscator({
rotateStringArray: true,
splitStrings: true,
splitStringsChunkLength: 6
}, []),
new webpack.BannerPlugin({banner: "#! /usr/bin/env node", raw: true}), new webpack.BannerPlugin({banner: "#! /usr/bin/env node", raw: true}),
// new CopyPlugin({ // new CopyPlugin({

5
packages/nc-gui/components/ProjectTreeView.vue

@ -599,6 +599,7 @@
<textDlgSubmitCancel <textDlgSubmitCancel
v-if="dialogRenameTable.dialogShow" v-if="dialogRenameTable.dialogShow"
:rules="[validateTableName]"
:dialog-show="dialogRenameTable.dialogShow" :dialog-show="dialogRenameTable.dialogShow"
:heading="dialogRenameTable.heading" :heading="dialogRenameTable.heading"
:cookie="dialogRenameTable.cookie" :cookie="dialogRenameTable.cookie"
@ -659,6 +660,7 @@ import {copyTextToClipboard} from '../helpers/xutils';
import DlgTableCreate from '@/components/utils/dlgTableCreate'; import DlgTableCreate from '@/components/utils/dlgTableCreate';
import DlgViewCreate from '@/components/utils/dlgViewCreate'; import DlgViewCreate from '@/components/utils/dlgViewCreate';
import SponsorMini from '@/components/sponsorMini'; import SponsorMini from '@/components/sponsorMini';
import {validateTableName} from "~/helpers";
// const {clipboard} = require('electron'); // const {clipboard} = require('electron');
@ -671,6 +673,7 @@ export default {
dlgLabelSubmitCancel, dlgLabelSubmitCancel,
}, },
data: () => ({ data: () => ({
validateTableName,
roleIcon: { roleIcon: {
owner: 'mdi-account-star', owner: 'mdi-account-star',
creator: 'mdi-account-hard-hat', creator: 'mdi-account-hard-hat',
@ -1299,7 +1302,7 @@ export default {
env: this.menuItem._nodes.env, env: this.menuItem._nodes.env,
dbAlias: this.menuItem._nodes.dbAlias, dbAlias: this.menuItem._nodes.dbAlias,
tn: this.menuItem._nodes.tn, tn: this.menuItem._nodes.tn,
_tn:_tn, _tn: _tn,
dbConnection: this.menuItem._nodes.dbConnection, dbConnection: this.menuItem._nodes.dbConnection,
type: 'table', type: 'table',

39
packages/nc-gui/components/auth/userManagement.vue

@ -120,12 +120,11 @@
<!-- {{ item.roles }}--> <!-- {{ item.roles }}-->
<v-chip <v-chip
v-for="role in (item.roles ? item.roles.split(',') : [])" v-if="item.roles"
:key="role"
class="mr-1" class="mr-1"
:color="rolesColors[role]" :color="rolesColors[getRole(item.roles)]"
> >
{{ role }} {{ getRole(item.roles) }}
</v-chip> </v-chip>
<!-- <v-edit-dialog--> <!-- <v-edit-dialog-->
@ -411,7 +410,6 @@
hide-details="auto" hide-details="auto"
:items="roles" :items="roles"
label="Select User roles" label="Select User roles"
multiple
dense dense
deletable-chips deletable-chips
@change="edited = true" @change="edited = true"
@ -421,6 +419,14 @@
{{ item }} {{ item }}
</v-chip> </v-chip>
</template> </template>
<template #item="{item}">
<div>
<div>{{ item }}</div>
<div class="mb-2 caption grey--text">
{{ roleDescriptions[item] }}
</div>
</div>
</template>
</v-combobox> </v-combobox>
</v-col> </v-col>
</v-row> </v-row>
@ -490,10 +496,13 @@ export default {
return !invalidEmails.length || `"${invalidEmails.join(', ')}" - invalid email` return !invalidEmails.length || `"${invalidEmails.join(', ')}" - invalid email`
} }
], ],
userList: [] userList: [],
roleDescriptions: {}
}), }),
computed: { computed: {
roleNames() {
return this.roles.map(r => r.title)
},
inviteUrl() { inviteUrl() {
return this.invite_token ? `${location.origin}${location.pathname}#/user/authentication/signup/${this.invite_token.invite_token}` : null return this.invite_token ? `${location.origin}${location.pathname}#/user/authentication/signup/${this.invite_token.invite_token}` : null
}, },
@ -506,11 +515,11 @@ export default {
}, },
selectedRoles: { selectedRoles: {
get() { get() {
return this.selectedUser && this.selectedUser.roles ? this.selectedUser.roles.split(',') : [] return (this.selectedUser && this.selectedUser.roles ? this.selectedUser.roles.split(',') : []).sort((a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a))[0]
}, },
set(roles) { set(roles) {
if (this.selectedUser) { if (this.selectedUser) {
this.selectedUser.roles = roles.filter(Boolean).join(',') this.selectedUser.roles = roles // .filter(Boolean).join(',')
} }
} }
}, },
@ -524,9 +533,6 @@ export default {
} }
}, },
watch: { watch: {
userEditDialog(v) {
if (!v) { this.validate = false }
},
options: { options: {
async handler() { async handler() {
await this.loadUsers() await this.loadUsers()
@ -534,6 +540,7 @@ export default {
deep: true deep: true
}, },
userEditDialog(v) { userEditDialog(v) {
// if (!v) { this.validate = false }
if (v && (this.selectedUser && !this.selectedUser.id)) { if (v && (this.selectedUser && !this.selectedUser.id)) {
this.$nextTick(() => { this.$nextTick(() => {
setTimeout(() => { setTimeout(() => {
@ -552,6 +559,9 @@ export default {
this.$eventBus.$off('show-add-user', this.addUser) this.$eventBus.$off('show-add-user', this.addUser)
}, },
methods: { methods: {
getRole(roles) {
return (roles ? roles.split(',') : []).sort((a, b) => this.roleNames.indexOf(a) - this.roleNames.indexOf(a))[0]
},
simpleAnim() { simpleAnim() {
const count = 30 const count = 30
const defaults = { const defaults = {
@ -651,7 +661,10 @@ export default {
params: { params: {
project_id: this.$route.params.project_id project_id: this.$route.params.project_id
} }
})).data.map(role => role.title).filter(role => role !== 'guest') })).data.map((role) => {
this.roleDescriptions[role.title] = role.description
return role.title
}).filter(role => role !== 'guest')
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }

8
packages/nc-gui/components/import/excelImport.vue

@ -128,15 +128,17 @@
<v-card class="pa-6"> <v-card class="pa-6">
<template-editor :project-template.sync="templateData" excel-import> <template-editor :project-template.sync="templateData" excel-import>
<template #toolbar="{valid}"> <template #toolbar="{valid}">
<!-- <h3 class="mt-2 grey&#45;&#45;text">--> <h3 class="mt-2 grey--text">
<!-- Import Excel as Project --> Importing : {{ filename }}
<!-- </h3>--> </h3>
<!-- <span class="grey&#45;&#45;text">Importing 2 sheets</span>--> <!-- <span class="grey&#45;&#45;text">Importing 2 sheets</span>-->
<v-spacer />
<v-spacer /> <v-spacer />
<create-project-from-template-btn <create-project-from-template-btn
:template-data="templateData" :template-data="templateData"
:import-data="importData" :import-data="importData"
excel-import
:valid="valid" :valid="valid"
create-gql-text="Import as GQL Project" create-gql-text="Import as GQL Project"
create-rest-text="Import as REST Project" create-rest-text="Import as REST Project"

5
packages/nc-gui/components/project/spreadsheet/components/editColumn.vue

@ -26,7 +26,8 @@
color="primary" color="primary"
:rules="[ :rules="[
v => !!v || 'Required', v => !!v || 'Required',
v => !meta || !meta.columns || meta.columns.every(c => column && c.cn === column.cn || v !== c.cn ) && meta.v.every(c => v !== c._cn ) || 'Duplicate column name' v => !meta || !meta.columns || meta.columns.every(c => column && c.cn === column.cn || v !== c.cn ) && meta.v.every(c => v !== c._cn ) || 'Duplicate column name',
validateColumnName
]" ]"
class="caption nc-column-name-input" class="caption nc-column-name-input"
label="Column name" label="Column name"
@ -420,6 +421,7 @@ import RelationOptions from '@/components/project/spreadsheet/components/editCol
import DlgLabelSubmitCancel from '@/components/utils/dlgLabelSubmitCancel' import DlgLabelSubmitCancel from '@/components/utils/dlgLabelSubmitCancel'
import LinkedToAnotherOptions from '@/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions' import LinkedToAnotherOptions from '@/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions'
import { SqliteUi, MssqlUi } from '@/helpers/sqlUi' import { SqliteUi, MssqlUi } from '@/helpers/sqlUi'
import { validateColumnName } from '~/helpers'
export default { export default {
name: 'EditColumn', name: 'EditColumn',
@ -442,6 +444,7 @@ export default {
value: Boolean value: Boolean
}, },
data: () => ({ data: () => ({
validateColumnName,
valid: false, valid: false,
relationDeleteDlg: false, relationDeleteDlg: false,
newColumn: {}, newColumn: {},

11
packages/nc-gui/components/project/spreadsheet/components/editVirtualColumn.vue

@ -28,7 +28,8 @@
label="Column name" label="Column name"
:rules="[ :rules="[
v => !!v || 'Required', v => !!v || 'Required',
v => !meta || !meta.columns || !column ||meta.columns.every(c => v !== c.cn ) && meta.v.every(c => column && c._cn === column._cn || v !== c._cn ) || 'Duplicate column name' v => !meta || !meta.columns || !column ||meta.columns.every(c => v !== c.cn ) && meta.v.every(c => column && c._cn === column._cn || v !== c._cn ) || 'Duplicate column name',
validateColumnName
]" ]"
dense dense
outlined outlined
@ -55,6 +56,8 @@
<script> <script>
import FormulaOptions from '@/components/project/spreadsheet/components/editColumn/formulaOptions' import FormulaOptions from '@/components/project/spreadsheet/components/editColumn/formulaOptions'
import { validateColumnName } from '~/helpers'
import { UITypes } from '~/components/project/spreadsheet/helpers/uiTypes'
export default { export default {
name: 'EditVirtualColumn', name: 'EditVirtualColumn',
@ -116,8 +119,12 @@ export default {
this.$refs.column.$el.querySelector('input').focus() this.$refs.column.$el.querySelector('input').focus()
} }
}, 100) }, 100)
} },
validateColumnName(v) {
if (this.column.hm || this.column.mm || this.column.bt || this.column.lk) { return true }
return validateColumnName(v)
}
} }
} }
</script> </script>

44
packages/nc-gui/components/project/spreadsheet/components/extras.vue

@ -37,22 +37,6 @@
:class="{ active: showCommunity }" :class="{ active: showCommunity }"
dense dense
> >
<v-list-item
dense
target="_blank"
href="https://calendly.com/nocodb"
>
<!-- Book a Free DEMO -->
<v-list-item-title>
<v-icon class="mr-1" small :color="textColors[3]">
mdi-calendar-month
</v-icon>
<span class="caption" :title="$t('projects.show_community_book_a_free_demo')">{{
$t('projects.show_community_book_a_free_demo')
}}</span>
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item dense href="https://discord.gg/5RgZmkW" target="_blank"> <v-list-item dense href="https://discord.gg/5RgZmkW" target="_blank">
<!-- Get your questions answered --> <!-- Get your questions answered -->
<v-list-item-title> <v-list-item-title>
@ -65,6 +49,18 @@
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-divider /> <v-divider />
<v-list-item dense href="https://twitter.com/NocoDB" target="_blank">
<!-- Follow NocoDB -->
<v-list-item-title>
<v-icon class="mr-1" small :color="textColors[1]">
mdi-twitter
</v-icon>
<span class="caption" title="$t('projects.show_community_follow_nocodb')"> {{
$t('projects.show_community_follow_nocodb')
}}</span>
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item dense href="https://www.reddit.com/r/NocoDB/" target="_blank"> <v-list-item dense href="https://www.reddit.com/r/NocoDB/" target="_blank">
<!-- Get your questions answered --> <!-- Get your questions answered -->
<v-list-item-title> <v-list-item-title>
@ -77,14 +73,18 @@
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-divider /> <v-divider />
<v-list-item dense href="https://twitter.com/NocoDB" target="_blank"> <v-list-item
<!-- Follow NocoDB --> dense
target="_blank"
href="https://calendly.com/nocodb"
>
<!-- Book a Free DEMO -->
<v-list-item-title> <v-list-item-title>
<v-icon class="mr-1" small :color="textColors[1]"> <v-icon class="mr-1" small :color="textColors[3]">
mdi-twitter mdi-calendar-month
</v-icon> </v-icon>
<span class="caption" title="$t('projects.show_community_follow_nocodb')"> {{ <span class="caption" :title="$t('projects.show_community_book_a_free_demo')">{{
$t('projects.show_community_follow_nocodb') $t('projects.show_community_book_a_free_demo')
}}</span> }}</span>
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>

40
packages/nc-gui/components/project/spreadsheet/rowsXcDataTable.vue

@ -583,6 +583,7 @@ export default {
}, },
mixins: [spreadsheet], mixins: [spreadsheet],
props: { props: {
tabId: String,
env: String, env: String,
nodes: Object, nodes: Object,
addNewRelationTab: Function, addNewRelationTab: Function,
@ -640,7 +641,7 @@ export default {
}, },
page: 1, page: 1,
count: 0, count: 0,
size: 25, // size: 25,
xWhere: '', xWhere: '',
sort: '', sort: '',
@ -668,8 +669,38 @@ export default {
}], }],
rowContextMenu: null rowContextMenu: null
}), }),
watch: {
page(p) {
this.$store.commit('tabs/MutSetTabState', {
id: this.uniqueId,
key: 'page',
val: p
})
},
selectedViewId(id) {
if (this.tabsState[this.tabId] && this.tabsState[this.tabId].page) {
this.page = this.tabsState[this.tabId].page || 1
} else {
this.page = 1
}
// this.$store.commit('tabs/MutSetTabState', {
// id: this.tabId,
// key: 'selectedViewId',
// val: id
// })
}
},
async mounted() { async mounted() {
try { try {
if (this.tabsState && this.tabsState[this.uniqueId]) {
if (this.tabsState[this.uniqueId].page) {
this.page = this.tabsState[this.uniqueId].page
}
// if (this.tabsState[this.tabId].selectedViewId) {
// this.selectedViewId = this.tabsState[this.tabId].selectedViewId
// }
}
await this.createTableIfNewTable() await this.createTableIfNewTable()
this.loadingMeta = true this.loadingMeta = true
await this.loadMeta(false) await this.loadMeta(false)
@ -1127,6 +1158,13 @@ export default {
} }
}, },
computed: { computed: {
tabsState() { return this.$store.state.tabs.tabsState || {} },
uniqueId() {
return `${this.tabId}_${this.selectedViewId}`
},
size() {
return (this.$store.state.project.projectInfo && this.$store.state.project.projectInfo.defaultLimit) || 25
},
isPkAvail() { isPkAvail() {
return this.meta && this.meta.columns.some(c => c.pk) return this.meta && this.meta.columns.some(c => c.pk)
}, },

3
packages/nc-gui/components/project/table.vue

@ -317,6 +317,7 @@
> >
<rows-xc-data-table <rows-xc-data-table
ref="tabs7" ref="tabs7"
:tab-id="tabId"
:show-tabs="relationTabs && relationTabs.length" :show-tabs="relationTabs && relationTabs.length"
:table="nodes.tn" :table="nodes.tn"
:nodes="nodes" :nodes="nodes"
@ -638,7 +639,7 @@ export default {
head() { head() {
return {} return {}
}, },
props: ['nodes', 'hideLogWindows'] props: ['nodes', 'hideLogWindows', 'tabId']
} }
</script> </script>

8
packages/nc-gui/components/projectTabs.vue

@ -60,7 +60,13 @@
style="height:100%" style="height:100%"
> >
<!-- <sqlLogAndOutput :hide="hideLogWindows">--> <!-- <sqlLogAndOutput :hide="hideLogWindows">-->
<TableView :ref="'tabs'+index" :hide-log-windows.sync="hideLogWindows" :nodes="tab._nodes" /> <TableView
v-if="activeTab === `${(tab._nodes && tab._nodes).type || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${tab.name}`"
:ref="'tabs'+index"
:tab-id="`${pid}||${(tab._nodes && tab._nodes).type || ''}||${(tab._nodes && tab._nodes.dbAlias) || ''}||${tab.name}`"
:hide-log-windows.sync="hideLogWindows"
:nodes="tab._nodes"
/>
<!-- </sqlLogAndOutput>--> <!-- </sqlLogAndOutput>-->
</div> </div>
<div v-else-if="tab._nodes.type === 'view'" style="height:100%"> <div v-else-if="tab._nodes.type === 'view'" style="height:100%">

9
packages/nc-gui/components/templates/createProjectFromTemplateBtn.vue

@ -42,6 +42,7 @@ export default {
name: 'CreateProjectFromTemplateBtn', name: 'CreateProjectFromTemplateBtn',
mixins: [colors], mixins: [colors],
props: { props: {
excelImport: Boolean,
loading: Boolean, loading: Boolean,
templateData: [Array, Object], templateData: [Array, Object],
importData: [Array, Object], importData: [Array, Object],
@ -99,8 +100,9 @@ export default {
// this.$emit('useTemplate', type) // this.$emit('useTemplate', type)
this.projectCreation = true this.projectCreation = true
let interv
try { try {
const interv = setInterval(() => { interv = setInterval(() => {
this.loaderMessagesIndex = this.loaderMessagesIndex < this.loaderMessages.length - 1 ? this.loaderMessagesIndex + 1 : 6 this.loaderMessagesIndex = this.loaderMessagesIndex < this.loaderMessages.length - 1 ? this.loaderMessagesIndex + 1 : 6
this.$store.commit('loader/MutMessage', this.loaderMessages[this.loaderMessagesIndex]) this.$store.commit('loader/MutMessage', this.loaderMessages[this.loaderMessagesIndex])
}, 1000) }, 1000)
@ -108,7 +110,8 @@ export default {
const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'projectCreateByWebWithXCDB', { const result = await this.$store.dispatch('sqlMgr/ActSqlOp', [null, 'projectCreateByWebWithXCDB', {
title: this.templateData.title, title: this.templateData.title,
projectType, projectType,
template: this.templateData template: this.templateData,
excelImport: this.excelImport
}]) }])
await this.$store.dispatch('project/ActLoadProjectInfo') await this.$store.dispatch('project/ActLoadProjectInfo')
@ -131,6 +134,8 @@ export default {
} catch (e) { } catch (e) {
console.log(e) console.log(e)
this.$toast.error(e.message).goAway(3000) this.$toast.error(e.message).goAway(3000)
this.$store.commit('loader/MutMessage', null)
clearInterval(interv)
} }
this.projectCreation = false this.projectCreation = false
}, },

22
packages/nc-gui/components/templates/editor.vue

@ -1,5 +1,5 @@
<template> <template>
<div class="h-100"> <div class="h-100" style="min-height: 500px">
<v-toolbar v-if="!viewMode" class="elevation-0"> <v-toolbar v-if="!viewMode" class="elevation-0">
<slot name="toolbar" :valid="valid"> <slot name="toolbar" :valid="valid">
<!-- <v-text-field <!-- <v-text-field
@ -86,6 +86,7 @@
</v-btn> </v-btn>
</slot> </slot>
</v-toolbar> </v-toolbar>
<v-divider class="mt-6" />
<v-container class="text-center" style="height:calc(100% - 64px);overflow-y: auto"> <v-container class="text-center" style="height:calc(100% - 64px);overflow-y: auto">
<v-form ref="form" v-model="valid"> <v-form ref="form" v-model="valid">
<v-row fluid class="justify-center"> <v-row fluid class="justify-center">
@ -97,17 +98,20 @@
<v-text-field <v-text-field
ref="project" ref="project"
v-model="project.title" v-model="project.title"
class="caption" class="title"
outlined outlined
dense hide-details
label="Project Name" denses
persistent-hint
:rules="[v => !!v || 'Project name required'] " :rules="[v => !!v || 'Project name required'] "
/> >
<template #label>
<span class="caption">Project Name</span>
</template>
</v-text-field>
</div> </div>
</div> </div>
<p v-if="project.tables" class="caption grey--text"> <p v-if="project.tables" class="caption grey--text mt-4">
{{ project.tables.length }} sheet{{ project.tables.length > 1 ? 's' :'' }} are available for import {{ project.tables.length }} sheet{{ project.tables.length > 1 ? 's' :'' }} are available for import
</p> </p>
@ -127,7 +131,7 @@
<v-text-field <v-text-field
v-if="editableTn[i]" v-if="editableTn[i]"
:value="table.tn" :value="table.tn"
class="title" class="font-weight-bold"
style="max-width: 300px" style="max-width: 300px"
outlinedk outlinedk
autofocus autofocus
@ -140,7 +144,7 @@
/> />
<span <span
v-else v-else
class="title" class="font-weight-bold"
@click="e => viewMode || (e.stopPropagation() , $set(editableTn,i, true))" @click="e => viewMode || (e.stopPropagation() , $set(editableTn,i, true))"
> >
<v-icon color="primary lighten-1">mdi-table</v-icon> <v-icon color="primary lighten-1">mdi-table</v-icon>

13
packages/nc-gui/components/utils/dlgTableCreate.vue

@ -20,6 +20,7 @@
persistent-hint persistent-hint
dense dense
hide-details1 hide-details1
:rules="[validateTableName]"
hint="Enter table name" hint="Enter table name"
class="mt-4 caption nc-table-name" class="mt-4 caption nc-table-name"
/> />
@ -119,6 +120,7 @@
<script> <script>
import inflection from 'inflection' import inflection from 'inflection'
import { validateTableName } from '~/helpers'
export default { export default {
name: 'DlgTableCreate', name: 'DlgTableCreate',
@ -131,7 +133,8 @@ export default {
'title', 'title',
'created_at', 'created_at',
'updated_at'] 'updated_at']
} },
validateTableName
} }
}, },
computed: { computed: {
@ -164,9 +167,11 @@ export default {
::v-deep{ ::v-deep{
.v-text-field__details { .v-text-field__details {
padding:0 2px !important; padding:0 2px !important;
.v-messages__message { .v-messages:not(.error--text) {
color: grey; .v-messages__message {
font-size: .65rem; color: grey;
font-size: .65rem;
}
} }
} }
} }

5
packages/nc-gui/components/utils/dlgTextSubmitCancel.vue

@ -24,7 +24,7 @@
v-model="fieldValue" v-model="fieldValue"
validate-on-blur validate-on-blur
:label="field || ''" :label="field || ''"
:rules="[v=> !!v || 'Value required']" :rules="[v=> !!v || 'Value required',...(rules || [])]"
autofocus autofocus
@keydown.enter.prevent="submitForm" @keydown.enter.prevent="submitForm"
/> />
@ -67,7 +67,8 @@ export default {
'type', 'type',
'cookie', 'cookie',
'defaultValue', 'defaultValue',
'submitText' 'submitText',
'rules'
], ],
data() { data() {
return { fieldValue: '', valid: null } return { fieldValue: '', valid: null }

34
packages/nc-gui/helpers/index.js

@ -78,6 +78,40 @@ function GetCaretPosition(ctrl) {
return (CaretPos) return (CaretPos)
} }
export function validateTableName(v) {
if (!v) {
return 'Table name required'
}
if (/^[_A-Za-z][_0-9A-Za-z]*$/.test(v)) {
return true
}
if (/^[^_A-Za-z]/.test(v)) {
return 'Name should start with an alphabet or _'
}
const m = v.match(/[^_A-Za-z\d]/g)
if (m) {
return `Following characters are not allowed ${m.map(c => JSON.stringify(c)).join(', ')}`
}
}
export function validateColumnName(v) {
if (!v) {
return 'Column name required'
}
if (/^[_A-Za-z][_0-9A-Za-z]*$/.test(v)) {
return true
}
if (/^[^_A-Za-z]/.test(v)) {
return 'Name should start with an alphabet or _'
}
const m = v.match(/[^_A-Za-z\d]/g)
if (m) {
return `Following characters are not allowed ${m.map(c => JSON.stringify(c)).join(', ')}`
}
}
/** /**
* @copyright Copyright (c) 2021, Xgene Cloud Ltd * @copyright Copyright (c) 2021, Xgene Cloud Ltd
* *

18
packages/nc-gui/helpers/rightClickOptions.js

@ -61,16 +61,16 @@ export default {
table: { table: {
'Table Rename': 'ENV_DB_TABLES_RENAME', 'Table Rename': 'ENV_DB_TABLES_RENAME',
// "Table Delete": "ENV_DB_TABLES_DELETE", // "Table Delete": "ENV_DB_TABLES_DELETE",
d1: null, // d1: null,
// "Send to SQL Editor": { // "Send to SQL Editor": {
'Copy To Clipboard': { // 'Copy To Clipboard': {
'Create Statement': 'ENV_DB_TABLES_CREATE_STATEMENT', // 'Create Statement': 'ENV_DB_TABLES_CREATE_STATEMENT',
'Insert Statement': 'ENV_DB_TABLES_INSERT_STATEMENT', // 'Insert Statement': 'ENV_DB_TABLES_INSERT_STATEMENT',
'Update Statement': 'ENV_DB_TABLES_UPDATE_STATEMENT', // 'Update Statement': 'ENV_DB_TABLES_UPDATE_STATEMENT',
'Select Statement': 'ENV_DB_TABLES_DELETE_STATEMENT', // 'Select Statement': 'ENV_DB_TABLES_DELETE_STATEMENT',
'Delete Statement': 'ENV_DB_TABLES_SELECT_STATEMENT' // 'Delete Statement': 'ENV_DB_TABLES_SELECT_STATEMENT'
}, // },
d2: null, // d2: null,
...(process.env.NODE_ENV === 'dev' ? { 'Show _Nodes Info': 'SHOW_NODES' } : {}) ...(process.env.NODE_ENV === 'dev' ? { 'Show _Nodes Info': 'SHOW_NODES' } : {})
}, },
view: { view: {

13
packages/nc-gui/layouts/default.vue

@ -148,19 +148,6 @@
@shortkey="$router.push('/')" @shortkey="$router.push('/')"
/> />
<x-btn
v-if="showAirtabLikeLink > 2"
text
btn.class="caption font-weight-bold px-2 text-capitalize"
tooltip="Data (^⇧D)"
to="/datatable"
>
<v-icon size="20">
mdi-table
</v-icon> &nbsp;
Data
</x-btn>
<x-btn <x-btn
v-if="!$store.state.windows.nc" v-if="!$store.state.windows.nc"
text text

4
packages/nc-gui/pages/projects/index.vue

@ -340,7 +340,7 @@
</td> </td>
<td style="width:150px;min-width:150px;max-width:150px"> <td style="width:150px;min-width:150px;max-width:150px">
<div <div
v-if="props.item.allowed && _isUIAllowed('projectActions',true)" v-if="props.item.allowed && _isUIAllowed('projectActions',true) && props.item.is_creator"
:class="{ :class="{
'action-icons': !( 'action-icons': !(
projectStatusUpdating && projectStatusUpdating &&
@ -1015,7 +1015,7 @@ export default {
.goAway(3000) .goAway(3000)
} catch (e) { } catch (e) {
this.$toast this.$toast
.error(`Project '${project.title}' restarting failed`) .error(`Project '${project.title}' deleting failed`)
.goAway(3000) .goAway(3000)
} }
await this.projectsLoad() await this.projectsLoad()

20
packages/nc-gui/pages/user/authentication/signup/_token.vue

@ -58,13 +58,6 @@
<template #progress /> <template #progress />
</v-text-field> </v-text-field>
<!---->
<!-- <vue-recaptcha @verify="onNormalVerify" sitekey="6LfbcqMUAAAAAAb_2319UdF8m68JHSYVy_m4wPBx"-->
<!-- style="transform:scale(0.7);-webkit-transform:scale(0.7);transform-origin:0 0;-webkit-transform-origin:0 0;">-->
<!-- </vue-recaptcha>-->
<v-btn <v-btn
v-ge="['Sign Up ','']" v-ge="['Sign Up ','']"
color="primary" color="primary"
@ -85,7 +78,10 @@
<br> <br>
<br> <br>
<br> <div class="d-flex align-center justify-center mb-2">
<v-switch v-model="subscribe" dense hide-details class="mt-0 pt-0" />
<label class="caption font-weight-light">Subscribe to our weekly newsletter</label>
</div>
<p v-ge="['Already have an account ?','']" class="font-weight-light caption"> <p v-ge="['Already have an account ?','']" class="font-weight-light caption">
{{ $t('signup.already_ve_an_account') }} {{ $t('signup.already_ve_an_account') }}
<router-link to="/user/authentication/signin"> <router-link to="/user/authentication/signin">
@ -198,10 +194,10 @@
<span class="grey--text pointer" @click="openUrl('https://nocodb.com/policy-nocodb')"><u>Terms of service</u></span> <span class="grey--text pointer" @click="openUrl('https://nocodb.com/policy-nocodb')"><u>Terms of service</u></span>
</p> &nbsp; </p> &nbsp;
<div class="d-flex align-center mb-4 justify-center"> <!-- <div class="d-flex align-center mb-4 justify-center">
<v-checkbox v-model="subscribe" color="grey" dense hide-details class="mt-0 pt-0" /> <v-checkbox v-model="subscribe" color="grey" dense hide-details class="mt-0 pt-0" />
<label class="caption grey--text font-weight-light">Subscribe to our weekly newsletter</label> <label class="caption grey&#45;&#45;text font-weight-light">Subscribe to our weekly newsletter</label>
</div> </div>-->
</v-row> </v-row>
<!--<br>--> <!--<br>-->
@ -247,7 +243,7 @@ export default {
data() { data() {
return { return {
subscribe: true, subscribe: false,
isDev: (process.env.NODE_ENV === 'dev'), isDev: (process.env.NODE_ENV === 'dev'),
dialog: false, dialog: false,

29
packages/nc-gui/pages/user/authentication/signup/index.vue

@ -78,8 +78,11 @@
<br> <br>
<br> <br>
<br> <div class="d-flex align-center justify-center mb-2">
<p v-ge="['Already have an account ?','']" class="font-weight-light caption grey--text"> <v-switch v-model="subscribe" dense hide-details class="mt-0 pt-0" />
<label class="caption font-weight-light">Subscribe to our weekly newsletter</label>
</div>
<p v-ge="['Already have an account ?','']" class="font-weight-light caption grey--text mb-0">
{{ $t('signup.already_ve_an_account') }} {{ $t('signup.already_ve_an_account') }}
<router-link to="/user/authentication/signin"> <router-link to="/user/authentication/signin">
{{ $t('signin.title') }} {{ $t('signin.title') }}
@ -176,25 +179,11 @@
By signing up, you agree to By signing up, you agree to
<span class="grey--text pointer" @click="openUrl('https://nocodb.com/policy-nocodb')"><u>Terms of service</u></span> <span class="grey--text pointer" @click="openUrl('https://nocodb.com/policy-nocodb')"><u>Terms of service</u></span>
</p> </p>
<div class="d-flex align-center mb-4 justify-center"> <!-- <div class="d-flex align-center mb-4 justify-center">
<v-checkbox v-model="subscribe" color="grey" dense hide-details class="mt-0 pt-0" /> <v-checkbox v-model="subscribe" color="grey" dense hide-details class="mt-0 pt-0" />
<label class="caption grey--text font-weight-light">Subscribe to our weekly newsletter</label> <label class="caption grey&#45;&#45;text font-weight-light">Subscribe to our weekly newsletter</label>
</div> </div>-->
</div> </div>
<!--<br>-->
<!--<h3>OR</h3>-->
<!--<br>-->
<!--<v-card class="pa-3 elevation-10" color="">-->
<!--<p>or sign up with one of these services</p>-->
<!--<a href="/api/auth/google?redirect_to=/"><img src=""~/assets/img//btn_google_signin_dark_normal_web@2x.png"-->
<!--class="img-responsive" alt="google" wifth="128px"></a>-->
<!--&lt;!&ndash;<br>&ndash;&gt;-->
<!--&lt;!&ndash;<a href="/api/auth/facebook?redirect_to=/"><img src="/facebook.png" class="img-responsive" alt="facebook"></a>&ndash;&gt;-->
<!--</v-card>-->
<!--<br>-->
<!--<br>-->
</v-col> </v-col>
</v-row> </v-row>
</v-col> </v-col>
@ -222,7 +211,7 @@ export default {
data() { data() {
return { return {
subscribe: true, subscribe: false,
isDev: (process.env.NODE_ENV === 'dev'), isDev: (process.env.NODE_ENV === 'dev'),
dialog: false, dialog: false,

1
packages/nc-gui/store/project.js

@ -282,6 +282,7 @@ export const actions = {
commit("list", data.data.list); commit("list", data.data.list);
commit("meta/MutClear", null, {root: true}); commit("meta/MutClear", null, {root: true});
commit("tabs/MutClearTabState",null, {root: true});
if (this.$ncApis) { if (this.$ncApis) {
this.$ncApis.clear(); this.$ncApis.clear();
this.$ncApis.setProjectId(projectId); this.$ncApis.setProjectId(projectId);

21
packages/nc-gui/store/tabs.js

@ -3,7 +3,8 @@ import Vue from 'vue'
export const state = () => ({ export const state = () => ({
list: [], list: [],
activeTab: 0, activeTab: 0,
activeTabCtx: {} activeTabCtx: {},
tabsState: {}
}) })
export const mutations = { export const mutations = {
@ -53,6 +54,20 @@ export const mutations = {
Vue.set(state, 'list', list) Vue.set(state, 'list', list)
// state.list = list; // state.list = list;
},
MutSetTabState(state, { id, key, val }) {
const tabState = { ...(state.tabsState[id] || {}) }
Vue.set(tabState, key, val)
Vue.set(state.tabsState, id, tabState)
},
MutClearTabState(state, key) {
if (key) {
const newState = { ...state.tabsState }
delete newState[key]
state.tabsState = newState
} else {
state.tabsState = {}
}
} }
} }
@ -257,7 +272,9 @@ export const actions = {
.children[0] // db .children[0] // db
.children.find(n => n.type === 'tableDir') // parent node .children.find(n => n.type === 'tableDir') // parent node
.children .children
if (nodes && nodes[0]) { tabs.push(nodes[0]) } if (nodes && nodes[0]) {
tabs.push(nodes[0])
}
} }
} }
commit('list', tabs) commit('list', tabs)

4
packages/nocodb/src/lib/dataMapper/lib/sql/BaseModelSql.ts

@ -75,8 +75,8 @@ class BaseModelSql extends BaseModel {
this.manyToManyRelations = manyToMany; this.manyToManyRelations = manyToMany;
this.virtualColumns = v; this.virtualColumns = v;
this.config = { this.config = {
limitDefault: process.env.DB_QUERY_LIMIT_DEFAULT || 10, limitDefault: process.env.DB_QUERY_LIMIT_DEFAULT || 25,
limitMax: process.env.DB_QUERY_LIMIT_MAX || 500, limitMax: process.env.DB_QUERY_LIMIT_MAX || 100,
limitMin: process.env.DB_QUERY_LIMIT_MIN || 1, limitMin: process.env.DB_QUERY_LIMIT_MIN || 1,
log: false, log: false,
explain: false, explain: false,

1
packages/nocodb/src/lib/noco/NcProjectBuilder.ts

@ -763,6 +763,7 @@ export default class NcProjectBuilder {
if (this.config.auth.jwt) { if (this.config.auth.jwt) {
if ( if (
!( !(
req.session.passport.user.roles.owner ||
req.session.passport.user.roles.creator || req.session.passport.user.roles.creator ||
req.session.passport.user.roles.editor || req.session.passport.user.roles.editor ||
req.session.passport.user.roles.commenter || req.session.passport.user.roles.commenter ||

89
packages/nocodb/src/lib/noco/meta/NcMetaIOImpl.ts

@ -407,44 +407,57 @@ export default class NcMetaIOImpl extends NcMetaIO {
public async userProjectList(userId: any): Promise<any[]> { public async userProjectList(userId: any): Promise<any[]> {
return ( return (
( await this.knexConnection('nc_projects')
await this.knexConnection('nc_projects') .leftJoin(
.leftJoin( this.knexConnection('nc_projects_users')
this.knexConnection('nc_projects_users') .where(`nc_projects_users.user_id`, userId)
.where(`nc_projects_users.user_id`, userId) .as('user'),
.as('user'), 'user.project_id',
'user.project_id', 'nc_projects.id'
'nc_projects.id' )
) .select('nc_projects.*')
.select('nc_projects.*') .select('user.user_id')
.select('user.user_id') .select(
//(SELECT `xc_users`.`email` this.knexConnection('xc_users')
// FROM `xc_users` .select('xc_users.email')
// INNER JOIN `nc_projects_users` .innerJoin(
// ON `nc_projects_users`.`user_id` = 'nc_projects_users',
// `xc_users`.`id` and `nc_projects_users`.project_id=`nc_projects`.id where `nc_projects_users`.`roles` like '%owner%' limit 1) 'nc_projects_users.user_id',
.select( '=',
this.knexConnection('xc_users') 'xc_users.id'
.select('xc_users.email') )
.innerJoin( .where('nc_projects_users.roles', 'like', '%owner%')
'nc_projects_users', .first()
'nc_projects_users.user_id', .as('owner')
'=', )
'xc_users.id' .select(
) this.knexConnection('xc_users')
.where('nc_projects_users.roles', 'like', '%owner%') .count('xc_users.id')
.first() .innerJoin(
.as('owner') 'nc_projects_users',
) 'nc_projects_users.user_id',
).map(p => { '=',
p.allowed = p.user_id === userId; 'xc_users.id'
p.config = CryptoJS.AES.decrypt( )
p.config, .where(qb => {
this.config?.auth?.jwt?.secret qb.where('nc_projects_users.roles', 'like', '%creator%').orWhere(
).toString(CryptoJS.enc.Utf8); 'nc_projects_users.roles',
return p; 'like',
}) '%owner%'
); );
})
.andWhere('xc_users.id', userId)
.first()
.as('is_creator')
)
).map(p => {
p.allowed = p.user_id === userId;
p.config = CryptoJS.AES.decrypt(
p.config,
this.config?.auth?.jwt?.secret
).toString(CryptoJS.enc.Utf8);
return p;
});
} }
public async isUserHaveAccessToProject( public async isUserHaveAccessToProject(

48
packages/nocodb/src/lib/noco/meta/NcMetaMgr.ts

@ -192,6 +192,7 @@ export default class NcMetaMgr {
if ( if (
!( !(
roles?.creator || roles?.creator ||
roles?.owner ||
roles?.editor || roles?.editor ||
roles?.viewer || roles?.viewer ||
roles?.commenter || roles?.commenter ||
@ -294,7 +295,14 @@ export default class NcMetaMgr {
oneClick: !!process.env.NC_ONE_CLICK, oneClick: !!process.env.NC_ONE_CLICK,
connectToExternalDB: !process.env connectToExternalDB: !process.env
.NC_CONNECT_TO_EXTERNAL_DB_DISABLED, .NC_CONNECT_TO_EXTERNAL_DB_DISABLED,
version: packageVersion version: packageVersion,
defaultLimit: Math.max(
Math.min(
+process.env.DB_QUERY_LIMIT_DEFAULT || 25,
+process.env.DB_QUERY_LIMIT_MAX || 100
),
+process.env.DB_QUERY_LIMIT_MIN || 1
)
}; };
return res.json(result); return res.json(result);
} }
@ -536,7 +544,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser( await this.xcMeta.projectAddUser(
projectId, projectId,
req?.session?.passport?.user?.id, req?.session?.passport?.user?.id,
'owner,creator' 'owner'
); );
await this.projectMgr await this.projectMgr
.getSqlMgr({ .getSqlMgr({
@ -644,7 +652,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser( await this.xcMeta.projectAddUser(
importProjectId, importProjectId,
req?.session?.passport?.user?.id, req?.session?.passport?.user?.id,
'owner,creator' 'owner'
); );
await this.projectMgr await this.projectMgr
.getSqlMgr({ .getSqlMgr({
@ -1535,6 +1543,7 @@ export default class NcMetaMgr {
'Creating new project with external Database not allowed' 'Creating new project with external Database not allowed'
); );
} }
await this.checkIsUserAllowedToCreateProject(req);
result = await this.xcMeta.projectCreate( result = await this.xcMeta.projectCreate(
args.args.project.title, args.args.project.title,
args.args.projectJson args.args.projectJson
@ -1542,7 +1551,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser( await this.xcMeta.projectAddUser(
result.id, result.id,
req?.session?.passport?.user?.id, req?.session?.passport?.user?.id,
'owner,creator' 'owner'
); );
await this.projectMgr await this.projectMgr
.getSqlMgr({ .getSqlMgr({
@ -1583,7 +1592,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser( await this.xcMeta.projectAddUser(
result.id, result.id,
req?.session?.passport?.user?.id, req?.session?.passport?.user?.id,
'owner,creator' 'owner'
); );
await this.projectMgr await this.projectMgr
.getSqlMgr({ .getSqlMgr({
@ -1605,6 +1614,7 @@ export default class NcMetaMgr {
} }
break; break;
case 'projectCreateByWebWithXCDB': { case 'projectCreateByWebWithXCDB': {
await this.checkIsUserAllowedToCreateProject(req);
const config = NcConfigFactory.makeProjectConfigFromConnection( const config = NcConfigFactory.makeProjectConfigFromConnection(
this.config?.meta?.db, this.config?.meta?.db,
args.args.projectType args.args.projectType
@ -1631,7 +1641,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser( await this.xcMeta.projectAddUser(
result.id, result.id,
req?.session?.passport?.user?.id, req?.session?.passport?.user?.id,
'owner,creator' 'owner'
); );
await this.projectMgr await this.projectMgr
.getSqlMgr({ .getSqlMgr({
@ -1657,6 +1667,12 @@ export default class NcMetaMgr {
Tele.emit('evt', { evt_type: 'project:created', xcdb: true }); Tele.emit('evt', { evt_type: 'project:created', xcdb: true });
postListenerCb = async () => { postListenerCb = async () => {
if (args?.args?.template) { if (args?.args?.template) {
Tele.emit('evt', {
evt_type: args.args?.excelImport
? 'project:created:fromExcel'
: 'project:created:fromTemplate',
xcdb: true
});
await this.xcModelsCreateFromTemplate( await this.xcModelsCreateFromTemplate(
{ {
dbAlias: 'db', // this.nodes.dbAlias, dbAlias: 'db', // this.nodes.dbAlias,
@ -4372,6 +4388,8 @@ export default class NcMetaMgr {
}); });
} }
Tele.emit('evt', { evt_type: 'template:imported' });
return result; return result;
} }
@ -5390,6 +5408,24 @@ export default class NcMetaMgr {
return nestedParams; return nestedParams;
} }
private async checkIsUserAllowedToCreateProject(req: any): Promise<void> {
const user = req.user;
const roles = await this.xcMeta.metaList(null, null, 'nc_projects_users', {
condition: { user_id: user?.id },
xcCondition: {
_or: [{ roles: { like: '%creator%' } }, { roles: { like: '%owner%' } }]
},
fields: ['roles']
});
if (
!roles.some(r => /\b(?:owner|creator)\b/.test(r?.roles)) &&
(await this.xcMeta.metaList(null, null, 'nc_projects'))?.length
) {
throw new Error("You don't have permission to create project");
}
}
} }
export class XCEeError extends Error { export class XCEeError extends Error {

26
packages/nocodb/src/lib/noco/rest/RestAuthCtrl.ts

@ -37,7 +37,7 @@ passport.serializeUser(function(
id, id,
email, email,
email_verified, email_verified,
roles, roles: _roles,
provider, provider,
firstname, firstname,
lastname, lastname,
@ -46,6 +46,12 @@ passport.serializeUser(function(
}, },
done done
) { ) {
const roles = (_roles || '')
.split(',')
.reduce((obj, role) => Object.assign(obj, { [role]: true }), {});
if (roles.owner) {
roles.creator = true;
}
done(null, { done(null, {
isAuthorized, isAuthorized,
isPublicBase, isPublicBase,
@ -55,9 +61,7 @@ passport.serializeUser(function(
provider, provider,
firstname, firstname,
lastname, lastname,
roles: (roles || '') roles
.split(',')
.reduce((obj, role) => Object.assign(obj, { [role]: true }), {})
}); });
}); });
@ -247,7 +251,12 @@ export default class RestAuthCtrl {
}, },
(_err, user, _info) => { (_err, user, _info) => {
if (user) { if (user) {
return resolve({ ...user, isAuthorized: true }); return resolve({
...user,
isAuthorized: true,
roles:
user.roles === 'owner' ? 'owner,creator' : user.roles
});
} else { } else {
resolve({ roles: 'guest' }); resolve({ roles: 'guest' });
} }
@ -401,7 +410,7 @@ export default class RestAuthCtrl {
let roles = 'editor'; let roles = 'editor';
if (!(await this.users.first())) { if (!(await this.users.first())) {
roles = 'owner,creator,editor'; roles = 'owner';
} }
if (!user) { if (!user) {
@ -508,7 +517,7 @@ export default class RestAuthCtrl {
let roles = 'editor'; let roles = 'editor';
if (!(await this.users.first())) { if (!(await this.users.first())) {
roles = 'owner,creator,editor'; roles = 'owner';
} }
if (!user) { if (!user) {
@ -573,7 +582,7 @@ export default class RestAuthCtrl {
.first() .first()
.then(user => { .then(user => {
if (user) { if (user) {
user.roles = 'owner,creator'; user.roles = 'owner';
return done(null, user); return done(null, user);
} else { } else {
return done(new Error('User not found')); return done(new Error('User not found'));
@ -900,6 +909,7 @@ export default class RestAuthCtrl {
let roles = 'user'; let roles = 'user';
if (!(await this.users.first())) { if (!(await this.users.first())) {
// todo: update in nc_store
// roles = 'owner,creator,editor' // roles = 'owner,creator,editor'
} else { } else {
if (process.env.NC_INVITE_ONLY_SIGNUP) { if (process.env.NC_INVITE_ONLY_SIGNUP) {

2
packages/nocodb/src/lib/noco/rest/RestAuthCtrlEE.ts

@ -224,6 +224,8 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
}) })
.then(projectUser => { .then(projectUser => {
user.roles = projectUser.roles; user.roles = projectUser.roles;
user.roles =
user.roles === 'owner' ? 'owner,creator' : user.roles;
XcCache.set(key, user); XcCache.set(key, user);
done(null, user); done(null, user);
}); });

2
packages/nocodb/src/lib/sqlMgr/SqlMgr.ts

@ -1,6 +1,7 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import url from 'url'; import url from 'url';
import { Tele } from 'nc-help';
import fsExtra from 'fs-extra'; import fsExtra from 'fs-extra';
import importFresh from 'import-fresh'; import importFresh from 'import-fresh';
@ -1023,6 +1024,7 @@ export default class SqlMgr {
// t = process.hrtime(); // t = process.hrtime();
const data = await require('axios')(...apiMeta); const data = await require('axios')(...apiMeta);
Tele.emit('evt', { evt_type: 'import:excel:url' });
return data.data; return data.data;
} }

Loading…
Cancel
Save