Commit 6cca2938 authored by Angelina Filippova's avatar Angelina Filippova Committed by feld

Allow to register new user accounts

parent 3a8a032e
export const users = [
export let users = [
{ active: true, deactivated: false, id: '2', nickname: 'allis', local: true, external: false, roles: { admin: true, moderator: false }, tags: [] },
{ active: true, deactivated: false, id: '10', nickname: 'bob', local: false, external: true, roles: { admin: false, moderator: false }, tags: ['sandbox'] },
{ active: false, deactivated: true, id: 'abc', nickname: 'john', local: true, external: false, roles: { admin: false, moderator: false }, tags: ['strip_media'] }
......@@ -69,3 +69,9 @@ export async function tagUser(nickname, tag, authHost, token) {
export async function untagUser(nickname, tag, authHost, token) {
return Promise.resolve()
}
export async function createNewAccount(nickname, email, password, authHost, token) {
const newUser = { active: true, deactivated: false, id: '15', nickname, local: true, external: false, roles: { admin: false, moderator: false }, tags: [] }
users = [...users, newUser]
return Promise.resolve()
}
......@@ -2,56 +2,57 @@ import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function fetchUsers(filters, authHost, token, page = 1) {
export async function addRight(nickname, right, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users?page=${page}&filters=${filters}`,
method: 'get',
url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
method: 'post',
headers: authHeaders(token)
})
}
export async function toggleUserActivation(nickname, authHost, token) {
export async function createNewAccount(nickname, email, password, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users/${nickname}/toggle_activation`,
method: 'patch',
headers: authHeaders(token)
url: '/api/pleroma/admin/users',
method: 'post',
headers: authHeaders(token),
data: { nickname, email, password }
})
}
export async function searchUsers(query, filters, authHost, token, page = 1) {
export async function deleteRight(nickname, right, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users?query=${query}&page=${page}&filters=${filters}`,
method: 'get',
url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
method: 'delete',
headers: authHeaders(token)
})
}
export async function addRight(nickname, right, authHost, token) {
export async function deleteUser(nickname, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
method: 'post',
url: `/api/pleroma/admin/users?nickname=${nickname}`,
method: 'delete',
headers: authHeaders(token)
})
}
export async function deleteRight(nickname, right, authHost, token) {
export async function fetchUsers(filters, authHost, token, page = 1) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users/${nickname}/permission_group/${right}`,
method: 'delete',
url: `/api/pleroma/admin/users?page=${page}&filters=${filters}`,
method: 'get',
headers: authHeaders(token)
})
}
export async function deleteUser(nickname, authHost, token) {
export async function searchUsers(query, filters, authHost, token, page = 1) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users?nickname=${nickname}`,
method: 'delete',
url: `/api/pleroma/admin/users?query=${query}&page=${page}&filters=${filters}`,
method: 'get',
headers: authHeaders(token)
})
}
......@@ -66,6 +67,15 @@ export async function tagUser(nicknames, tags, authHost, token) {
})
}
export async function toggleUserActivation(nickname, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/users/${nickname}/toggle_activation`,
method: 'patch',
headers: authHeaders(token)
})
}
export async function untagUser(nicknames, tags, authHost, token) {
return await request({
baseURL: baseName(authHost),
......
......@@ -182,16 +182,46 @@ export default {
revokeModerator: 'Revoke Moderator',
grantModerator: 'Grant Moderator',
activateAccount: 'Activate Account',
activateAccounts: 'Activate Accounts',
deactivateAccount: 'Deactivate Account',
deactivateAccounts: 'Deactivate Accounts',
deleteAccount: 'Delete Account',
deleteAccounts: 'Delete Accounts',
forceNsfw: 'Force posts to be NSFW',
stripMedia: 'Force posts not to have media',
forceUnlisted: 'Force posts to be unlisted',
sandbox: 'Force posts to be followers-only',
disableRemoteSubscription: 'Disallow following user from remote instances',
disableRemoteSubscriptionForMultiple: 'Disallow following users from remote instances',
disableAnySubscription: 'Disallow following user at all',
disableAnySubscriptionForMultiple: 'Disallow following users at all',
selectUsers: 'Select users to apply actions to multiple users',
moderateUsers: 'Moderate multiple users'
moderateUsers: 'Moderate multiple users',
createAccount: 'Create new user account',
apply: 'apply',
remove: 'remove',
grantRightConfirmation: 'Are you sure you want to grant {right} rights to all selected users?',
revokeRightConfirmation: 'Are you sure you want to revoke {right} rights from all selected users?',
activateMultipleUsersConfirmation: 'Are you sure you want to activate accounts of all selected users?',
deactivateMultipleUsersConfirmation: 'Are you sure you want to deactivate accounts of all selected users?',
deleteMultipleUsersConfirmation: 'Are you sure you want to delete accounts of all selected users?',
addTagForMultipleUsersConfirmation: 'Are you sure you want to apply tag to all selected users?',
removeTagFromMultipleUsersConfirmation: 'Are you sure you want to remove tag from all selected users?',
ok: 'Okay',
completed: 'Completed',
cancel: 'Cancel',
canceled: 'Canceled',
username: 'Username',
email: 'E-mail',
password: 'Password',
create: 'Create',
submitFormError: 'There are errors on the form. Please fix them before continuing.',
emptyEmailError: 'Please input the e-mail',
invalidEmailError: 'Please input valid e-mail',
emptyPasswordError: 'Please input the password',
emptyNicknameError: 'Please input the username',
invalidNicknameError: 'Username can include "a-z", "A-Z" and "0-9" characters'
},
usersFilter: {
inputPlaceholder: 'Select filter',
......
import { addRight, fetchUsers, deleteRight, deleteUser, searchUsers, tagUser, toggleUserActivation, untagUser } from '@/api/users'
import { addRight, createNewAccount, fetchUsers, deleteRight, deleteUser, searchUsers, tagUser, toggleUserActivation, untagUser } from '@/api/users'
const users = {
state: {
......@@ -61,6 +61,10 @@ const users = {
commit('CLEAR_USERS_FILTERS')
dispatch('SearchUsers', { query: state.searchQuery, page: 1 })
},
async CreateNewAccount({ dispatch, getters, state }, { nickname, email, password }) {
await createNewAccount(nickname, email, password, getters.authHost, getters.token)
dispatch('FetchUsers', { page: state.currentPage })
},
async DeleteUser({ commit, getters }, user) {
await deleteUser(user.nickname, getters.authHost, getters.token)
const updatedUser = { ...user, deactivated: true }
......@@ -83,7 +87,7 @@ const users = {
async SearchUsers({ commit, dispatch, state, getters }, { query, page }) {
if (query.length === 0) {
commit('SET_SEARCH_QUERY', query)
dispatch('FetchUsers', page)
dispatch('FetchUsers', { page })
} else {
commit('SET_LOADING', true)
commit('SET_SEARCH_QUERY', query)
......
......@@ -29,22 +29,26 @@
<el-dropdown-item
divided
@click.native="activateMultipleUsers">
{{ $t('users.activateAccount') }}
{{ $t('users.activateAccounts') }}
</el-dropdown-item>
<el-dropdown-item
@click.native="deactivateMultipleUsers">
{{ $t('users.deactivateAccount') }}
{{ $t('users.deactivateAccounts') }}
</el-dropdown-item>
<el-dropdown-item
@click.native="deleteMultipleUsers">
{{ $t('users.deleteAccount') }}
{{ $t('users.deleteAccounts') }}
</el-dropdown-item>
<el-dropdown-item divided class="no-hover">
<div class="tag-container">
<span class="tag-text">{{ $t('users.forceNsfw') }}</span>
<el-button-group class="tag-button-group">
<el-button size="mini" @click.native="addTagForMultipleUsers('force_nsfw')">apply</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('force_nsfw')">remove</el-button>
<el-button size="mini" @click.native="addTagForMultipleUsers('force_nsfw')">
{{ $t('users.apply') }}
</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('force_nsfw')">
{{ $t('users.remove') }}
</el-button>
</el-button-group>
</div>
</el-dropdown-item>
......@@ -52,8 +56,12 @@
<div class="tag-container">
<span class="tag-text">{{ $t('users.stripMedia') }}</span>
<el-button-group class="tag-button-group">
<el-button size="mini" @click.native="addTagForMultipleUsers('strip_media')">apply</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('strip_media')">remove</el-button>
<el-button size="mini" @click.native="addTagForMultipleUsers('strip_media')">
{{ $t('users.apply') }}
</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('strip_media')">
{{ $t('users.remove') }}
</el-button>
</el-button-group>
</div>
</el-dropdown-item>
......@@ -61,8 +69,12 @@
<div class="tag-container">
<span class="tag-text">{{ $t('users.forceUnlisted') }}</span>
<el-button-group class="tag-button-group">
<el-button size="mini" @click.native="addTagForMultipleUsers('force_unlisted')">apply</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('force_unlisted')">remove</el-button>
<el-button size="mini" @click.native="addTagForMultipleUsers('force_unlisted')">
{{ $t('users.apply') }}
</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('force_unlisted')">
{{ $t('users.remove') }}
</el-button>
</el-button-group>
</div>
</el-dropdown-item>
......@@ -70,26 +82,38 @@
<div class="tag-container">
<span class="tag-text">{{ $t('users.sandbox') }}</span>
<el-button-group class="tag-button-group">
<el-button size="mini" @click.native="addTagForMultipleUsers('sandbox')">apply</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('sandbox')">remove</el-button>
<el-button size="mini" @click.native="addTagForMultipleUsers('sandbox')">
{{ $t('users.apply') }}
</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('sandbox')">
{{ $t('users.remove') }}
</el-button>
</el-button-group>
</div>
</el-dropdown-item>
<el-dropdown-item class="no-hover">
<div class="tag-container">
<span class="tag-text">{{ $t('users.disableRemoteSubscription') }}</span>
<span class="tag-text">{{ $t('users.disableRemoteSubscriptionForMultiple') }}</span>
<el-button-group class="tag-button-group">
<el-button size="mini" @click.native="addTagForMultipleUsers('disable_remote_subscription')">apply</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('disable_remote_subscription')">remove</el-button>
<el-button size="mini" @click.native="addTagForMultipleUsers('disable_remote_subscription')">
{{ $t('users.apply') }}
</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('disable_remote_subscription')">
{{ $t('users.remove') }}
</el-button>
</el-button-group>
</div>
</el-dropdown-item>
<el-dropdown-item class="no-hover">
<div class="tag-container">
<span class="tag-text">{{ $t('users.disableAnySubscription') }}</span>
<span class="tag-text">{{ $t('users.disableAnySubscriptionForMultiple') }}</span>
<el-button-group class="tag-button-group">
<el-button size="mini" @click.native="addTagForMultipleUsers('disable_any_subscription')">apply</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('disable_any_subscription')">remove</el-button>
<el-button size="mini" @click.native="addTagForMultipleUsers('disable_any_subscription')">
{{ $t('users.apply') }}
</el-button>
<el-button size="mini" @click.native="removeTagFromMultipleUsers('disable_any_subscription')">
{{ $t('users.remove') }}
</el-button>
</el-button-group>
</div>
</el-dropdown-item>
......@@ -157,68 +181,68 @@ export default {
grantRightToMultipleUsers(right) {
const { grantRight } = this.mappers()
this.confirmMessage(
`Are you sure you want to grant ${right} rights to all selected users?`,
this.$t('users.grantRightConfirmation', { right }),
grantRight(right)
)
},
revokeRightFromMultipleUsers(right) {
const { revokeRight } = this.mappers()
this.confirmMessage(
`Are you sure you want to revoke ${right} rights from all selected users?`,
this.$t('users.revokeRightConfirmation', { right }),
revokeRight(right)
)
},
activateMultipleUsers() {
const { activate } = this.mappers()
this.confirmMessage(
'Are you sure you want to activate accounts of all selected users?',
this.$t('users.activateMultipleUsersConfirmation'),
activate
)
},
deactivateMultipleUsers() {
const { deactivate } = this.mappers()
this.confirmMessage(
'Are you sure you want to deactivate accounts of all selected users?',
this.$t('users.deactivateMultipleUsersConfirmation'),
deactivate
)
},
deleteMultipleUsers() {
const { remove } = this.mappers()
this.confirmMessage(
'Are you sure you want to delete accounts of all selected users?',
this.$t('users.deleteMultipleUsersConfirmation'),
remove
)
},
addTagForMultipleUsers(tag) {
const { addTag } = this.mappers()
this.confirmMessage(
'Are you sure you want to apply tag to all selected users?',
this.$t('users.addTagForMultipleUsersConfirmation'),
addTag(tag)
)
},
removeTagFromMultipleUsers(tag) {
const { removeTag } = this.mappers()
this.confirmMessage(
'Are you sure you want to remove tag from all selected users?',
this.$t('users.removeTagFromMultipleUsersConfirmation'),
removeTag(tag)
)
},
confirmMessage(message, applyAction) {
this.$confirm(message, {
confirmButtonText: 'OK',
cancelButtonText: 'Cancel',
confirmButtonText: this.$t('users.ok'),
cancelButtonText: this.$t('users.cancel'),
type: 'warning'
}).then(() => {
applyAction()
this.$emit('apply-action')
this.$message({
type: 'success',
message: 'Completed'
message: this.$t('users.completed')
})
}).catch(() => {
this.$message({
type: 'info',
message: 'Canceled'
message: this.$t('users.canceled')
})
})
}
......@@ -229,9 +253,8 @@ export default {
<style rel='stylesheet/scss' lang='scss' scoped>
.actions-button {
text-align: left;
margin: 0 15px 10px 0;
width: 350px;
padding: 10px 15px;
padding: 10px;
}
.actions-button-container {
display: flex;
......@@ -240,8 +263,8 @@ export default {
.el-dropdown {
float: right;
}
.el-dropdown-menu {
margin-right: 15px;
.el-icon-edit {
margin-right: 5px;
}
.tag-container {
display: flex;
......
<template>
<el-dialog
:visible.sync="isVisible"
:show-close="false"
:title="$t('users.createAccount')"
custom-class="create-user-dialog"
@open="resetForm">
<el-form ref="form" :model="form" :rules="rules" :label-width="getLabelWidth" status-icon>
<el-form-item :label="$t('users.username')" prop="nickname" class="create-account-form-item">
<el-input v-model="form.nickname" name="nickname" autofocus/>
</el-form-item>
<el-form-item :label="$t('users.email')" prop="email" class="create-account-form-item">
<el-input v-model="form.email" name="email" type="email"/>
</el-form-item>
<el-form-item :label="$t('users.password')" prop="password" class="create-account-form-item">
<el-input v-model="form.password" type="password" name="password" autocomplete="off"/>
</el-form-item>
</el-form>
<span slot="footer">
<el-button @click="closeDialogWindow">{{ $t('users.cancel') }}</el-button>
<el-button type="primary" @click="submitForm('form')">{{ $t('users.create') }}</el-button>
</span>
</el-dialog>
</template>
<script>
export default {
name: 'NewAccountDialog',
props: {
dialogFormVisible: {
type: Boolean,
default: function() {
return false
}
}
},
data() {
return {
form: {
nickname: '',
email: '',
password: ''
},
rules: {
nickname: [
{ validator: this.validateUsername, trigger: 'blur' }
],
email: [
{ validator: this.validateEmail, trigger: 'blur' }
],
password: [
{ validator: this.validatePassword, trigger: 'blur' }
]
}
}
},
computed: {
isDesktop() {
return this.$store.state.app.device === 'desktop'
},
isVisible: {
get() {
return this.$props.dialogFormVisible
},
set() {
this.closeDialogWindow()
}
},
getLabelWidth() {
return this.isDesktop ? '120px' : '80px'
}
},
methods: {
closeDialogWindow() {
this.$emit('closeWindow')
},
resetForm() {
this.$nextTick(() => {
this.$refs['form'].resetFields()
})
},
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$emit('createNewAccount', this.$data.form)
this.closeDialogWindow()
this.$message({
type: 'success',
message: this.$t('users.completed')
})
} else {
this.$message({
type: 'error',
message: this.$t('users.submitFormError')
})
return false
}
})
},
validateEmail(rule, value, callback) {
if (value === '') {
return callback(new Error(this.$t('users.emptyEmailError')))
} else if (!this.validEmail(value)) {
return callback(new Error(this.$t('users.invalidEmailError')))
} else {
return callback()
}
},
validatePassword(rule, value, callback) {
if (value === '') {
return callback(new Error(this.$t('users.emptyPasswordError')))
} else {
return callback()
}
},
validateUsername(rule, value, callback) {
if (value === '') {
return callback(new Error(this.$t('users.emptyNicknameError')))
} else if (!this.validNickname(value)) {
return callback(new Error(this.$t('users.invalidNicknameError')))
} else {
return callback()
}
},
validEmail(email) {
var re = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
return re.test(email)
},
validNickname(nickname) {
var re = /^[a-zA-Z\d]+$/
return re.test(nickname)
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@media
only screen and (max-width: 760px),
(min-device-width: 768px) and (max-device-width: 1024px) {
.create-user-dialog {
width: 80%
}
.create-account-form-item {
margin-bottom: 30px;
}
.el-dialog__body {
padding: 20px 20px 0 20px
}
}
</style>
......@@ -4,13 +4,25 @@
{{ $t('users.users') }}
<span class="user-count">({{ normalizedUsersCount }})</span>
</h1>
<div class="search-container">
<div class="filter-container">
<users-filter/>
<el-input :placeholder="$t('users.search')" v-model="search" class="search" @input="handleDebounceSearchInput"/>
</div>
<dropdown-actions-menu
<div class="actions-container">
<el-button class="actions-button create-account" @click="dialogFormVisible = true">
<span>
<i class="el-icon-plus" />
{{ $t('users.createAccount') }}
</span>
</el-button>
<multiple-users-menu
:selected-users="selectedUsers"
@apply-action="clearSelection"/>
</div>
<new-account-dialog
:dialog-form-visible="dialogFormVisible"
@createNewAccount="createNewAccount"
@closeWindow="dialogFormVisible = false"/>
<el-table
v-loading="loading"
ref="usersTable"
......@@ -140,22 +152,21 @@
import debounce from 'lodash.debounce'
import numeral from 'numeral'
import UsersFilter from './components/UsersFilter'
import DropdownActionsMenu from './components/DropdownActionsMenu'
import MultipleUsersMenu from './components/MultipleUsersMenu'
import NewAccountDialog from './components/NewAccountDialog'
export default {
name: 'Users',
components: {
UsersFilter,
DropdownActionsMenu
},
data: function() {
return {
selectedUsers: []
}
MultipleUsersMenu,
NewAccountDialog
},
data() {
return {
search: ''
search: '',
selectedUsers: [],
dialogFormVisible: false
}
},
data() {
......@@ -207,6 +218,9 @@ export default {
clearSelection() {
this.$refs.usersTable.clearSelection()
},
createNewAccount(accountData) {
this.$store.dispatch('CreateNewAccount', accountData)
},
getFirstLetter(str) {
return str.charAt(0).toUpperCase()
},
......@@ -246,6 +260,18 @@ export default {
</script>
<style rel='stylesheet/scss' lang='scss' scoped>
.actions-button {
text-align: left;
width: 350px;
padding: 10px;
}
.actions-container {