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. 37
      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>
<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%">
<h4 class="text-center text-capitalize mt-2 d-100 display-1">
<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
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>
<template v-else>
<v-form v-model="valid" @submit.prevent="saveUser">
@ -403,6 +406,8 @@
v-model="selectedUser.email"
:rules="emailRules"
@input="edited=true"
validate-on-blur
hint="You can add multiple comma(,) separated emails"
label="Email"></v-text-field>
</v-col>
<v-col cols="12">
@ -476,14 +481,21 @@ export default {
valid: null,
emailRules: [
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: []
}),
async created() {
this.$eventBus.$on('show-add-user', this.addUser);
await this.loadUsers();
await this.loadRoles();
},
beforeDestroy() {
this.$eventBus.$off('show-add-user', this.addUser);
},
methods: {
simpleAnim() {
var count = 30;
@ -619,7 +631,7 @@ export default {
addUser() {
this.invite_token = null;
this.selectedUser = {
roles: 'creator,editor'
roles: 'editor'
}
this.userEditDialog = true
},
@ -733,7 +745,7 @@ export default {
this.selectedUser = this.users[i];
}
}
}
},
}
</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>

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

@ -93,7 +93,8 @@
</x-icon>
</template>
<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-group>
@ -256,9 +257,13 @@
v-if="time - $store.state.windows.miniSponsorCard > 15 * 60 * 1000"
>
<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 class="text-center">
<v-hover >
@ -338,28 +343,6 @@
</v-list>
</v-menu>
</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>
<template v-slot:activator="{ on }">
@ -461,11 +444,11 @@
<script>
import CreateViewDialog from '@/components/project/spreadsheet/dialog/createViewDialog';
import SponsorMini from '@/components/sponsorMini';
import Extras from "~/components/project/spreadsheet/components/extras";
export default {
name: 'spreadsheetNavDrawer',
components: { SponsorMini, CreateViewDialog },
components: {Extras, CreateViewDialog},
props: {
showAdvanceOptions: Boolean,
hideViews: Boolean,

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

@ -34,6 +34,35 @@
</template>
</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>
<!-- <gh-btns-star-->
@ -51,7 +80,7 @@
</x-icon>
</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">
mdi-video-outline
</x-icon>
@ -59,7 +88,7 @@
<!-- <x-btn btn.class=" mr-4" small text-->
<!-- 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-->
<!-- color="">-->
@ -78,9 +107,6 @@
mdi-folder-outline
</v-icon>
{{ $store.getters['project/GtrProjectName'] }}
<!-- <v-icon small color="grey lighten-2">
mdi-menu-down
</v-icon>-->
<v-tooltip bottom>
<template v-slot:activator="{on}">
<v-icon style="pointer-events:all" x-small v-on="on" color="grey lighten-2">
@ -91,23 +117,20 @@
</v-tooltip>
</h5>
</div>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-sm-and-down">
<v-spacer></v-spacer>
<!-- <x-icon-->
<!-- :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>-->
<v-toolbar-items class="hidden-sm-and-down">
<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"-->
<!-- @click="codeGenerateMvc()"-->
@ -137,31 +160,6 @@
<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"-->
<!-- 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-->
@ -679,7 +677,8 @@
<v-list dense>
<v-list-item dense v-if="!user && !isThisMobile" to="/user/authentication/signup">
<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>
<v-list-item dense v-if="!user && !isThisMobile" to="/user/authentication/signin">
@ -885,7 +884,8 @@ export default {
snackbar: false,
timeout: 10000,
text: 'contact@senseprofit.com',
rolesList: null
rolesList: null,
}),
computed: {
...mapGetters({
@ -1193,7 +1193,9 @@ export default {
item._nodes.type = 'roles';
this.$store.dispatch("tabs/ActAddTab", item);
}
setTimeout(() => {
this.$eventBus.$emit('show-add-user');
}, 200)
},
settingsTabAdd() {
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/globalComponentLoader",
"@/plugins/globalMixin",
"@/plugins/globalEventBus",
"~/plugins/i18n.js",
{src: '~plugins/projectLoader.js', ssr: false}
],

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

@ -508,7 +508,7 @@
<v-list-item
dense
target="_blank"
href="https://calendly.com/xgenecloud/demo"
href="https://calendly.com/nocodb"
>
<v-list-item-icon>
<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_2": "Book a Free DEMO",
"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.search.no_result": "Your search for {search} found no results",
"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_2": "Planifier une démo gratuite",
"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.search.no_result": "Votre recherche pour {search} n'a renvoyée aucun résultat",
"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_2": "Book a Free DEMO",
"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.search.no_result": "Your search for {search} found no results",
"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_2": "Book a Free DEMO",
"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.search.no_result": "Your search for {search} found no results",
"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_5.desc": "Clear all metadata from meta tables."
}

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

@ -1,6 +1,6 @@
{
"name": "nc-lib-gui",
"version": "0.2.3",
"version": "0.2.4",
"description": "> TODO: description",
"author": "“pranavxc” <pranavxc@gmail.com>",
"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> {
// if (!this.config?.mailer || !this.emailClient) {
// return next(new Error('SMTP config is not found'));
// }
const emails = (req.body.email || '').split(/\s*,\s*/).map(v => v.trim());
const email = req.body.email;
if (!email || !validator.isEmail(email)) {
// check for invalid emails
const invalidEmails = emails.filter(v => !validator.isEmail(v))
if (!emails.length) {
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
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 error = [];
for (const email of emails) {
// add user to project if user already exist
const user = await this.users.where({email}).first();
if (user) {
if (!await this.xcMeta.isUserHaveAccessToProject(req.body.project_id, user.id)) {
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 {
try {
// create new user with invite token
await this.users.insert({
invite_token,
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();
// add user to project
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'})
this.xcMeta.audit(req.body.project_id, null, 'nc_audit', {
op_type: 'AUTHENTICATION',
@ -62,10 +66,31 @@ export default class RestAuthCtrlEE extends RestAuthCtrl {
user: req.user.email,
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({
msg: 'success'
})
} else {
return res.json({invite_token, emails, error});
}
}
protected async updateAdmin(req, res, next): Promise<any> {

Loading…
Cancel
Save