From 81510916b56c6fc66655e5a11b5f71c1139a2171 Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Mon, 23 Sep 2019 19:00:28 +0000 Subject: [PATCH] Generate invite tokens from admin-fe --- CHANGELOG.md | 13 +- src/api/__mocks__/invites.js | 35 ++ src/api/__mocks__/users.js | 4 + src/api/invites.js | 46 +++ src/api/users.js | 11 +- src/components/LangSelect/index.vue | 32 -- src/components/Screenfull/index.vue | 51 --- src/components/UploadExcel/index.vue | 136 -------- src/lang/en.js | 35 +- src/router/index.js | 21 +- src/store/index.js | 2 + src/store/modules/invites.js | 45 +++ src/store/modules/users.js | 21 +- src/views/guide/defineSteps.js | 8 - src/views/invites/index.vue | 328 ++++++++++++++++++ src/views/settings/components/ActivityPub.vue | 8 +- .../settings/components/Authentication.vue | 8 +- src/views/settings/components/AutoLinker.vue | 8 +- src/views/settings/components/Captcha.vue | 8 +- src/views/settings/components/Database.vue | 8 +- src/views/settings/components/Endpoint.vue | 8 +- src/views/settings/components/Esshd.vue | 8 +- src/views/settings/components/Frontend.vue | 8 +- src/views/settings/components/Gopher.vue | 8 +- src/views/settings/components/Http.vue | 8 +- src/views/settings/components/Instance.vue | 8 +- src/views/settings/components/JobQueue.vue | 8 +- src/views/settings/components/Logger.vue | 8 +- src/views/settings/components/MRF.vue | 8 +- src/views/settings/components/Mailer.vue | 8 +- src/views/settings/components/MediaProxy.vue | 8 +- src/views/settings/components/Metadata.vue | 8 +- src/views/settings/components/Other.vue | 8 +- .../settings/components/RateLimiters.vue | 8 +- src/views/settings/components/Upload.vue | 8 +- src/views/settings/components/WebPush.vue | 8 +- src/views/table/dynamicTable/fixedThead.vue | 58 ---- src/views/table/dynamicTable/index.vue | 20 -- src/views/table/dynamicTable/unfixedThead.vue | 46 --- src/views/table/treeTable/customEval.js | 48 --- src/views/table/treeTable/customTreeTable.vue | 138 -------- src/views/table/treeTable/treeTable.vue | 129 ------- .../users/components/MultipleUsersMenu.vue | 111 ++++-- .../users/components/NewAccountDialog.vue | 40 ++- src/views/users/index.vue | 72 +++- test/views/invites/index.test.js | 151 ++++++++ test/views/invites/store.conf.js | 13 + test/views/layout/store.conf.js | 1 - test/views/users/index.test.js | 27 ++ 49 files changed, 1030 insertions(+), 780 deletions(-) create mode 100644 src/api/__mocks__/invites.js create mode 100644 src/api/invites.js delete mode 100644 src/components/LangSelect/index.vue delete mode 100644 src/components/Screenfull/index.vue delete mode 100644 src/components/UploadExcel/index.vue create mode 100644 src/store/modules/invites.js create mode 100644 src/views/invites/index.vue delete mode 100644 src/views/table/dynamicTable/fixedThead.vue delete mode 100644 src/views/table/dynamicTable/index.vue delete mode 100644 src/views/table/dynamicTable/unfixedThead.vue delete mode 100644 src/views/table/treeTable/customEval.js delete mode 100644 src/views/table/treeTable/customTreeTable.vue delete mode 100644 src/views/table/treeTable/treeTable.vue create mode 100644 test/views/invites/index.test.js create mode 100644 test/views/invites/store.conf.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e40d47..75553acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,20 +14,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added -- add ability to configure new settings (UploadS3 bucket namespace, Rate limit for Activity pub routes, Email notifications settings, MRF Vocabulary, user bio and name length and others) -- add ability to disable certain features (settings/reports) -- add sign in via PleromaFE +- adds ability to configure new settings (UploadS3 bucket namespace, Rate limit for Activity pub routes, Email notifications settings, MRF Vocabulary, user bio and name length and others) +- adds ability to disable certain features (settings/reports/invites) +- adds sign in via PleromaFE +- adds ability to generate invite tokens and list them on a separate tab +- adds ability to invite users via email +- adds ability to reset users passwords +- adds tests for invites and resetting password ### Changed - removes "Dashboard" from dropdown menu - makes all single selects clearable and allow to enter custom values in all multiple selects -- remove legacy activitypub accept_blocks setting +- removes legacy activitypub accept_blocks setting ### Fixed - converts maps and structs to JS objects, not array of tuples when wrapping config - changes type of IP value from string to number +- updates error handling for users and invites modules ## [1.0.1] - 2019-08-15 diff --git a/src/api/__mocks__/invites.js b/src/api/__mocks__/invites.js new file mode 100644 index 00000000..44a774c7 --- /dev/null +++ b/src/api/__mocks__/invites.js @@ -0,0 +1,35 @@ +let inviteTokens = [ + { expires_at: '01-01-2020', id: 1, invite_type: 'one_time', max_use: 3, token: 'DCN8XyTsVEuz9_KuxPlkbH1RgMsMHepwmZE2gyX07Jw=', used: false, uses: 1 }, + { expires_at: '02-02-2020', id: 2, invite_type: 'one_time', max_use: 1, token: 'KnJTHNedj2Mh14ckx06t-VfOuFL8oNA0nVAK1HLeLf4=', used: true, uses: 1 }, + { expires_at: '03-03-2020', id: 3, invite_type: 'one_time', max_use: 5, token: 'P6F5ayP-rAMbxtmtGJwFJcd7Yk_D2g6UZRfh8EskRUc=', used: false, uses: 0 } +] + +export async function generateInviteToken(max_use, expires_at, authHost, token) { + const newToken = { + expires_at: '2019-04-10', + id: 4, + invite_type: 'one_time', + max_use: 3, + token: 'JYl0SjXW8t-t-pLSZBnZLf6PwjCW-qy6Dq70jfUOuqk=', + used: false, + uses: 0 + } + inviteTokens = [...inviteTokens, newToken] + return Promise.resolve({ data: newToken }) +} + +export async function inviteViaEmail(email, name, authHost, token) { + return Promise.resolve() +} + +export async function listInviteTokens(authHost, token) { + return Promise.resolve({ data: { + invites: inviteTokens + }}) +} + +export async function revokeToken(tokenToRevoke, authHost, token) { + inviteTokens.splice(3, 1, { ...inviteTokens[3], used: true }) + return Promise.resolve() +} + diff --git a/src/api/__mocks__/users.js b/src/api/__mocks__/users.js index 646a5dd3..016772a7 100644 --- a/src/api/__mocks__/users.js +++ b/src/api/__mocks__/users.js @@ -29,6 +29,10 @@ export async function fetchUsers(filters, authHost, token, page = 1) { }}) } +export async function getPasswordResetToken(nickname, authHost, token) { + return Promise.resolve({ data: { token: 'g05lxnBJQnL', link: 'http://url/api/pleroma/password_reset/g05lxnBJQnL' }}) +} + export async function toggleUserActivation(nickname, authHost, token) { const response = users.find(user => user.nickname === nickname) return Promise.resolve({ data: { ...response, deactivated: !response.deactivated }}) diff --git a/src/api/invites.js b/src/api/invites.js new file mode 100644 index 00000000..1d8371db --- /dev/null +++ b/src/api/invites.js @@ -0,0 +1,46 @@ +import request from '@/utils/request' +import { getToken } from '@/utils/auth' +import { baseName } from './utils' + +export async function generateInviteToken(max_use, expires_at, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users/invite_token`, + method: 'post', + headers: authHeaders(token), + data: expires_at && expires_at.length > 0 ? { max_use, expires_at } : { max_use } + }) +} + +export async function inviteViaEmail(email, name, authHost, token) { + const url = name.length > 0 + ? `/api/pleroma/admin/users/email_invite?email=${email}&name=${name}` + : `/api/pleroma/admin/users/email_invite?email=${email}` + return await request({ + baseURL: baseName(authHost), + url, + method: 'post', + headers: authHeaders(token) + }) +} + +export async function listInviteTokens(authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users/invites`, + method: 'get', + headers: authHeaders(token) + }) +} + +export async function revokeToken(tokenToRevoke, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users/revoke_invite`, + method: 'post', + headers: authHeaders(token), + data: { token: tokenToRevoke } + }) +} + +const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} diff --git a/src/api/users.js b/src/api/users.js index dd75045f..0f8e4a41 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -17,7 +17,7 @@ export async function createNewAccount(nickname, email, password, authHost, toke url: '/api/pleroma/admin/users', method: 'post', headers: authHeaders(token), - data: { nickname, email, password } + data: { users: [{ nickname, email, password }] } }) } @@ -57,6 +57,15 @@ export async function fetchUsers(filters, authHost, token, page = 1) { }) } +export async function getPasswordResetToken(nickname, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users/${nickname}/password_reset`, + method: 'get', + headers: authHeaders(token) + }) +} + export async function searchUsers(query, filters, authHost, token, page = 1) { return await request({ baseURL: baseName(authHost), diff --git a/src/components/LangSelect/index.vue b/src/components/LangSelect/index.vue deleted file mode 100644 index fea7ba71..00000000 --- a/src/components/LangSelect/index.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - diff --git a/src/components/Screenfull/index.vue b/src/components/Screenfull/index.vue deleted file mode 100644 index f3951229..00000000 --- a/src/components/Screenfull/index.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - - - diff --git a/src/components/UploadExcel/index.vue b/src/components/UploadExcel/index.vue deleted file mode 100644 index 1d07ebf3..00000000 --- a/src/components/UploadExcel/index.vue +++ /dev/null @@ -1,136 +0,0 @@ - - - - - diff --git a/src/lang/en.js b/src/lang/en.js index 0f1e4bbc..f2c6c137 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -202,7 +202,7 @@ export default { disableAnySubscriptionForMultiple: 'Disallow following users at all', selectUsers: 'Select users to apply actions to multiple users', moderateUsers: 'Moderate multiple users', - createAccount: 'Create new user account', + createAccount: 'Create new account', apply: 'apply', remove: 'remove', grantRightConfirmation: 'Are you sure you want to grant {right} rights to all selected users?', @@ -220,12 +220,15 @@ export default { email: 'E-mail', password: 'Password', create: 'Create', - submitFormError: 'There are errors on the form. Please fix them before continuing.', + submitFormError: 'There are invalid values in 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' + invalidNicknameError: 'Username can include "a-z", "A-Z" and "0-9" characters', + getPasswordResetToken: 'Get password reset token', + passwordResetTokenCreated: 'Password reset token was created', + accountCreated: 'New account was created!' }, userProfile: { tags: 'Tags', @@ -303,5 +306,31 @@ export default { database: 'Database', other: 'Other', success: 'Settings changed successfully!' + }, + invites: { + inviteTokens: 'Invite tokens', + createInviteToken: 'Generate invite token', + pickDate: 'Pick a date', + maxUse: 'Max use', + expiresAt: 'Expires at', + tokenCreated: 'Invite token was created', + token: 'Token', + uses: 'Uses', + used: 'Used', + cancel: 'Cancel', + create: 'Create', + revoke: 'Revoke', + id: 'ID', + actions: 'Actions', + active: 'Active', + inviteUserViaEmail: 'Invite user via email', + sendRegistration: 'Send registration invite via email', + email: 'Email', + name: 'Name', + emptyEmailError: 'Please input the e-mail', + invalidEmailError: 'Please input valid e-mail', + emailSent: 'Invite was sent', + submitFormError: 'There are invalid values in the form. Please fix them before continuing.', + inviteViaEmailAlert: 'To send invite via email make sure to enable `invites_enabled` and disable `registrations_open`' } } diff --git a/src/router/index.js b/src/router/index.js index 9070b14f..b0e0d750 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -16,7 +16,7 @@ const settings = { path: 'index', component: () => import('@/views/settings/index'), name: 'Settings', - meta: { title: 'settings', icon: 'settings', noCache: true } + meta: { title: 'Settings', icon: 'settings', noCache: true } } ] } @@ -30,7 +30,21 @@ const reports = { path: 'index', component: () => import('@/views/reports/index'), name: 'Reports', - meta: { title: 'reports', icon: 'documentation', noCache: true } + meta: { title: 'Reports', icon: 'documentation', noCache: true } + } + ] +} + +const invitesDisabled = disabledFeatures.includes('invites') +const invites = { + path: '/invites', + component: Layout, + children: [ + { + path: 'index', + component: () => import('@/views/invites/index'), + name: 'Invites', + meta: { title: 'Invites', icon: 'guide', noCache: true } } ] } @@ -108,12 +122,13 @@ export const asyncRouterMap = [ path: 'index', component: () => import('@/views/users/index'), name: 'Users', - meta: { title: 'users', icon: 'peoples', noCache: true } + meta: { title: 'Users', icon: 'peoples', noCache: true } } ] }, ...(settingsDisabled ? [] : [settings]), ...(reportsDisabled ? [] : [reports]), + ...(invitesDisabled ? [] : [invites]), ...(emojiPacksDisabled ? [] : [emojiPacks]), { path: '/users/:id', diff --git a/src/store/index.js b/src/store/index.js index c3d1eb78..830c0788 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -2,6 +2,7 @@ import Vue from 'vue' import Vuex from 'vuex' import app from './modules/app' import errorLog from './modules/errorLog' +import invites from './modules/invites' import permission from './modules/permission' import reports from './modules/reports' import settings from './modules/settings' @@ -18,6 +19,7 @@ const store = new Vuex.Store({ modules: { app, errorLog, + invites, permission, reports, settings, diff --git a/src/store/modules/invites.js b/src/store/modules/invites.js new file mode 100644 index 00000000..bdf43ce3 --- /dev/null +++ b/src/store/modules/invites.js @@ -0,0 +1,45 @@ +import { generateInviteToken, inviteViaEmail, listInviteTokens, revokeToken } from '@/api/invites' + +const invites = { + state: { + inviteTokens: [], + loading: false, + newToken: {} + }, + mutations: { + SET_LOADING: (state, status) => { + state.loading = status + }, + SET_NEW_TOKEN: (state, token) => { + state.newToken = token + }, + SET_TOKENS: (state, tokens) => { + state.inviteTokens = tokens + } + }, + actions: { + async FetchInviteTokens({ commit, getters }) { + commit('SET_LOADING', true) + const response = await listInviteTokens(getters.authHost, getters.token) + commit('SET_TOKENS', response.data.invites.reverse()) + commit('SET_LOADING', false) + }, + async GenerateInviteToken({ commit, dispatch, getters }, { maxUse, expiresAt }) { + const { data } = await generateInviteToken(maxUse, expiresAt, getters.authHost, getters.token) + commit('SET_NEW_TOKEN', { token: data.token, maxUse: data.max_use, expiresAt: data.expires_at }) + dispatch('FetchInviteTokens') + }, + async InviteUserViaEmail({ commit, dispatch, getters }, { email, name }) { + await inviteViaEmail(email, name, getters.authHost, getters.token) + }, + RemoveNewToken({ commit }) { + commit('SET_NEW_TOKEN', {}) + }, + async RevokeToken({ commit, dispatch, getters }, token) { + await revokeToken(token, getters.authHost, getters.token) + dispatch('FetchInviteTokens') + } + } +} + +export default invites diff --git a/src/store/modules/users.js b/src/store/modules/users.js index 0246a98d..f732c810 100644 --- a/src/store/modules/users.js +++ b/src/store/modules/users.js @@ -1,4 +1,4 @@ -import { addRight, createNewAccount, fetchUsers, deleteRight, deleteUser, searchUsers, tagUser, toggleUserActivation, untagUser } from '@/api/users' +import { addRight, createNewAccount, deleteRight, deleteUser, fetchUsers, getPasswordResetToken, searchUsers, tagUser, toggleUserActivation, untagUser } from '@/api/users' const users = { state: { @@ -12,6 +12,10 @@ const users = { external: false, active: false, deactivated: false + }, + passwordResetToken: { + token: '', + link: '' } }, mutations: { @@ -23,7 +27,9 @@ const users = { }, SWAP_USER: (state, updatedUser) => { const updated = state.fetchedUsers.map(user => user.id === updatedUser.id ? updatedUser : user) - state.fetchedUsers = updated.sort((a, b) => a.nickname.localeCompare(b.nickname)) + state.fetchedUsers = updated + .map(user => user.nickname ? user : { ...user, nickname: '' }) + .sort((a, b) => a.nickname.localeCompare(b.nickname)) }, SWAP_USERS: (state, users) => { const usersWithoutSwapped = users.reduce((acc, user) => { @@ -43,6 +49,10 @@ const users = { SET_PAGE_SIZE: (state, pageSize) => { state.pageSize = pageSize }, + SET_PASSWORD_RESET_TOKEN: (state, { token, link }) => { + state.passwordResetToken.token = token + state.passwordResetToken.link = link + }, SET_SEARCH_QUERY: (state, query) => { state.searchQuery = query }, @@ -79,6 +89,13 @@ const users = { const response = await fetchUsers(filters, getters.authHost, getters.token, page) loadUsers(commit, page, response.data) }, + async GetPasswordResetToken({ commit, state, getters }, nickname) { + const { data } = await getPasswordResetToken(nickname, getters.authHost, getters.token) + commit('SET_PASSWORD_RESET_TOKEN', data) + }, + RemovePasswordToken({ commit }) { + commit('SET_PASSWORD_RESET_TOKEN', { link: '', token: '' }) + }, async RemoveTag({ commit, getters }, { users, tag }) { const nicknames = users.map(user => user.nickname) await untagUser(nicknames, [tag], getters.authHost, getters.token) diff --git a/src/views/guide/defineSteps.js b/src/views/guide/defineSteps.js index 405a3f72..0c969e7f 100644 --- a/src/views/guide/defineSteps.js +++ b/src/views/guide/defineSteps.js @@ -15,14 +15,6 @@ const steps = [ position: 'bottom' } }, - { - element: '.screenfull', - popover: { - title: 'Screenfull', - description: 'Bring the page into fullscreen', - position: 'left' - } - }, { element: '.international-icon', popover: { diff --git a/src/views/invites/index.vue b/src/views/invites/index.vue new file mode 100644 index 00000000..783e897a --- /dev/null +++ b/src/views/invites/index.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/src/views/settings/components/ActivityPub.vue b/src/views/settings/components/ActivityPub.vue index cd14d1f9..903e34fe 100644 --- a/src/views/settings/components/ActivityPub.vue +++ b/src/views/settings/components/ActivityPub.vue @@ -57,8 +57,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Authentication.vue b/src/views/settings/components/Authentication.vue index 76ac2e55..a2084327 100644 --- a/src/views/settings/components/Authentication.vue +++ b/src/views/settings/components/Authentication.vue @@ -259,8 +259,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/AutoLinker.vue b/src/views/settings/components/AutoLinker.vue index 2c2f197b..e84f3394 100644 --- a/src/views/settings/components/AutoLinker.vue +++ b/src/views/settings/components/AutoLinker.vue @@ -107,8 +107,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Captcha.vue b/src/views/settings/components/Captcha.vue index afc3ae22..4bff5c0b 100644 --- a/src/views/settings/components/Captcha.vue +++ b/src/views/settings/components/Captcha.vue @@ -53,8 +53,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Database.vue b/src/views/settings/components/Database.vue index ec54d1c9..ab9d42a8 100644 --- a/src/views/settings/components/Database.vue +++ b/src/views/settings/components/Database.vue @@ -157,8 +157,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Endpoint.vue b/src/views/settings/components/Endpoint.vue index feb86c96..aa1f661a 100644 --- a/src/views/settings/components/Endpoint.vue +++ b/src/views/settings/components/Endpoint.vue @@ -246,8 +246,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Esshd.vue b/src/views/settings/components/Esshd.vue index 86af2ab7..512ca7a4 100644 --- a/src/views/settings/components/Esshd.vue +++ b/src/views/settings/components/Esshd.vue @@ -82,8 +82,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Frontend.vue b/src/views/settings/components/Frontend.vue index 46255836..eac9b478 100644 --- a/src/views/settings/components/Frontend.vue +++ b/src/views/settings/components/Frontend.vue @@ -419,8 +419,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Gopher.vue b/src/views/settings/components/Gopher.vue index 1b019232..b4f0b2d4 100644 --- a/src/views/settings/components/Gopher.vue +++ b/src/views/settings/components/Gopher.vue @@ -43,8 +43,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Http.vue b/src/views/settings/components/Http.vue index ec69890e..08e8da7c 100644 --- a/src/views/settings/components/Http.vue +++ b/src/views/settings/components/Http.vue @@ -161,8 +161,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Instance.vue b/src/views/settings/components/Instance.vue index 78c8c0a2..05539e13 100644 --- a/src/views/settings/components/Instance.vue +++ b/src/views/settings/components/Instance.vue @@ -396,8 +396,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/JobQueue.vue b/src/views/settings/components/JobQueue.vue index d06e9d3d..f18cc853 100644 --- a/src/views/settings/components/JobQueue.vue +++ b/src/views/settings/components/JobQueue.vue @@ -72,8 +72,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Logger.vue b/src/views/settings/components/Logger.vue index cd772271..a58c286c 100644 --- a/src/views/settings/components/Logger.vue +++ b/src/views/settings/components/Logger.vue @@ -212,8 +212,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/MRF.vue b/src/views/settings/components/MRF.vue index e4de0bb8..043f3b7d 100644 --- a/src/views/settings/components/MRF.vue +++ b/src/views/settings/components/MRF.vue @@ -257,8 +257,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Mailer.vue b/src/views/settings/components/Mailer.vue index 1b276324..87979815 100644 --- a/src/views/settings/components/Mailer.vue +++ b/src/views/settings/components/Mailer.vue @@ -249,8 +249,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/MediaProxy.vue b/src/views/settings/components/MediaProxy.vue index e50ba8de..0c0a73a8 100644 --- a/src/views/settings/components/MediaProxy.vue +++ b/src/views/settings/components/MediaProxy.vue @@ -126,8 +126,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Metadata.vue b/src/views/settings/components/Metadata.vue index 01af8c96..2f63ddd0 100644 --- a/src/views/settings/components/Metadata.vue +++ b/src/views/settings/components/Metadata.vue @@ -72,8 +72,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Other.vue b/src/views/settings/components/Other.vue index a872c732..99ab16ff 100644 --- a/src/views/settings/components/Other.vue +++ b/src/views/settings/components/Other.vue @@ -65,8 +65,12 @@ export default { }, {}) this.updateSetting(updatedValue, 'types', 'value') }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/RateLimiters.vue b/src/views/settings/components/RateLimiters.vue index b4afe0e0..491ad51b 100644 --- a/src/views/settings/components/RateLimiters.vue +++ b/src/views/settings/components/RateLimiters.vue @@ -379,8 +379,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/Upload.vue b/src/views/settings/components/Upload.vue index 1adbc923..776c9ed6 100644 --- a/src/views/settings/components/Upload.vue +++ b/src/views/settings/components/Upload.vue @@ -207,8 +207,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/settings/components/WebPush.vue b/src/views/settings/components/WebPush.vue index 8582e2d3..7dbe87ea 100644 --- a/src/views/settings/components/WebPush.vue +++ b/src/views/settings/components/WebPush.vue @@ -41,8 +41,12 @@ export default { updateSetting(value, tab, input) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) }, - onSubmit() { - this.$store.dispatch('SubmitChanges') + async onSubmit() { + try { + await this.$store.dispatch('SubmitChanges') + } catch (e) { + return + } this.$message({ type: 'success', message: i18n.t('settings.success') diff --git a/src/views/table/dynamicTable/fixedThead.vue b/src/views/table/dynamicTable/fixedThead.vue deleted file mode 100644 index ca1bac67..00000000 --- a/src/views/table/dynamicTable/fixedThead.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - - diff --git a/src/views/table/dynamicTable/index.vue b/src/views/table/dynamicTable/index.vue deleted file mode 100644 index 3c16bc46..00000000 --- a/src/views/table/dynamicTable/index.vue +++ /dev/null @@ -1,20 +0,0 @@ - - - - diff --git a/src/views/table/dynamicTable/unfixedThead.vue b/src/views/table/dynamicTable/unfixedThead.vue deleted file mode 100644 index f49369d9..00000000 --- a/src/views/table/dynamicTable/unfixedThead.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - diff --git a/src/views/table/treeTable/customEval.js b/src/views/table/treeTable/customEval.js deleted file mode 100644 index 73badb68..00000000 --- a/src/views/table/treeTable/customEval.js +++ /dev/null @@ -1,48 +0,0 @@ -/** -* @Author: jianglei -* @Date: 2017-10-12 12:06:49 -*/ -'use strict' -import Vue from 'vue' -export default function treeToArray(data, expandAll, parent, level, item) { - const marLTemp = [] - let tmp = [] - Array.from(data).forEach(function(record) { - if (record._expanded === undefined) { - Vue.set(record, '_expanded', expandAll) - } - let _level = 1 - if (level !== undefined && level !== null) { - _level = level + 1 - } - Vue.set(record, '_level', _level) - // 如果有父元素 - if (parent) { - Vue.set(record, 'parent', parent) - // 如果父元素有偏移量,需要计算在this的偏移量中 - // 偏移量还与前面同级元素有关,需要加上前面所有元素的长度和 - if (!marLTemp[_level]) { - marLTemp[_level] = 0 - } - Vue.set(record, '_marginLeft', marLTemp[_level] + parent._marginLeft) - Vue.set(record, '_width', record[item] / parent[item] * parent._width) - // 在本次计算过偏移量后加上自己长度,以供下一个元素使用 - marLTemp[_level] += record._width - } else { - // 如果为根 - // 初始化偏移量存储map - marLTemp[record.id] = [] - // map中是一个数组,存储的是每级的长度和 - // 初始情况下为0 - marLTemp[record.id][_level] = 0 - Vue.set(record, '_marginLeft', 0) - Vue.set(record, '_width', 1) - } - tmp.push(record) - if (record.children && record.children.length > 0) { - const children = treeToArray(record.children, expandAll, record, _level, item) - tmp = tmp.concat(children) - } - }) - return tmp -} diff --git a/src/views/table/treeTable/customTreeTable.vue b/src/views/table/treeTable/customTreeTable.vue deleted file mode 100644 index ae447b2d..00000000 --- a/src/views/table/treeTable/customTreeTable.vue +++ /dev/null @@ -1,138 +0,0 @@ - - - diff --git a/src/views/table/treeTable/treeTable.vue b/src/views/table/treeTable/treeTable.vue deleted file mode 100644 index e9e2e32b..00000000 --- a/src/views/table/treeTable/treeTable.vue +++ /dev/null @@ -1,129 +0,0 @@ - - - diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue index 96b76ace..02eb7cd5 100644 --- a/src/views/users/components/MultipleUsersMenu.vue +++ b/src/views/users/components/MultipleUsersMenu.vue @@ -146,35 +146,87 @@ export default { }, methods: { mappers() { + const applyActionToAllUsers = (filteredUsers, fn) => Promise.all(filteredUsers.map(fn)) + .then(() => { + this.$message({ + type: 'success', + message: this.$t('users.completed') + }) + this.$emit('apply-action') + }).catch((err) => { + console.log(err) + return + }) return { - grantRight: (right) => () => this.selectedUsers - .filter(user => user.local && !user.roles[right] && this.$store.state.user.id !== user.id) - .map(user => this.$store.dispatch('ToggleRight', { user, right })), - revokeRight: (right) => () => this.selectedUsers - .filter(user => user.local && user.roles[right] && this.$store.state.user.id !== user.id) - .map(user => this.$store.dispatch('ToggleRight', { user, right })), - activate: () => this.selectedUsers - .filter(user => user.deactivated && this.$store.state.user.id !== user.id) - .map(user => this.$store.dispatch('ToggleUserActivation', user.nickname)), - deactivate: () => this.selectedUsers - .filter(user => !user.deactivated && this.$store.state.user.id !== user.id) - .map(user => this.$store.dispatch('ToggleUserActivation', user.nickname)), - remove: () => this.selectedUsers - .filter(user => this.$store.state.user.id !== user.id) - .map(user => this.$store.dispatch('DeleteUser', user)), - addTag: (tag) => () => { - const users = this.selectedUsers - .filter(user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription' - ? user.local && !user.tags.includes(tag) - : !user.tags.includes(tag)) - this.$store.dispatch('AddTag', { users, tag }) + grantRight: (right) => () => { + const filterUsersFn = user => user.local && !user.roles[right] && this.$store.state.user.id !== user.id + const toggleRightFn = async(user) => await this.$store.dispatch('ToggleRight', { user, right }) + const filtered = this.selectedUsers.filter(filterUsersFn) + + applyActionToAllUsers(filtered, toggleRightFn) }, - removeTag: (tag) => () => { - const users = this.selectedUsers - .filter(user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription' - ? user.local && user.tags.includes(tag) - : user.tags.includes(tag)) - this.$store.dispatch('RemoveTag', { users, tag }) + revokeRight: (right) => () => { + const filterUsersFn = user => user.local && user.roles[right] && this.$store.state.user.id !== user.id + const toggleRightFn = async(user) => await this.$store.dispatch('ToggleRight', { user, right }) + const filtered = this.selectedUsers.filter(filterUsersFn) + + applyActionToAllUsers(filtered, toggleRightFn) + }, + activate: () => { + const filtered = this.selectedUsers.filter(user => user.deactivated && this.$store.state.user.id !== user.id) + const toggleActivationFn = async(user) => await this.$store.dispatch('ToggleUserActivation', user.nickname) + + applyActionToAllUsers(filtered, toggleActivationFn) + }, + deactivate: () => { + const filtered = this.selectedUsers.filter(user => !user.deactivated && this.$store.state.user.id !== user.id) + const toggleActivationFn = async(user) => await this.$store.dispatch('ToggleUserActivation', user.nickname) + + applyActionToAllUsers(filtered, toggleActivationFn) + }, + remove: () => { + const filtered = this.selectedUsers.filter(user => this.$store.state.user.id !== user.id) + const deleteAccountFn = async(user) => await this.$store.dispatch('DeleteUser', user) + + applyActionToAllUsers(filtered, deleteAccountFn) + }, + addTag: (tag) => async() => { + const filterUsersFn = user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription' + ? user.local && !user.tags.includes(tag) + : !user.tags.includes(tag) + const users = this.selectedUsers.filter(filterUsersFn) + + try { + await this.$store.dispatch('AddTag', { users, tag }) + } catch (err) { + console.log(err) + return + } + + this.$message({ + type: 'success', + message: this.$t('users.completed') + }) + this.$emit('apply-action') + }, + removeTag: (tag) => async() => { + const filterUsersFn = user => tag === 'disable_remote_subscription' || tag === 'disable_any_subscription' + ? user.local && user.tags.includes(tag) + : user.tags.includes(tag) + const users = this.selectedUsers.filter(filterUsersFn) + + try { + await this.$store.dispatch('RemoveTag', { users, tag }) + } catch (err) { + console.log(err) + return + } + + this.$message({ + type: 'success', + message: this.$t('users.completed') + }) + this.$emit('apply-action') } } }, @@ -234,11 +286,6 @@ export default { type: 'warning' }).then(() => { applyAction() - this.$emit('apply-action') - this.$message({ - type: 'success', - message: this.$t('users.completed') - }) }).catch(() => { this.$message({ type: 'info', diff --git a/src/views/users/components/NewAccountDialog.vue b/src/views/users/components/NewAccountDialog.vue index 8cf2a725..4190f3cc 100644 --- a/src/views/users/components/NewAccountDialog.vue +++ b/src/views/users/components/NewAccountDialog.vue @@ -5,20 +5,20 @@ :title="$t('users.createAccount')" custom-class="create-user-dialog" @open="resetForm"> - + - {{ $t('users.cancel') }} - {{ $t('users.create') }} + {{ $t('users.create') }} @@ -36,7 +36,7 @@ export default { }, data() { return { - form: { + newUserForm: { nickname: '', email: '', password: '' @@ -67,7 +67,7 @@ export default { } }, getLabelWidth() { - return this.isDesktop ? '120px' : '80px' + return this.isDesktop ? '120px' : '85px' } }, methods: { @@ -76,18 +76,13 @@ export default { }, resetForm() { this.$nextTick(() => { - this.$refs['form'].resetFields() + this.$refs['newUserForm'].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') - }) + this.$emit('createNewAccount', this.$data.newUserForm) } else { this.$message({ type: 'error', @@ -135,17 +130,26 @@ export default { diff --git a/src/views/users/index.vue b/src/views/users/index.vue index 47856e95..fd26a993 100644 --- a/src/views/users/index.vue +++ b/src/views/users/index.vue @@ -9,9 +9,9 @@
-
+ @closeWindow="createAccountDialogOpen = false"/> + + {{ $t('users.getPasswordResetToken') }} + + +
+

Password reset token was generated: {{ passwordResetToken }}

+

You can also use this link to reset password: + {{ passwordResetLink }} +

+
+

There are no users to display

@@ -166,7 +185,8 @@ export default { return { search: '', selectedUsers: [], - dialogFormVisible: false + createAccountDialogOpen: false, + resetPasswordDialogOpen: false } }, computed: { @@ -185,6 +205,12 @@ export default { pageSize() { return this.$store.state.users.pageSize }, + passwordResetLink() { + return this.$store.state.users.passwordResetToken.link + }, + passwordResetToken() { + return this.$store.state.users.passwordResetToken.token + }, currentPage() { return this.$store.state.users.currentPage }, @@ -213,12 +239,26 @@ export default { clearSelection() { this.$refs.usersTable.clearSelection() }, - createNewAccount(accountData) { - this.$store.dispatch('CreateNewAccount', accountData) + async createNewAccount(accountData) { + try { + await this.$store.dispatch('CreateNewAccount', accountData) + } catch (_e) { + return + } finally { + this.createAccountDialogOpen = false + } + this.$message({ + type: 'success', + message: this.$t('users.accountCreated') + }) }, getFirstLetter(str) { return str.charAt(0).toUpperCase() }, + getPasswordResetToken(nickname) { + this.resetPasswordDialogOpen = true + this.$store.dispatch('GetPasswordResetToken', nickname) + }, handleDeactivation({ nickname }) { this.$store.dispatch('ToggleUserActivation', nickname) }, @@ -236,6 +276,10 @@ export default { handleSelectionChange(value) { this.$data.selectedUsers = value }, + closeResetPasswordDialog() { + this.resetPasswordDialogOpen = false + this.$store.dispatch('RemovePasswordToken') + }, showAdminAction({ local, id }) { return local && this.showDeactivatedButton(id) }, @@ -254,7 +298,7 @@ export default { } -