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",
"version": "0.1.21",
"version": "0.1.22",
"description": "nc-cli",
"main": "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]);
mkdirp.sync(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);
if (answers.type !== 'sqlite3') {
@ -239,7 +239,7 @@ class NewMgr {
}
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',
borderStyle: 'round',
margin: 1,
@ -490,7 +490,7 @@ class NewMgr {
args.folder = path.join(args.folder, args._[1]);
mkdirp.sync(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);
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) {
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 webpack = require('webpack');
const JavaScriptObfuscator = require('webpack-obfuscator');
// const JavaScriptObfuscator = require('webpack-obfuscator');
const path = require('path');
module.exports = {
entry: './src/index.ts',
@ -43,11 +43,6 @@ module.exports = {
// },
plugins: [
new JavaScriptObfuscator({
rotateStringArray: true,
splitStrings: true,
splitStringsChunkLength: 6
}, []),
new webpack.BannerPlugin({banner: "#! /usr/bin/env node", raw: true}),
// new CopyPlugin({

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

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

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

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

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

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

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

@ -26,7 +26,8 @@
color="primary"
:rules="[
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"
label="Column name"
@ -420,6 +421,7 @@ import RelationOptions from '@/components/project/spreadsheet/components/editCol
import DlgLabelSubmitCancel from '@/components/utils/dlgLabelSubmitCancel'
import LinkedToAnotherOptions from '@/components/project/spreadsheet/components/editColumn/linkedToAnotherOptions'
import { SqliteUi, MssqlUi } from '@/helpers/sqlUi'
import { validateColumnName } from '~/helpers'
export default {
name: 'EditColumn',
@ -442,6 +444,7 @@ export default {
value: Boolean
},
data: () => ({
validateColumnName,
valid: false,
relationDeleteDlg: false,
newColumn: {},

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

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

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

@ -37,22 +37,6 @@
:class="{ active: showCommunity }"
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">
<!-- Get your questions answered -->
<v-list-item-title>
@ -65,6 +49,18 @@
</v-list-item-title>
</v-list-item>
<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">
<!-- Get your questions answered -->
<v-list-item-title>
@ -77,14 +73,18 @@
</v-list-item-title>
</v-list-item>
<v-divider />
<v-list-item dense href="https://twitter.com/NocoDB" target="_blank">
<!-- Follow NocoDB -->
<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[1]">
mdi-twitter
<v-icon class="mr-1" small :color="textColors[3]">
mdi-calendar-month
</v-icon>
<span class="caption" title="$t('projects.show_community_follow_nocodb')"> {{
$t('projects.show_community_follow_nocodb')
<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>

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

@ -583,6 +583,7 @@ export default {
},
mixins: [spreadsheet],
props: {
tabId: String,
env: String,
nodes: Object,
addNewRelationTab: Function,
@ -640,7 +641,7 @@ export default {
},
page: 1,
count: 0,
size: 25,
// size: 25,
xWhere: '',
sort: '',
@ -668,8 +669,38 @@ export default {
}],
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() {
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()
this.loadingMeta = true
await this.loadMeta(false)
@ -1127,6 +1158,13 @@ export default {
}
},
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() {
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
ref="tabs7"
:tab-id="tabId"
:show-tabs="relationTabs && relationTabs.length"
:table="nodes.tn"
:nodes="nodes"
@ -638,7 +639,7 @@ export default {
head() {
return {}
},
props: ['nodes', 'hideLogWindows']
props: ['nodes', 'hideLogWindows', 'tabId']
}
</script>

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

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

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

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

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

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

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

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

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

@ -78,6 +78,40 @@ function GetCaretPosition(ctrl) {
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
*

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

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

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

@ -148,19 +148,6 @@
@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
v-if="!$store.state.windows.nc"
text

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

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

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

@ -58,13 +58,6 @@
<template #progress />
</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-ge="['Sign Up ','']"
color="primary"
@ -85,7 +78,10 @@
<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">
{{ $t('signup.already_ve_an_account') }}
<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>
</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" />
<label class="caption grey--text font-weight-light">Subscribe to our weekly newsletter</label>
</div>
<label class="caption grey&#45;&#45;text font-weight-light">Subscribe to our weekly newsletter</label>
</div>-->
</v-row>
<!--<br>-->
@ -247,7 +243,7 @@ export default {
data() {
return {
subscribe: true,
subscribe: false,
isDev: (process.env.NODE_ENV === 'dev'),
dialog: false,

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

@ -78,8 +78,11 @@
<br>
<br>
<br>
<p v-ge="['Already have an account ?','']" class="font-weight-light caption grey--text">
<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 grey--text mb-0">
{{ $t('signup.already_ve_an_account') }}
<router-link to="/user/authentication/signin">
{{ $t('signin.title') }}
@ -176,25 +179,11 @@
By signing up, you agree to
<span class="grey--text pointer" @click="openUrl('https://nocodb.com/policy-nocodb')"><u>Terms of service</u></span>
</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" />
<label class="caption grey--text font-weight-light">Subscribe to our weekly newsletter</label>
</div>
<label class="caption grey&#45;&#45;text font-weight-light">Subscribe to our weekly newsletter</label>
</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-row>
</v-col>
@ -222,7 +211,7 @@ export default {
data() {
return {
subscribe: true,
subscribe: false,
isDev: (process.env.NODE_ENV === 'dev'),
dialog: false,

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

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

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

@ -3,7 +3,8 @@ import Vue from 'vue'
export const state = () => ({
list: [],
activeTab: 0,
activeTabCtx: {}
activeTabCtx: {},
tabsState: {}
})
export const mutations = {
@ -53,6 +54,20 @@ export const mutations = {
Vue.set(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.find(n => n.type === 'tableDir') // parent node
.children
if (nodes && nodes[0]) { tabs.push(nodes[0]) }
if (nodes && nodes[0]) {
tabs.push(nodes[0])
}
}
}
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.virtualColumns = v;
this.config = {
limitDefault: process.env.DB_QUERY_LIMIT_DEFAULT || 10,
limitMax: process.env.DB_QUERY_LIMIT_MAX || 500,
limitDefault: process.env.DB_QUERY_LIMIT_DEFAULT || 25,
limitMax: process.env.DB_QUERY_LIMIT_MAX || 100,
limitMin: process.env.DB_QUERY_LIMIT_MIN || 1,
log: 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 (
!(
req.session.passport.user.roles.owner ||
req.session.passport.user.roles.creator ||
req.session.passport.user.roles.editor ||
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[]> {
return (
(
await this.knexConnection('nc_projects')
.leftJoin(
this.knexConnection('nc_projects_users')
.where(`nc_projects_users.user_id`, userId)
.as('user'),
'user.project_id',
'nc_projects.id'
)
.select('nc_projects.*')
.select('user.user_id')
//(SELECT `xc_users`.`email`
// FROM `xc_users`
// INNER JOIN `nc_projects_users`
// ON `nc_projects_users`.`user_id` =
// `xc_users`.`id` and `nc_projects_users`.project_id=`nc_projects`.id where `nc_projects_users`.`roles` like '%owner%' limit 1)
.select(
this.knexConnection('xc_users')
.select('xc_users.email')
.innerJoin(
'nc_projects_users',
'nc_projects_users.user_id',
'=',
'xc_users.id'
)
.where('nc_projects_users.roles', 'like', '%owner%')
.first()
.as('owner')
)
).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;
})
);
await this.knexConnection('nc_projects')
.leftJoin(
this.knexConnection('nc_projects_users')
.where(`nc_projects_users.user_id`, userId)
.as('user'),
'user.project_id',
'nc_projects.id'
)
.select('nc_projects.*')
.select('user.user_id')
.select(
this.knexConnection('xc_users')
.select('xc_users.email')
.innerJoin(
'nc_projects_users',
'nc_projects_users.user_id',
'=',
'xc_users.id'
)
.where('nc_projects_users.roles', 'like', '%owner%')
.first()
.as('owner')
)
.select(
this.knexConnection('xc_users')
.count('xc_users.id')
.innerJoin(
'nc_projects_users',
'nc_projects_users.user_id',
'=',
'xc_users.id'
)
.where(qb => {
qb.where('nc_projects_users.roles', 'like', '%creator%').orWhere(
'nc_projects_users.roles',
'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(

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

@ -192,6 +192,7 @@ export default class NcMetaMgr {
if (
!(
roles?.creator ||
roles?.owner ||
roles?.editor ||
roles?.viewer ||
roles?.commenter ||
@ -294,7 +295,14 @@ export default class NcMetaMgr {
oneClick: !!process.env.NC_ONE_CLICK,
connectToExternalDB: !process.env
.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);
}
@ -536,7 +544,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser(
projectId,
req?.session?.passport?.user?.id,
'owner,creator'
'owner'
);
await this.projectMgr
.getSqlMgr({
@ -644,7 +652,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser(
importProjectId,
req?.session?.passport?.user?.id,
'owner,creator'
'owner'
);
await this.projectMgr
.getSqlMgr({
@ -1535,6 +1543,7 @@ export default class NcMetaMgr {
'Creating new project with external Database not allowed'
);
}
await this.checkIsUserAllowedToCreateProject(req);
result = await this.xcMeta.projectCreate(
args.args.project.title,
args.args.projectJson
@ -1542,7 +1551,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser(
result.id,
req?.session?.passport?.user?.id,
'owner,creator'
'owner'
);
await this.projectMgr
.getSqlMgr({
@ -1583,7 +1592,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser(
result.id,
req?.session?.passport?.user?.id,
'owner,creator'
'owner'
);
await this.projectMgr
.getSqlMgr({
@ -1605,6 +1614,7 @@ export default class NcMetaMgr {
}
break;
case 'projectCreateByWebWithXCDB': {
await this.checkIsUserAllowedToCreateProject(req);
const config = NcConfigFactory.makeProjectConfigFromConnection(
this.config?.meta?.db,
args.args.projectType
@ -1631,7 +1641,7 @@ export default class NcMetaMgr {
await this.xcMeta.projectAddUser(
result.id,
req?.session?.passport?.user?.id,
'owner,creator'
'owner'
);
await this.projectMgr
.getSqlMgr({
@ -1657,6 +1667,12 @@ export default class NcMetaMgr {
Tele.emit('evt', { evt_type: 'project:created', xcdb: true });
postListenerCb = async () => {
if (args?.args?.template) {
Tele.emit('evt', {
evt_type: args.args?.excelImport
? 'project:created:fromExcel'
: 'project:created:fromTemplate',
xcdb: true
});
await this.xcModelsCreateFromTemplate(
{
dbAlias: 'db', // this.nodes.dbAlias,
@ -4372,6 +4388,8 @@ export default class NcMetaMgr {
});
}
Tele.emit('evt', { evt_type: 'template:imported' });
return result;
}
@ -5390,6 +5408,24 @@ export default class NcMetaMgr {
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 {

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

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

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

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

Loading…
Cancel
Save