Browse Source

fix: Bulk add users

Enabled option to add bulk add user with comma separated emails

re #300

Signed-off-by: Pranav C <61551451+pranavxc@users.noreply.github.com>
pull/350/head
Pranav C 3 years ago
parent
commit
12ab1d2d8a
  1. 22
      packages/nc-gui/components/auth/userManagement.vue
  2. 96
      packages/nc-gui/components/project/spreadsheet/components/extras.vue
  3. 59
      packages/nc-gui/components/project/spreadsheet/components/spreadsheetNavDrawer.vue
  4. 90
      packages/nc-gui/layouts/default.vue
  5. 1
      packages/nc-gui/nuxt.config.js
  6. 2
      packages/nc-gui/pages/projects/index.vue
  7. 9
      packages/nc-gui/plugins/globalEventBus.js
  8. 1
      packages/nc-gui/static/lang/en.json
  9. 1
      packages/nc-gui/static/lang/fr.json
  10. 1
      packages/nc-gui/static/lang/ja.json
  11. 2
      packages/nc-gui/static/lang/zh.json
  12. 2
      packages/nc-lib-gui/package.json
  13. 57
      packages/nocodb/src/lib/noco/rest/RestAuthCtrlEE.ts

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

@ -346,7 +346,7 @@
></dlg-label-submit-cancel> ></dlg-label-submit-cancel>
<v-dialog v-model="userEditDialog" :width="invite_token ? 700 :480" @close="invite_token = null"> <v-dialog v-model="userEditDialog" :width="invite_token ? 700 :700" @close="invite_token = null">
<v-card v-if="selectedUser" class="px-15 py-5 " style="min-height: 100%"> <v-card v-if="selectedUser" class="px-15 py-5 " style="min-height: 100%">
<h4 class="text-center text-capitalize mt-2 d-100 display-1"> <h4 class="text-center text-capitalize mt-2 d-100 display-1">
<template v-if="invite_token">Copy Invite Token</template> <template v-if="invite_token">Copy Invite Token</template>
@ -388,7 +388,10 @@
<p class="caption grey--text mt-3"> Looks like you have not configured mailer yet! <br>Please copy above <p class="caption grey--text mt-3"> Looks like you have not configured mailer yet! <br>Please copy above
invite invite
link and send it to {{ invite_token && invite_token.email }}.</p> link and send it to {{ invite_token && (invite_token.email || invite_token.emails && invite_token.emails.join(', ')) }}.</p>
<!-- todo: show error message if failed-->
</div> </div>
<template v-else> <template v-else>
<v-form v-model="valid" @submit.prevent="saveUser"> <v-form v-model="valid" @submit.prevent="saveUser">
@ -403,6 +406,8 @@
v-model="selectedUser.email" v-model="selectedUser.email"
:rules="emailRules" :rules="emailRules"
@input="edited=true" @input="edited=true"
validate-on-blur
hint="You can add multiple comma(,) separated emails"
label="Email"></v-text-field> label="Email"></v-text-field>
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
@ -476,14 +481,21 @@ export default {
valid: null, valid: null,
emailRules: [ emailRules: [
v => !!v || 'E-mail is required', v => !!v || 'E-mail is required',
v => isEmail(v) || 'E-mail must be valid' v => {
const invalidEmails = (v||'').split(/\s*,\s*/).filter(e => !isEmail(e));
return !invalidEmails.length || `"${invalidEmails.join(', ')}" - invalid email`
}
], ],
userList: [] userList: []
}), }),
async created() { async created() {
this.$eventBus.$on('show-add-user', this.addUser);
await this.loadUsers(); await this.loadUsers();
await this.loadRoles(); await this.loadRoles();
}, },
beforeDestroy() {
this.$eventBus.$off('show-add-user', this.addUser);
},
methods: { methods: {
simpleAnim() { simpleAnim() {
var count = 30; var count = 30;
@ -619,7 +631,7 @@ export default {
addUser() { addUser() {
this.invite_token = null; this.invite_token = null;
this.selectedUser = { this.selectedUser = {
roles: 'creator,editor' roles: 'editor'
} }
this.userEditDialog = true this.userEditDialog = true
}, },
@ -733,7 +745,7 @@ export default {
this.selectedUser = this.users[i]; this.selectedUser = this.users[i];
} }
} }
} },
} }
</script> </script>

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

@ -0,0 +1,96 @@
<template>
<div class="wrapper">
<div class="d-flex justify-end">
<v-list
width="100%"
class="
flex-shrink-1
text-left
elevation-1
rounded-sm
community-card
item
"
: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_message_2')">{{
$t('projects.show_community_message_2')
}}</span></v-list-item-title>
</v-list-item>
<v-divider></v-divider>
<v-list-item dense href="https://discord.gg/5RgZmkW" target="_blank">
<!-- Get your questions answered -->
<v-list-item-title>
<v-icon class="mr-1" small :color="textColors[0]">mdi-discord</v-icon>
<span class="caption" :title="$t('projects.show_community_message_3_short')">{{
$t('projects.show_community_message_3_short')
}}</span>
</v-list-item-title>
</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_message_4')"> {{
$t('projects.show_community_message_4')
}}</span></v-list-item-title>
</v-list-item>
</v-list>
</div>
<sponsor-mini
:class="{ active: !showCommunity }" class="item" :nav="true"></sponsor-mini>
</div>
</template>
<script>
import SponsorMini from "~/components/sponsorMini";
import colors from "~/mixins/colors";
export default {
name: "extras",
data: () => ({
showCommunity: true
}),
mixins: [colors],
components: {SponsorMini},
mounted() {
setInterval(() => this.showCommunity = !this.showCommunity, 60000)
}
}
</script>
<style scoped lang="scss">
.wrapper {
position: relative;
.item {
z-index: -1;
opacity: 0;
position: absolute;
transition: .6s opacity;
bottom: 0;
right: 0;
width: 100%;
&.active {
z-index: 1;
position: relative;
opacity: 1;
}
}
}
</style>

59
packages/nc-gui/components/project/spreadsheet/components/spreadsheetNavDrawer.vue

@ -93,13 +93,14 @@
</x-icon> </x-icon>
</template> </template>
<v-icon v-if="view.id === selectedViewId" small class="check-icon" <v-icon v-if="view.id === selectedViewId" small class="check-icon"
>mdi-check-bold</v-icon >mdi-check-bold
</v-icon
> >
</v-list-item> </v-list-item>
</v-list-item-group> </v-list-item-group>
</v-list> </v-list>
<template v-if="hideViews && _isUIAllowed('virtualViewsCreateOrEdit')"> <template v-if="hideViews && _isUIAllowed('virtualViewsCreateOrEdit')">
<v-divider class="advance-menu-divider"> </v-divider> <v-divider class="advance-menu-divider"></v-divider>
<v-list <v-list
dense dense
@ -256,9 +257,13 @@
v-if="time - $store.state.windows.miniSponsorCard > 15 * 60 * 1000" v-if="time - $store.state.windows.miniSponsorCard > 15 * 60 * 1000"
> >
<v-icon small class="close-icon" @click="hideMiniSponsorCard" <v-icon small class="close-icon" @click="hideMiniSponsorCard"
>mdi-close-circle-outline</v-icon >mdi-close-circle-outline
</v-icon
> >
<sponsor-mini :nav="true"></sponsor-mini>
<extras></extras>
</div> </div>
<!--<div class="text-center"> <!--<div class="text-center">
<v-hover > <v-hover >
@ -338,28 +343,6 @@
</v-list> </v-list>
</v-menu> </v-menu>
</v-list-item> </v-list-item>
<!-- </template>-->
<!-- <v-card dense class="backgroundColor">-->
<!-- <v-container fluid @click.stop>-->
<!-- <v-text-field-->
<!-- label="Password"-->
<!-- hint="Enter shared view password"-->
<!-- flat-->
<!-- solo-->
<!-- dense-->
<!-- v-model="sharedViewPassword"-->
<!-- ></v-text-field>-->
<!-- <div class="text-right">-->
<!-- <v-btn small color="primary" @click="genShareLink()">Create</v-btn>-->
<!-- </div>-->
<!-- </v-container>-->
<!-- </v-card>-->
<!-- </v-menu>-->
<!-- </template>-->
<!-- Generate shared view url-->
<!-- </v-tooltip>-->
<v-tooltip bottom> <v-tooltip bottom>
<template v-slot:activator="{ on }"> <template v-slot:activator="{ on }">
@ -407,7 +390,7 @@
</p> </p>
<div style="border-radius: 4px" class="share-link-box body-2 pa-2 d-flex align-center"> <div style="border-radius: 4px" class="share-link-box body-2 pa-2 d-flex align-center">
{{ shareLink.url }} {{ shareLink.url }}
<v-spacer> </v-spacer> <v-spacer></v-spacer>
<a :href="shareLink.url" style="text-decoration: none" target="_blank"> <a :href="shareLink.url" style="text-decoration: none" target="_blank">
<v-icon small class="mx-2">mdi-open-in-new</v-icon> <v-icon small class="mx-2">mdi-open-in-new</v-icon>
</a> </a>
@ -461,11 +444,11 @@
<script> <script>
import CreateViewDialog from '@/components/project/spreadsheet/dialog/createViewDialog'; import CreateViewDialog from '@/components/project/spreadsheet/dialog/createViewDialog';
import SponsorMini from '@/components/sponsorMini'; import Extras from "~/components/project/spreadsheet/components/extras";
export default { export default {
name: 'spreadsheetNavDrawer', name: 'spreadsheetNavDrawer',
components: { SponsorMini, CreateViewDialog }, components: {Extras, CreateViewDialog},
props: { props: {
showAdvanceOptions: Boolean, showAdvanceOptions: Boolean,
hideViews: Boolean, hideViews: Boolean,
@ -505,11 +488,11 @@ export default {
overShieldIcon: false, overShieldIcon: false,
viewsList: [], viewsList: [],
viewIcons: { viewIcons: {
grid: { icon: 'mdi-grid-large', color: 'blue' }, grid: {icon: 'mdi-grid-large', color: 'blue'},
form: { icon: 'mdi-form-select', color: 'pink' }, form: {icon: 'mdi-form-select', color: 'pink'},
calendar: { icon: 'mdi-calendar', color: 'purple' }, calendar: {icon: 'mdi-calendar', color: 'purple'},
gallery: { icon: 'mdi-camera-image', color: 'orange' }, gallery: {icon: 'mdi-camera-image', color: 'orange'},
kanban: { icon: 'mdi-tablet-dashboard', color: 'green' }, kanban: {icon: 'mdi-tablet-dashboard', color: 'green'},
}, },
copyViewRef: null, copyViewRef: null,
shareLink: {}, shareLink: {},
@ -592,7 +575,7 @@ export default {
async saveShareLinkPassword() { async saveShareLinkPassword() {
try { try {
await this.$store.dispatch('sqlMgr/ActSqlOp', [ await this.$store.dispatch('sqlMgr/ActSqlOp', [
{ dbAlias: this.nodes.dbAlias }, {dbAlias: this.nodes.dbAlias},
'updateSharedViewLinkPassword', 'updateSharedViewLinkPassword',
{ {
id: this.shareLink.id, id: this.shareLink.id,
@ -639,7 +622,7 @@ export default {
}, },
async updateViewName(view) { async updateViewName(view) {
try { try {
await this.sqlOp({ dbAlias: this.nodes.dbAlias }, 'xcVirtualTableRename', { await this.sqlOp({dbAlias: this.nodes.dbAlias}, 'xcVirtualTableRename', {
id: view.id, id: view.id,
title: view.title, title: view.title,
alias: view.alias, alias: view.alias,
@ -661,7 +644,7 @@ export default {
}, },
async deleteView(view) { async deleteView(view) {
try { try {
await this.sqlOp({ dbAlias: this.nodes.dbAlias }, 'xcVirtualTableDelete', { await this.sqlOp({dbAlias: this.nodes.dbAlias}, 'xcVirtualTableDelete', {
id: view.id, id: view.id,
title: view.alias || view.title, title: view.alias || view.title,
parent_model_title: this.table, parent_model_title: this.table,
@ -675,7 +658,7 @@ export default {
async genShareLink() { async genShareLink() {
this.showShareModel = true; this.showShareModel = true;
const sharedViewUrl = await this.$store.dispatch('sqlMgr/ActSqlOp', [ const sharedViewUrl = await this.$store.dispatch('sqlMgr/ActSqlOp', [
{ dbAlias: this.nodes.dbAlias }, {dbAlias: this.nodes.dbAlias},
'createSharedViewLink', 'createSharedViewLink',
{ {
model_name: this.table, model_name: this.table,

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

@ -34,6 +34,35 @@
</template> </template>
</v-toolbar-title> </v-toolbar-title>
<v-toolbar-items class="ml-3">
<gh-btns-star
icon="mark-github" slug="nocodb/nocodb" show-count class="mr-3 align-self-center"></gh-btns-star>
</v-toolbar-items>
<!-- <template v-if="!isThisMobile ">
<a class="align-self-center" style="" target="_blank" href="https://calendly.com/nocodb">
<x-icon size="20" tooltip="Book a free demo" color="white" icon.class="mr-3">mdi-calendar-month</x-icon>
</a>
<a href="https://twitter.com/NocoDB" target="_blank" class="align-self-center" style="">
<v-icon size="20" color="white" class="mr-3">mdi-twitter</v-icon>
</a>
&lt;!&ndash; <v-menu offset-y>&ndash;&gt;
&lt;!&ndash; <template v-slot:activator="{on}">&ndash;&gt;
&lt;!&ndash; href="https://discord.gg/5RgZmkW"&ndash;&gt;
<a target="_blank" class="align-self-center" style="" href="https://discord.gg/5RgZmkW">
<v-icon size="20" color="white" class="mr-3">mdi-discord</v-icon>
</a>
&lt;!&ndash; </template>&ndash;&gt;
&lt;!&ndash; <discord></discord>&ndash;&gt;
&lt;!&ndash; </v-menu>&ndash;&gt;
</template>
</v-toolbar-items>-->
<span class="caption grey--text ml-3" v-show="$nuxt.$loading.show">Loading <v-icon small color="grey">mdi-spin mdi-loading</v-icon></span> <span class="caption grey--text ml-3" v-show="$nuxt.$loading.show">Loading <v-icon small color="grey">mdi-spin mdi-loading</v-icon></span>
<!-- <gh-btns-star--> <!-- <gh-btns-star-->
@ -51,7 +80,7 @@
</x-icon> </x-icon>
</a> </a>
<a href="https://calendly.com/xgenecloud/demo" target="_blank"> <a href="https://calendly.com/nocodb" target="_blank">
<x-icon iconClass="ml-4" tooltip="Book a demo"> <x-icon iconClass="ml-4" tooltip="Book a demo">
mdi-video-outline mdi-video-outline
</x-icon> </x-icon>
@ -59,7 +88,7 @@
<!-- <x-btn btn.class=" mr-4" small text--> <!-- <x-btn btn.class=" mr-4" small text-->
<!-- tooltip="We'll help you get started over a video call"--> <!-- tooltip="We'll help you get started over a video call"-->
<!-- @click="openUrl('https://calendly.com/xgenecloud/demo')">--> <!-- @click="openUrl('https://calendly.com/nocodb')">-->
<!-- <v-icon--> <!-- <v-icon-->
<!-- color="">--> <!-- color="">-->
@ -78,9 +107,6 @@
mdi-folder-outline mdi-folder-outline
</v-icon> </v-icon>
{{ $store.getters['project/GtrProjectName'] }} {{ $store.getters['project/GtrProjectName'] }}
<!-- <v-icon small color="grey lighten-2">
mdi-menu-down
</v-icon>-->
<v-tooltip bottom> <v-tooltip bottom>
<template v-slot:activator="{on}"> <template v-slot:activator="{on}">
<v-icon style="pointer-events:all" x-small v-on="on" color="grey lighten-2"> <v-icon style="pointer-events:all" x-small v-on="on" color="grey lighten-2">
@ -91,23 +117,20 @@
</v-tooltip> </v-tooltip>
</h5> </h5>
</div> </div>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-sm-and-down"> <v-spacer></v-spacer>
<!-- <x-icon--> <v-toolbar-items class="hidden-sm-and-down">
<!-- :iconClass="isDashboard ? 'mr-4 mt-n1':'mr-4 mt-n1 '" @click="loadChat" color="#2196f3 #2196f3"-->
<!-- v-if="!$store.state.windows.isComp"-->
<!-- tooltip="Facing any issues ? Chat with us<br>We can help you right away">-->
<!-- mdi-account-tie-voice-->
<!-- </x-icon>-->
<div>
<x-btn v-if="_isUIAllowed('add-user')" @click="rolesTabAdd" small color="white" btn-class="primary--text">
<v-icon small class="mr-1">mdi-account-supervisor-outline</v-icon>
Share
</x-btn>
</div>
<!-- <x-icon iconClass="mr-4" v-if="$store.getters['project/GtrProjectJson']" @click="environmentDialog = true" tooltip="Environments">-->
<!-- mdi-eye-outline-->
<!-- </x-icon>-->
<!-- <x-icon iconClass="mr-4" v-if="isDashboard"--> <!-- <x-icon iconClass="mr-4" v-if="isDashboard"-->
<!-- @click="codeGenerateMvc()"--> <!-- @click="codeGenerateMvc()"-->
@ -137,31 +160,6 @@
<template v-if="isDashboard"> <template v-if="isDashboard">
<template v-if="!isThisMobile ">
<a class="align-self-center" style="" target="_blank" href="https://calendly.com/xgenecloud/demo">
<x-icon size="20" tooltip="Book a free demo" color="white" icon.class="mr-3">mdi-calendar-month</x-icon>
</a>
<a href="https://twitter.com/NocoDB" target="_blank" class="align-self-center" style="">
<v-icon size="20" color="white" class="mr-3">mdi-twitter</v-icon>
</a>
<!-- <v-menu offset-y>-->
<!-- <template v-slot:activator="{on}">-->
<!-- href="https://discord.gg/5RgZmkW"-->
<a target="_blank" class="align-self-center" style="" href="https://discord.gg/5RgZmkW">
<v-icon size="20" color="white" class="mr-3">mdi-discord</v-icon>
</a>
<!-- </template>-->
<!-- <discord></discord>-->
<!-- </v-menu>-->
</template>
<gh-btns-star
icon="mark-github" slug="nocodb/nocodb" show-count class="mr-1 align-self-center"></gh-btns-star>
<!-- <github-button v-show="$store.state.isLoaded" class="mt-2 mr-md-2" href="https://github.com/nocodb/nocodb"--> <!-- <github-button v-show="$store.state.isLoaded" class="mt-2 mr-md-2" href="https://github.com/nocodb/nocodb"-->
<!-- data-color-scheme="no-preference: light; light: light; dark: light;" data-size="large"--> <!-- data-color-scheme="no-preference: light; light: light; dark: light;" data-size="large"-->
<!-- data-show-count="true" aria-label="Star nocodb/nocodb on GitHub">Star--> <!-- data-show-count="true" aria-label="Star nocodb/nocodb on GitHub">Star-->
@ -679,7 +677,8 @@
<v-list dense> <v-list dense>
<v-list-item dense v-if="!user && !isThisMobile" to="/user/authentication/signup"> <v-list-item dense v-if="!user && !isThisMobile" to="/user/authentication/signup">
<v-list-item-title> <v-list-item-title>
<v-icon small>mdi-account-plus-outline</v-icon> &nbsp; <span class="font-weight-regular">Sign Up</span> <v-icon small>mdi-account-plus-outline</v-icon> &nbsp; <span
class="font-weight-regular">Sign Up</span>
</v-list-item-title> </v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item dense v-if="!user && !isThisMobile" to="/user/authentication/signin"> <v-list-item dense v-if="!user && !isThisMobile" to="/user/authentication/signin">
@ -885,7 +884,8 @@ export default {
snackbar: false, snackbar: false,
timeout: 10000, timeout: 10000,
text: 'contact@senseprofit.com', text: 'contact@senseprofit.com',
rolesList: null rolesList: null,
}), }),
computed: { computed: {
...mapGetters({ ...mapGetters({
@ -1193,7 +1193,9 @@ export default {
item._nodes.type = 'roles'; item._nodes.type = 'roles';
this.$store.dispatch("tabs/ActAddTab", item); this.$store.dispatch("tabs/ActAddTab", item);
} }
setTimeout(() => {
this.$eventBus.$emit('show-add-user');
}, 200)
}, },
settingsTabAdd() { settingsTabAdd() {
const tabIndex = this.tabs.findIndex(el => el.key === `projectSettings`); const tabIndex = this.tabs.findIndex(el => el.key === `projectSettings`);

1
packages/nc-gui/nuxt.config.js

@ -55,6 +55,7 @@ export default {
"@/plugins/vueClipboard", "@/plugins/vueClipboard",
"@/plugins/globalComponentLoader", "@/plugins/globalComponentLoader",
"@/plugins/globalMixin", "@/plugins/globalMixin",
"@/plugins/globalEventBus",
"~/plugins/i18n.js", "~/plugins/i18n.js",
{src: '~plugins/projectLoader.js', ssr: false} {src: '~plugins/projectLoader.js', ssr: false}
], ],

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

@ -508,7 +508,7 @@
<v-list-item <v-list-item
dense dense
target="_blank" target="_blank"
href="https://calendly.com/xgenecloud/demo" href="https://calendly.com/nocodb"
> >
<v-list-item-icon> <v-list-item-icon>
<v-icon class="ml-2" :color="textColors[3]" <v-icon class="ml-2" :color="textColors[3]"

9
packages/nc-gui/plugins/globalEventBus.js

@ -0,0 +1,9 @@
import Vue from 'vue';
const GlobalPlugins = {
install(v) {
v.prototype.$eventBus = new Vue();
},
};
Vue.use(GlobalPlugins);

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

@ -22,6 +22,7 @@
"projects.show_community_message_1_2": "us on Github", "projects.show_community_message_1_2": "us on Github",
"projects.show_community_message_2": "Book a Free DEMO", "projects.show_community_message_2": "Book a Free DEMO",
"projects.show_community_message_3": "Get your questions answered", "projects.show_community_message_3": "Get your questions answered",
"projects.show_community_message_3_short": "Join Discord",
"projects.show_community_message_4": "Follow NocoDB", "projects.show_community_message_4": "Follow NocoDB",
"projects.search.no_result": "Your search for {search} found no results", "projects.search.no_result": "Your search for {search} found no results",
"projects.ext_db.title.edit": "Edit Project", "projects.ext_db.title.edit": "Edit Project",

1
packages/nc-gui/static/lang/fr.json

@ -22,6 +22,7 @@
"projects.show_community_message_1_2": "sur Github", "projects.show_community_message_1_2": "sur Github",
"projects.show_community_message_2": "Planifier une démo gratuite", "projects.show_community_message_2": "Planifier une démo gratuite",
"projects.show_community_message_3": "Obtenir des réponses à vos questions", "projects.show_community_message_3": "Obtenir des réponses à vos questions",
"projects.show_community_message_3_short": "Join Discord",
"projects.show_community_message_4": "Suivre NocoDB", "projects.show_community_message_4": "Suivre NocoDB",
"projects.search.no_result": "Votre recherche pour {search} n'a renvoyée aucun résultat", "projects.search.no_result": "Votre recherche pour {search} n'a renvoyée aucun résultat",
"projects.ext_db.title.edit": "Editer le projet", "projects.ext_db.title.edit": "Editer le projet",

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

@ -22,6 +22,7 @@
"projects.show_community_message_1_2": "us on Github", "projects.show_community_message_1_2": "us on Github",
"projects.show_community_message_2": "Book a Free DEMO", "projects.show_community_message_2": "Book a Free DEMO",
"projects.show_community_message_3": "Get your questions answered", "projects.show_community_message_3": "Get your questions answered",
"projects.show_community_message_3_short": "Join Discord",
"projects.show_community_message_4": "Follow NocoDB", "projects.show_community_message_4": "Follow NocoDB",
"projects.search.no_result": "Your search for {search} found no results", "projects.search.no_result": "Your search for {search} found no results",
"projects.ext_db.title.edit": "Edit Project", "projects.ext_db.title.edit": "Edit Project",

2
packages/nc-gui/static/lang/zh.json

@ -22,6 +22,7 @@
"projects.show_community_message_1_2": "us on Github", "projects.show_community_message_1_2": "us on Github",
"projects.show_community_message_2": "Book a Free DEMO", "projects.show_community_message_2": "Book a Free DEMO",
"projects.show_community_message_3": "Get your questions answered", "projects.show_community_message_3": "Get your questions answered",
"projects.show_community_message_3_short": "Join Discord",
"projects.show_community_message_4": "Follow NocoDB", "projects.show_community_message_4": "Follow NocoDB",
"projects.search.no_result": "Your search for {search} found no results", "projects.search.no_result": "Your search for {search} found no results",
"projects.ext_db.title.edit": "Edit Project", "projects.ext_db.title.edit": "Edit Project",
@ -128,4 +129,3 @@
"management.meta.operation_4.desc": "Import project meta zip file and restart.", "management.meta.operation_4.desc": "Import project meta zip file and restart.",
"management.meta.operation_5.desc": "Clear all metadata from meta tables." "management.meta.operation_5.desc": "Clear all metadata from meta tables."
} }

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

@ -1,6 +1,6 @@
{ {
"name": "nc-lib-gui", "name": "nc-lib-gui",
"version": "0.2.3", "version": "0.2.4",
"description": "> TODO: description", "description": "> TODO: description",
"author": "“pranavxc” <pranavxc@gmail.com>", "author": "“pranavxc” <pranavxc@gmail.com>",
"homepage": "https://gitlab.com/xgenecloud-ts/xgenecloud-ts#readme", "homepage": "https://gitlab.com/xgenecloud-ts/xgenecloud-ts#readme",

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

@ -11,15 +11,17 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
protected async addAdmin(req, res, next): Promise<any> { protected async addAdmin(req, res, next): Promise<any> {
// if (!this.config?.mailer || !this.emailClient) { const emails = (req.body.email || '').split(/\s*,\s*/).map(v => v.trim());
// return next(new Error('SMTP config is not found'));
// }
const email = req.body.email; // check for invalid emails
const invalidEmails = emails.filter(v => !validator.isEmail(v))
if (!email || !validator.isEmail(email)) { if (!emails.length) {
return next(new Error('Invalid email address')); return next(new Error('Invalid email address'));
} }
if (invalidEmails.length) {
return next(new Error('Invalid email address : ' + invalidEmails.join(', ')));
}
// todo: handle roles which contains super // todo: handle roles which contains super
if (!req.session?.passport?.user?.roles?.owner && req.body.roles.indexOf('owner') > -1) { if (!req.session?.passport?.user?.roles?.owner && req.body.roles.indexOf('owner') > -1) {
@ -27,14 +29,25 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
} }
const invite_token = uuidv4(); const invite_token = uuidv4();
const error = [];
for (const email of emails) {
// add user to project if user already exist
const user = await this.users.where({email}).first(); const user = await this.users.where({email}).first();
if (user) { if (user) {
if (!await this.xcMeta.isUserHaveAccessToProject(req.body.project_id, user.id)) { if (!await this.xcMeta.isUserHaveAccessToProject(req.body.project_id, user.id)) {
await this.xcMeta.projectAddUser(req.body.project_id, user.id, 'editor'); await this.xcMeta.projectAddUser(req.body.project_id, user.id, 'editor');
} }
this.xcMeta.audit(req.body.project_id, null, 'nc_audit', {
op_type: 'AUTHENTICATION',
op_sub_type: 'INVITE',
user: req.user.email,
description: `invited ${email} to ${req.body.project_id} project `, ip: req.clientIp
})
} else { } else {
try { try {
// create new user with invite token
await this.users.insert({ await this.users.insert({
invite_token, invite_token,
invite_token_expires: new Date(Date.now() + (24 * 60 * 60 * 1000)), invite_token_expires: new Date(Date.now() + (24 * 60 * 60 * 1000)),
@ -43,18 +56,9 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
}); });
const {id} = await this.users.where({email}).first(); const {id} = await this.users.where({email}).first();
// add user to project
await this.xcMeta.projectAddUser(req.body.project_id, id, req.body.roles); await this.xcMeta.projectAddUser(req.body.project_id, id, req.body.roles);
if (!await this.sendInviteEmail(email, invite_token, req)) {
res.json({invite_token, email})
}
} catch (e) {
return next(e);
}
}
Tele.emit('evt', {evt_type: 'project:invite'}) Tele.emit('evt', {evt_type: 'project:invite'})
this.xcMeta.audit(req.body.project_id, null, 'nc_audit', { this.xcMeta.audit(req.body.project_id, null, 'nc_audit', {
op_type: 'AUTHENTICATION', op_type: 'AUTHENTICATION',
@ -62,10 +66,31 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
user: req.user.email, user: req.user.email,
description: `invited ${email} to ${req.body.project_id} project `, ip: req.clientIp description: `invited ${email} to ${req.body.project_id} project `, ip: req.clientIp
}) })
// in case of single user check for smtp failure
// and send back token if failed
if (emails.length === 1 && !await this.sendInviteEmail(email, invite_token, req)) {
return res.json({invite_token, email});
} else {
this.sendInviteEmail(email, invite_token, req)
}
} catch (e) {
if (emails.length === 1) {
return next(e);
} else {
error.push({email, error: e.message})
}
}
}
}
if (emails.length === 1) {
res.json({ res.json({
msg: 'success' msg: 'success'
}) })
} else {
return res.json({invite_token, emails, error});
}
} }
protected async updateAdmin(req, res, next): Promise<any> { protected async updateAdmin(req, res, next): Promise<any> {

Loading…
Cancel
Save