From acfdb3ae6f1b2dc46e9dffe5cde52ef8e4b313a1 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 09:37:01 +0300 Subject: [PATCH 01/62] Report errors from requests with the error json property --- src/utils/request.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/request.js b/src/utils/request.js index 7e9acc1e..271fe591 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -11,8 +11,13 @@ service.interceptors.response.use( response => response, error => { console.log('Error ' + error) + console.log(error.response.data) + + // If there's an "error" property in the json, use it + const edata = error.response.data.error ? error.response.data.error : error.response.data + Message({ - message: `${error.message} - ${error.response.data}`, + message: `${error.message} - ${edata}`, type: 'error', duration: 5 * 1000 }) From 10d3f3f0a03d56ed30dcb8399ec5f7a6b17d3ea4 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 19:38:35 +0300 Subject: [PATCH 02/62] Use the remote pack listing endpoint --- src/api/emoji_packs.js | 10 ++++++++++ src/store/modules/emoji_packs.js | 7 +++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/api/emoji_packs.js b/src/api/emoji_packs.js index ce261fdb..4e7c85dd 100644 --- a/src/api/emoji_packs.js +++ b/src/api/emoji_packs.js @@ -48,6 +48,16 @@ export async function listPacks(host) { }) } +export async function listRemotePacks(host, token, instance) { + return await request({ + baseURL: baseName(host), + url: `/api/pleroma/emoji/packs/list_from`, + method: 'post', + headers: authHeaders(token), + data: { instance_address: instance } + }) +} + export async function downloadFrom(host, instance_address, pack_name, as, token) { if (as.trim() === '') { as = null diff --git a/src/store/modules/emoji_packs.js b/src/store/modules/emoji_packs.js index 20dacf3a..1cee0060 100644 --- a/src/store/modules/emoji_packs.js +++ b/src/store/modules/emoji_packs.js @@ -1,4 +1,6 @@ -import { listPacks, +import { + listPacks, + listRemotePacks, downloadFrom, reloadEmoji, createPack, @@ -47,7 +49,8 @@ const packs = { commit('SET_LOCAL_PACKS', data) }, async SetRemoteEmojiPacks({ commit, getters, state }, { remoteInstance }) { - const { data } = await listPacks(remoteInstance) + const { data } = await listRemotePacks(getters.authHost, getters.token, remoteInstance) + commit('SET_REMOTE_PACKS', data) }, async DownloadFrom({ commit, getters, state }, { instanceAddress, packName, as }) { From 2662210063208482c85c8bff34865db8c88beef7 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 20:10:50 +0300 Subject: [PATCH 03/62] Make baseName check if http is already in the name --- src/api/utils.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/api/utils.js b/src/api/utils.js index 4943f650..1346a204 100644 --- a/src/api/utils.js +++ b/src/api/utils.js @@ -1,5 +1,10 @@ const isLocalhost = (instanceName) => instanceName.startsWith('localhost:') || instanceName.startsWith('127.0.0.1:') -export const baseName = (instanceName) => - isLocalhost(instanceName) ? `http://${instanceName}` : `https://${instanceName}` +export const baseName = (instanceName) => { + if (instanceName.match(/https?:\/\//)) { + return instanceName + } else { + return isLocalhost(instanceName) ? `http://${instanceName}` : `https://${instanceName}` + } +} From e9060cd8bada7a10fa21c9964252381dc923b033 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 24 Sep 2019 20:12:17 +0300 Subject: [PATCH 04/62] Add baseName to some remote instance requests --- src/api/emoji_packs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/emoji_packs.js b/src/api/emoji_packs.js index 4e7c85dd..cc07b1b6 100644 --- a/src/api/emoji_packs.js +++ b/src/api/emoji_packs.js @@ -54,7 +54,7 @@ export async function listRemotePacks(host, token, instance) { url: `/api/pleroma/emoji/packs/list_from`, method: 'post', headers: authHeaders(token), - data: { instance_address: instance } + data: { instance_address: baseName(instance) } }) } @@ -68,7 +68,7 @@ export async function downloadFrom(host, instance_address, pack_name, as, token) url: '/api/pleroma/emoji/packs/download_from', method: 'post', headers: authHeaders(token), - data: { instance_address, pack_name, as }, + data: { instance_address: baseName(instance_address), pack_name, as }, timeout: 0 }) } From ada2de97330496b0be50d5aa4b710b469f3b41cc Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Tue, 24 Sep 2019 22:55:42 +0000 Subject: [PATCH 05/62] Ability to require user's password reset --- CHANGELOG.md | 3 ++- src/api/users.js | 9 +++++++++ src/lang/en.js | 2 ++ src/store/modules/users.js | 17 ++++++++++++++++- .../users/components/MultipleUsersMenu.vue | 14 ++++++++++++++ src/views/users/index.vue | 8 ++++++++ test/views/users/multipleUsersMenu.test.js | 12 ++++++------ 7 files changed, 57 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75553acf..ef27fb4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - Emoji pack configuration +- Ability to require user's password reset ## [1.1.0] - 2019-09-15 @@ -26,7 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - removes "Dashboard" from dropdown menu - makes all single selects clearable and allow to enter custom values in all multiple selects -- removes legacy activitypub accept_blocks setting +- removes legacy activitypub accept_blocks setting ### Fixed diff --git a/src/api/users.js b/src/api/users.js index 0f8e4a41..1e97da92 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -66,6 +66,15 @@ export async function getPasswordResetToken(nickname, authHost, token) { }) } +export async function requirePasswordReset(nickname, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users/${nickname}/force_password_reset`, + method: 'patch', + headers: authHeaders(token) + }) +} + export async function searchUsers(query, filters, authHost, token, page = 1) { return await request({ baseURL: baseName(authHost), diff --git a/src/lang/en.js b/src/lang/en.js index f2c6c137..eed40f5f 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -200,6 +200,7 @@ export default { disableRemoteSubscriptionForMultiple: 'Disallow following users from remote instances', disableAnySubscription: 'Disallow following user at all', disableAnySubscriptionForMultiple: 'Disallow following users at all', + requirePasswordReset: 'Require password reset on next login', selectUsers: 'Select users to apply actions to multiple users', moderateUsers: 'Moderate multiple users', createAccount: 'Create new account', @@ -212,6 +213,7 @@ export default { 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?', + requirePasswordResetConfirmation: 'Are you sure you want to require password reset for all selected users?', ok: 'Okay', completed: 'Completed', cancel: 'Cancel', diff --git a/src/store/modules/users.js b/src/store/modules/users.js index f732c810..36a7f507 100644 --- a/src/store/modules/users.js +++ b/src/store/modules/users.js @@ -1,4 +1,16 @@ -import { addRight, createNewAccount, deleteRight, deleteUser, fetchUsers, getPasswordResetToken, searchUsers, tagUser, toggleUserActivation, untagUser } from '@/api/users' +import { + addRight, + createNewAccount, + deleteRight, + deleteUser, + fetchUsers, + getPasswordResetToken, + searchUsers, + tagUser, + toggleUserActivation, + untagUser, + requirePasswordReset +} from '@/api/users' const users = { state: { @@ -83,6 +95,9 @@ const users = { const users = state.fetchedUsers.filter(user => user.nickname !== data) commit('SET_USERS', users) }, + async RequirePasswordReset({ commit, getters, state }, user) { + await requirePasswordReset(user.nickname, getters.authHost, getters.token) + }, async FetchUsers({ commit, state, getters }, { page }) { commit('SET_LOADING', true) const filters = Object.keys(state.filters).filter(filter => state.filters[filter]).join() diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue index 02eb7cd5..ff016f51 100644 --- a/src/views/users/components/MultipleUsersMenu.vue +++ b/src/views/users/components/MultipleUsersMenu.vue @@ -39,6 +39,10 @@ @click.native="deleteMultipleUsers"> {{ $t('users.deleteAccounts') }} + + {{ $t('users.requirePasswordReset') }} +
{{ $t('users.forceNsfw') }} @@ -227,6 +231,9 @@ export default { message: this.$t('users.completed') }) this.$emit('apply-action') + }, + requirePasswordReset: () => { + this.selectedUsers.map(user => this.$store.dispatch('RequirePasswordReset', user)) } } }, @@ -265,6 +272,13 @@ export default { remove ) }, + requirePasswordReset() { + const { requirePasswordReset } = this.mappers() + this.confirmMessage( + this.$t('users.requirePasswordResetConfirmation'), + requirePasswordReset + ) + }, addTagForMultipleUsers(tag) { const { addTag } = this.mappers() this.confirmMessage( diff --git a/src/views/users/index.vue b/src/views/users/index.vue index fd26a993..16160a97 100644 --- a/src/views/users/index.vue +++ b/src/views/users/index.vue @@ -133,6 +133,11 @@ @click.native="getPasswordResetToken(scope.row.nickname)"> {{ $t('users.getPasswordResetToken') }} + + {{ $t('users.requirePasswordReset') }} + @@ -259,6 +264,9 @@ export default { this.resetPasswordDialogOpen = true this.$store.dispatch('GetPasswordResetToken', nickname) }, + requirePasswordReset(nickname) { + this.$store.dispatch('RequirePasswordReset', { nickname }) + }, handleDeactivation({ nickname }) { this.$store.dispatch('ToggleUserActivation', nickname) }, diff --git a/test/views/users/multipleUsersMenu.test.js b/test/views/users/multipleUsersMenu.test.js index b4388716..355430e1 100644 --- a/test/views/users/multipleUsersMenu.test.js +++ b/test/views/users/multipleUsersMenu.test.js @@ -251,15 +251,15 @@ describe('Apply users actions to multiple users', () => { const addTagForMultipleUsersStub = jest.fn() wrapper.setMethods({ addTagForMultipleUsers: addTagForMultipleUsersStub }) - wrapper.find(`.el-dropdown-menu__item:nth-child(8) button:nth-child(1)`).trigger('click') + wrapper.find(`.el-dropdown-menu__item:nth-child(9) button:nth-child(1)`).trigger('click') expect(wrapper.vm.addTagForMultipleUsers).toHaveBeenCalled() expect(wrapper.vm.addTagForMultipleUsers).toHaveBeenCalledWith('force_nsfw') - wrapper.find(`.el-dropdown-menu__item:nth-child(10) button:nth-child(1)`).trigger('click') + wrapper.find(`.el-dropdown-menu__item:nth-child(11) button:nth-child(1)`).trigger('click') expect(wrapper.vm.addTagForMultipleUsers).toHaveBeenCalled() expect(wrapper.vm.addTagForMultipleUsers).toHaveBeenCalledWith('force_unlisted') - wrapper.find(`.el-dropdown-menu__item:nth-child(12) button:nth-child(1)`).trigger('click') + wrapper.find(`.el-dropdown-menu__item:nth-child(13) button:nth-child(1)`).trigger('click') expect(wrapper.vm.addTagForMultipleUsers).toHaveBeenCalled() expect(wrapper.vm.addTagForMultipleUsers).toHaveBeenCalledWith('disable_remote_subscription') @@ -292,15 +292,15 @@ describe('Apply users actions to multiple users', () => { const removeTagFromMultipleUsersStub = jest.fn() wrapper.setMethods({ removeTagFromMultipleUsers: removeTagFromMultipleUsersStub }) - wrapper.find(`.el-dropdown-menu__item:nth-child(9) button:nth-child(2)`).trigger('click') + wrapper.find(`.el-dropdown-menu__item:nth-child(10) button:nth-child(2)`).trigger('click') expect(wrapper.vm.removeTagFromMultipleUsers).toHaveBeenCalled() expect(wrapper.vm.removeTagFromMultipleUsers).toHaveBeenCalledWith('strip_media') - wrapper.find(`.el-dropdown-menu__item:nth-child(11) button:nth-child(2)`).trigger('click') + wrapper.find(`.el-dropdown-menu__item:nth-child(12) button:nth-child(2)`).trigger('click') expect(wrapper.vm.removeTagFromMultipleUsers).toHaveBeenCalled() expect(wrapper.vm.removeTagFromMultipleUsers).toHaveBeenCalledWith('sandbox') - wrapper.find(`.el-dropdown-menu__item:nth-child(13) button:nth-child(2)`).trigger('click') + wrapper.find(`.el-dropdown-menu__item:nth-child(14) button:nth-child(2)`).trigger('click') expect(wrapper.vm.removeTagFromMultipleUsers).toHaveBeenCalled() expect(wrapper.vm.removeTagFromMultipleUsers).toHaveBeenCalledWith('disable_any_subscription') From a30a795b4456b10bebacb9daa0ea3b81f261cd54 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Fri, 27 Sep 2019 12:41:54 +0000 Subject: [PATCH 06/62] Add moderation log --- CHANGELOG.md | 3 + README.md | 15 ++- src/api/moderationLog.js | 38 ++++++++ src/lang/en.js | 4 + src/router/index.js | 19 +++- src/store/index.js | 2 + src/store/modules/moderationLog.js | 51 ++++++++++ src/views/moderation_log/index.vue | 151 +++++++++++++++++++++++++++++ 8 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 src/api/moderationLog.js create mode 100644 src/store/modules/moderationLog.js create mode 100644 src/views/moderation_log/index.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index ef27fb4b..ac8ad270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +## [1.2.0] - 2019-09-27 + ### Added - Emoji pack configuration - Ability to require user's password reset +– Ability to track admin/moderator actions, a.k.a. "the moderation log" ## [1.1.0] - 2019-09-15 diff --git a/README.md b/README.md index 72c81829..059918a9 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,19 @@ To compile everything for production run `yarn build:prod`. #### Disabling features -You can disable certain AdminFE features, like reports or settings by modifying `config/prod.env.js` env variable `DISABLED_FEATURES`, e.g. if you want to compile AdminFE without "Settings" you'll need to set it to: `DISABLED_FEATURES: '["settings"]'`, -to disable emoji pack settings add `"emoji-packs"` to the list. +You can disable certain AdminFE features, like reports or settings by modifying `config/prod.env.js` env variable `DISABLED_FEATURES`, e.g. if you want to compile AdminFE without "Settings" you'll need to set it to: `DISABLED_FEATURES: '["settings"]'`. + +Features, that can be disabled: + +- reports: `DISABLED_FEATURES: '["reports"]'` +- invites: `DISABLED_FEATURES: '["invites"]'` +- moderation log: `DISABLED_FEATURES: '["moderationLog"]'` +- settings: `DISABLED_FEATURES: '["settings"]'` +- emoji packs: `DISABLED_FEATURES: '["emojiPacks"]'` + +Of course, you can disable multiple features just by adding to the array, e.g. `DISABLED_FEATURES: '["emojiPacks", "settings"]'` will have both emoji packs and settings disabled. + +Users administration cannot be disabled. ## Changelog diff --git a/src/api/moderationLog.js b/src/api/moderationLog.js new file mode 100644 index 00000000..b866df33 --- /dev/null +++ b/src/api/moderationLog.js @@ -0,0 +1,38 @@ +import _ from 'lodash' + +import request from '@/utils/request' +import { getToken } from '@/utils/auth' +import { baseName } from './utils' + +export async function fetchLog(authHost, token, params, page = 1) { + const normalizedParams = new URLSearchParams( + _.omitBy({ ...params, page }, _.isUndefined) + ).toString() + + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/moderation_log?${normalizedParams}`, + method: 'get', + headers: authHeaders(token) + }) +} + +export async function fetchAdmins(authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users?filters=is_admin`, + method: 'get', + headers: authHeaders(token) + }) +} + +export async function fetchModerators(authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/users?filters=is_moderator`, + method: 'get', + headers: authHeaders(token) + }) +} + +const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} diff --git a/src/lang/en.js b/src/lang/en.js index eed40f5f..5d705cdf 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -67,6 +67,7 @@ export default { users: 'Users', reports: 'Reports', settings: 'Settings', + moderationLog: 'Moderation Log', 'emoji-packs': 'Emoji packs' }, navbar: { @@ -284,6 +285,9 @@ export default { closed: 'Closed', resolved: 'Resolved' }, + moderationLog: { + moderationLog: 'Moderation Log' + }, settings: { settings: 'Settings', instance: 'Instance', diff --git a/src/router/index.js b/src/router/index.js index b0e0d750..a5c496cc 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -49,6 +49,20 @@ const invites = { ] } +const moderationLogDisabled = disabledFeatures.includes('moderation-log') +const moderationLog = { + path: '/moderation_log', + component: Layout, + children: [ + { + path: 'index', + component: () => import('@/views/moderation_log/index'), + name: 'Moderation Log', + meta: { title: 'moderationLog', icon: 'list', noCache: true } + } + ] +} + const emojiPacksDisabled = disabledFeatures.includes('emoji-packs') const emojiPacks = { path: '/emoji-packs', @@ -122,13 +136,14 @@ 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]), + ...(moderationLogDisabled ? [] : [moderationLog]), + ...(settingsDisabled ? [] : [settings]), ...(emojiPacksDisabled ? [] : [emojiPacks]), { path: '/users/:id', diff --git a/src/store/index.js b/src/store/index.js index 830c0788..985074a0 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 moderationLog from './modules/moderationLog' import invites from './modules/invites' import permission from './modules/permission' import reports from './modules/reports' @@ -19,6 +20,7 @@ const store = new Vuex.Store({ modules: { app, errorLog, + moderationLog, invites, permission, reports, diff --git a/src/store/modules/moderationLog.js b/src/store/modules/moderationLog.js new file mode 100644 index 00000000..5e790f9e --- /dev/null +++ b/src/store/modules/moderationLog.js @@ -0,0 +1,51 @@ +import { fetchLog, fetchAdmins, fetchModerators } from '@/api/moderationLog' + +const moderationLog = { + state: { + fetchedLog: [], + logItemsCount: 0, + admins: [], + moderators: [], + logLoading: true, + adminsLoading: true + }, + mutations: { + SET_LOG_LOADING: (state, status) => { + state.logLoading = status + }, + SET_ADMINS_LOADING: (state, status) => { + state.adminsLoading = status + }, + SET_MODERATION_LOG: (state, log) => { + state.fetchedLog = log + }, + SET_MODERATION_LOG_COUNT: (state, count) => { + state.logItemsCount = count + }, + SET_ADMINS: (state, admins) => { + state.admins = admins + }, + SET_MODERATORS: (state, moderators) => { + state.moderators = moderators + } + }, + actions: { + async FetchModerationLog({ commit, getters }, opts = {}) { + const response = await fetchLog(getters.authHost, getters.token, opts) + + commit('SET_MODERATION_LOG', response.data.items) + commit('SET_MODERATION_LOG_COUNT', response.data.total) + commit('SET_LOG_LOADING', false) + }, + async FetchAdmins({ commit, getters }) { + const adminsResponse = await fetchAdmins(getters.authHost, getters.token) + const moderatorsResponse = await fetchModerators(getters.authHost, getters.token) + + commit('SET_ADMINS', adminsResponse.data) + commit('SET_MODERATORS', moderatorsResponse.data) + commit('SET_ADMINS_LOADING', false) + } + } +} + +export default moderationLog diff --git a/src/views/moderation_log/index.vue b/src/views/moderation_log/index.vue new file mode 100644 index 00000000..6dba06f6 --- /dev/null +++ b/src/views/moderation_log/index.vue @@ -0,0 +1,151 @@ + + + + + From 81409616af9ba0a567bef4636d40d297643bc768 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Fri, 27 Sep 2019 15:54:03 +0300 Subject: [PATCH 07/62] List features in the readme --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 059918a9..f6b359c6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,17 @@ ## About -Admin UI for pleroma instance owners +Admin UI for pleroma instance owners. + +### Features + +1. User administration: grant roles to users (admin/moderator), deactivate/delete as well as force their statuses to have NSFW tag, strip media and many more +1. Invites management: generate invite tokens & send invites via email +1. Moderation log: track moderator/admin actions +1. Settings: configure your pleroma instance via friendly (hopefully) UI +1. Emoji packs: configure your emoji packs + +You can have any combination of these features (i.e. you can disable anything, but user administration, see "Disabling features" section below). ## Usage From 8956e68fa16d29d4d0712b7ded7b51d052a80776 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Fri, 27 Sep 2019 18:32:14 +0300 Subject: [PATCH 08/62] `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email) --- CHANGELOG.md | 4 ++++ src/api/nodeInfo.js | 10 ++++++++++ src/api/utils.js | 2 +- src/lang/en.js | 1 + src/store/modules/user.js | 11 ++++++++++- src/store/modules/users.js | 3 ++- src/views/users/components/MultipleUsersMenu.vue | 8 ++++++++ src/views/users/index.vue | 8 ++++++++ 8 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 src/api/nodeInfo.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ac8ad270..be0fb971 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +### Changed + +- `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email) + ## [1.2.0] - 2019-09-27 ### Added diff --git a/src/api/nodeInfo.js b/src/api/nodeInfo.js new file mode 100644 index 00000000..c67df01b --- /dev/null +++ b/src/api/nodeInfo.js @@ -0,0 +1,10 @@ +import request from '@/utils/request' +import { baseName } from './utils' + +export async function getNodeInfo(authHost) { + return await request({ + baseURL: baseName(authHost), + url: `/nodeinfo/2.0.json`, + method: 'get' + }) +} diff --git a/src/api/utils.js b/src/api/utils.js index 1346a204..9f47a1f8 100644 --- a/src/api/utils.js +++ b/src/api/utils.js @@ -1,7 +1,7 @@ const isLocalhost = (instanceName) => instanceName.startsWith('localhost:') || instanceName.startsWith('127.0.0.1:') -export const baseName = (instanceName) => { +export const baseName = (instanceName = 'localhost') => { if (instanceName.match(/https?:\/\//)) { return instanceName } else { diff --git a/src/lang/en.js b/src/lang/en.js index 5d705cdf..8088e441 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -215,6 +215,7 @@ export default { 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?', requirePasswordResetConfirmation: 'Are you sure you want to require password reset for all selected users?', + mailerMustBeEnabled: 'To require user\'s password reset you must enable mailer.', ok: 'Okay', completed: 'Completed', cancel: 'Cancel', diff --git a/src/store/modules/user.js b/src/store/modules/user.js index 18ff5419..3ab9339d 100644 --- a/src/store/modules/user.js +++ b/src/store/modules/user.js @@ -1,4 +1,5 @@ import { loginByUsername, getUserInfo } from '@/api/login' +import { getNodeInfo } from '@/api/nodeInfo' import { getToken, setToken, removeToken, getAuthHost, setAuthHost, removeAuthHost } from '@/utils/auth' const user = { @@ -15,7 +16,8 @@ const user = { roles: [], setting: { articlePlatform: [] - } + }, + nodeInfo: {} }, mutations: { @@ -48,6 +50,9 @@ const user = { }, SET_AUTH_HOST: (state, authHost) => { state.authHost = authHost + }, + SET_NODE_INFO: (state, nodeInfo) => { + state.nodeInfo = nodeInfo } }, @@ -67,7 +72,11 @@ const user = { }) }) }, + async GetNodeInfo({ commit, state }) { + const nodeInfo = await getNodeInfo(state.authHost) + commit('SET_NODE_INFO', nodeInfo.data) + }, GetUserInfo({ commit, state }) { return new Promise((resolve, reject) => { getUserInfo(state.token, state.authHost).then(response => { diff --git a/src/store/modules/users.js b/src/store/modules/users.js index 36a7f507..b3d35108 100644 --- a/src/store/modules/users.js +++ b/src/store/modules/users.js @@ -98,10 +98,11 @@ const users = { async RequirePasswordReset({ commit, getters, state }, user) { await requirePasswordReset(user.nickname, getters.authHost, getters.token) }, - async FetchUsers({ commit, state, getters }, { page }) { + async FetchUsers({ commit, state, getters, dispatch }, { page }) { commit('SET_LOADING', true) const filters = Object.keys(state.filters).filter(filter => state.filters[filter]).join() const response = await fetchUsers(filters, getters.authHost, getters.token, page) + await dispatch('GetNodeInfo') loadUsers(commit, page, response.data) }, async GetPasswordResetToken({ commit, state, getters }, nickname) { diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue index ff016f51..ecd2baf2 100644 --- a/src/views/users/components/MultipleUsersMenu.vue +++ b/src/views/users/components/MultipleUsersMenu.vue @@ -273,6 +273,14 @@ export default { ) }, requirePasswordReset() { + const mailerEnabled = this.$store.state.user.nodeInfo.metadata.mailerEnabled + + if (!mailerEnabled) { + this.$alert(this.$t('users.mailerMustBeEnabled'), 'Error', { type: 'error' }) + + return + } + const { requirePasswordReset } = this.mappers() this.confirmMessage( this.$t('users.requirePasswordResetConfirmation'), diff --git a/src/views/users/index.vue b/src/views/users/index.vue index 16160a97..062b23c8 100644 --- a/src/views/users/index.vue +++ b/src/views/users/index.vue @@ -265,6 +265,14 @@ export default { this.$store.dispatch('GetPasswordResetToken', nickname) }, requirePasswordReset(nickname) { + const mailerEnabled = this.$store.state.user.nodeInfo.metadata.mailerEnabled + + if (!mailerEnabled) { + this.$alert(this.$t('users.mailerMustBeEnabled'), 'Error', { type: 'error' }) + + return + } + this.$store.dispatch('RequirePasswordReset', { nickname }) }, handleDeactivation({ nickname }) { From 3daf35d0b0c583837a8e96a765d54d3880aaff99 Mon Sep 17 00:00:00 2001 From: Maxim Filippov Date: Fri, 27 Sep 2019 19:40:25 +0300 Subject: [PATCH 09/62] Fix tests --- src/api/__mocks__/nodeInfo.js | 9 +++++++++ src/utils/request.js | 2 +- test/views/login/index.test.js | 1 + test/views/users/index.test.js | 1 + test/views/users/multipleUsersMenu.test.js | 1 + test/views/users/usersFilters.test.js | 1 + 6 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/api/__mocks__/nodeInfo.js diff --git a/src/api/__mocks__/nodeInfo.js b/src/api/__mocks__/nodeInfo.js new file mode 100644 index 00000000..6daf074e --- /dev/null +++ b/src/api/__mocks__/nodeInfo.js @@ -0,0 +1,9 @@ +export async function getNodeInfo(authHost) { + const data = { + metadata: { + mailerEnabled: true + } + } + + return Promise.resolve({ data }) +} diff --git a/src/utils/request.js b/src/utils/request.js index 271fe591..bbed6501 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -10,7 +10,7 @@ const service = axios.create({ service.interceptors.response.use( response => response, error => { - console.log('Error ' + error) + console.log(`Error ${error}`) console.log(error.response.data) // If there's an "error" property in the json, use it diff --git a/test/views/login/index.test.js b/test/views/login/index.test.js index b2986b5d..03e45667 100644 --- a/test/views/login/index.test.js +++ b/test/views/login/index.test.js @@ -16,6 +16,7 @@ localVue.use(Vuex) localVue.use(VueRouter) localVue.use(Element) +jest.mock('@/api/nodeInfo') jest.mock('@/api/login') describe('Login', () => { diff --git a/test/views/users/index.test.js b/test/views/users/index.test.js index 863186f3..d435e1ce 100644 --- a/test/views/users/index.test.js +++ b/test/views/users/index.test.js @@ -14,6 +14,7 @@ const localVue = createLocalVue() localVue.use(Vuex) localVue.use(Element) +jest.mock('@/api/nodeInfo') jest.mock('@/api/users') describe('Search and filter users', () => { diff --git a/test/views/users/multipleUsersMenu.test.js b/test/views/users/multipleUsersMenu.test.js index 355430e1..c92232bf 100644 --- a/test/views/users/multipleUsersMenu.test.js +++ b/test/views/users/multipleUsersMenu.test.js @@ -13,6 +13,7 @@ const localVue = createLocalVue() localVue.use(Vuex) localVue.use(Element) +jest.mock('@/api/nodeInfo') jest.mock('@/api/users') describe('Apply users actions to multiple users', () => { diff --git a/test/views/users/usersFilters.test.js b/test/views/users/usersFilters.test.js index 9cf14c66..e56822ca 100644 --- a/test/views/users/usersFilters.test.js +++ b/test/views/users/usersFilters.test.js @@ -13,6 +13,7 @@ const localVue = createLocalVue() localVue.use(Vuex) localVue.use(Element) +jest.mock('@/api/nodeInfo') jest.mock('@/api/users') describe('Filters users', () => { From 2d52fbdfcb91d6bcb497893550bc187554fd7589 Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Tue, 1 Oct 2019 22:54:13 +0300 Subject: [PATCH 10/62] Update card with user information --- src/lang/en.js | 11 +++- src/views/users/show.vue | 137 +++++++++++++++++++++------------------ 2 files changed, 81 insertions(+), 67 deletions(-) diff --git a/src/lang/en.js b/src/lang/en.js index 8088e441..c1ecd22a 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -238,11 +238,16 @@ export default { tags: 'Tags', moderator: 'Moderator', admin: 'Admin', - local: 'Local', + local: 'local', + external: 'external', + localUppercase: 'Local', nickname: 'Nickname', - deactivated: 'Deactivated', recentStatuses: 'Recent Statues', - showPrivateStatuses: 'Show private statuses' + showPrivateStatuses: 'Show private statuses', + roles: 'Roles', + activeUppercase: 'Active', + active: 'active', + deactivated: 'deactivated' }, usersFilter: { inputPlaceholder: 'Select filter', diff --git a/src/views/users/show.vue b/src/views/users/show.vue index 049ae02f..00ca72b4 100644 --- a/src/views/users/show.vue +++ b/src/views/users/show.vue @@ -5,70 +5,66 @@

{{ user.display_name }}

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID - {{ user.id }} -
{{ $t('userProfile.tags') }} - {{ tag }} - None -
{{ $t('userProfile.moderator') }} - - -
{{ $t('userProfile.admin') }} - - -
{{ $t('userProfile.local') }} - - -
{{ $t('userProfile.deactivated') }} - - -
{{ $t('userProfile.nickname') }} - {{ user.nickname }} -
-
+ + - -

{{ $t('userProfile.recentStatuses') }}

- - - {{ $t('userProfile.showPrivateStatuses') }} - - -
- + + +

{{ $t('userProfile.recentStatuses') }}

+ + + {{ $t('userProfile.showPrivateStatuses') }} + + +
@@ -168,12 +164,25 @@ table { width: 100%; } } +.recent-statuses-header { + margin-top: 10px; +} .statuses { - padding-right: 20px; + padding: 0 20px 0 0; } .show-private { text-align: right; line-height: 67px; padding-right: 20px; } +.user-profile-card { + margin-left: 15px; + margin-right: 20px; +} +.user-profile-table { + margin: 0; +} +.user-profile-tag { + margin: 0 4px 4px 0; +} From df26ac0858636455207a57627b9cc27c74f70393 Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Fri, 4 Oct 2019 18:49:03 +0300 Subject: [PATCH 11/62] Display role tags --- src/views/users/show.vue | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/views/users/show.vue b/src/views/users/show.vue index 00ca72b4..a9dada5a 100644 --- a/src/views/users/show.vue +++ b/src/views/users/show.vue @@ -32,8 +32,12 @@ {{ $t('userProfile.roles') }} -
- -
+ + - -
+
@@ -44,11 +43,10 @@
- -
+ + - -
+
@@ -62,7 +60,9 @@ export default { data() { return { remoteInstanceAddress: '', - newPackName: '' + newPackName: '', + activeLocalPack: '', + activeRemotePack: '' } }, computed: { From 9bc103042b4c515ce0b73dfba0f68b70b35c2ae0 Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Mon, 7 Oct 2019 03:09:53 +0300 Subject: [PATCH 17/62] Add form for uploading new emoji --- src/lang/en.js | 11 +- src/views/emojiPacks/components/EmojiPack.vue | 12 +- .../components/NewEmojiUploader.vue | 129 +++++++++--------- src/views/emojiPacks/index.vue | 4 +- 4 files changed, 85 insertions(+), 71 deletions(-) diff --git a/src/lang/en.js b/src/lang/en.js index a84a4ec3..1250f0df 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -330,7 +330,16 @@ export default { license: 'License', fallbackSrc: 'Fallback source', fallbackSrcSha: 'Fallback source SHA', - savePackMetadata: 'Save pack metadata' + savePackMetadata: 'Save pack metadata', + addNewEmoji: 'Add new emoji to the pack', + shortcode: 'Shortcode', + uploadFile: 'Upload a file', + customFilename: 'Custom filename', + optional: 'optional', + customFilenameDesc: 'Custom file name (optional)', + url: 'URL', + required: 'required', + clickToUpload: 'Click to upload' }, invites: { inviteTokens: 'Invite tokens', diff --git a/src/views/emojiPacks/components/EmojiPack.vue b/src/views/emojiPacks/components/EmojiPack.vue index 71708ee3..5003a61c 100644 --- a/src/views/emojiPacks/components/EmojiPack.vue +++ b/src/views/emojiPacks/components/EmojiPack.vue @@ -1,6 +1,6 @@ @@ -38,6 +43,9 @@ } .el-collapse-item__header { height: 36px; + font-size: 14px; + font-weight: 700; + color: #606266; } .emoji-pack-card { margin-top: 5px; @@ -72,7 +80,7 @@ export default { data() { return { - shownPackEmoji: [], + showPackContent: [], downloadSharedAs: '' } }, diff --git a/src/views/emojiPacks/components/NewEmojiUploader.vue b/src/views/emojiPacks/components/NewEmojiUploader.vue index d62011de..0770e332 100644 --- a/src/views/emojiPacks/components/NewEmojiUploader.vue +++ b/src/views/emojiPacks/components/NewEmojiUploader.vue @@ -1,49 +1,53 @@ + + From d74bdecc32d622e45a1436dd0fe019da6d25625f Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Wed, 9 Oct 2019 18:24:27 +0300 Subject: [PATCH 19/62] Add ability to delete pack, download an archive and download a pack --- src/lang/en.js | 11 ++- src/views/emojiPacks/components/EmojiPack.vue | 77 ++++++++++++++----- 2 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/lang/en.js b/src/lang/en.js index e501cfe3..46b4b641 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -352,7 +352,16 @@ export default { leaveEmptyShortcode: 'leave empty to use the same shortcode', leaveEmptyFilename: 'leave empty to use the same filename', copy: 'Copy', - copyToLocalPack: 'Copy to local pack' + copyToLocalPack: 'Copy to local pack', + thisWillDownload: 'This will download the', + downloadToCurrentInstance: 'pack to the current instance under the name', + canBeChanged: 'can be changed below', + willBeUsable: 'It will then be usable and shareable from the current instance', + downloadPack: 'Download pack', + deletePack: 'Delete pack', + downloadSharedPack: 'Download shared pack to current instance', + downloadAsOptional: 'Download as (optional)', + downloadPackArchive: 'Download pack archive' }, invites: { inviteTokens: 'Invite tokens', diff --git a/src/views/emojiPacks/components/EmojiPack.vue b/src/views/emojiPacks/components/EmojiPack.vue index 9094e12f..3f819de8 100644 --- a/src/views/emojiPacks/components/EmojiPack.vue +++ b/src/views/emojiPacks/components/EmojiPack.vue @@ -25,15 +25,26 @@ :label="$t('settings.fallbackSrcSha')"> {{ pack.pack["fallback-src-sha256"] }} - - {{ $t('settings.savePackMetadata') }} + + {{ $t('settings.savePackMetadata') }} + {{ $t('settings.deletePack') }} + + + + {{ $t('settings.downloadPackArchive') }} + - - + + - + + +

+ {{ $t('settings.thisWillDownload') }} "{{ name }}" {{ $t('settings.downloadToCurrentInstance') }} + "{{ downloadSharedAs.trim() === '' ? name : downloadSharedAs }}" ({{ $t('settings.canBeChanged') }}). + {{ $t('settings.willBeUsable') }}. +

+
+ + + {{ $t('settings.downloadSharedPack') }} + +
+
- - + + From 117c3ca644de05f936c09e1b288ec3f597e02449 Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Thu, 10 Oct 2019 04:01:31 +0300 Subject: [PATCH 20/62] Add background for collapse headers --- src/views/emojiPacks/components/EmojiPack.vue | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/views/emojiPacks/components/EmojiPack.vue b/src/views/emojiPacks/components/EmojiPack.vue index 3f819de8..a04ea5d1 100644 --- a/src/views/emojiPacks/components/EmojiPack.vue +++ b/src/views/emojiPacks/components/EmojiPack.vue @@ -1,5 +1,5 @@
diff --git a/src/components/Tinymce/components/editorImage.vue b/src/components/Tinymce/components/editorImage.vue deleted file mode 100644 index 93b211d0..00000000 --- a/src/components/Tinymce/components/editorImage.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - - - diff --git a/src/components/Tinymce/index.vue b/src/components/Tinymce/index.vue deleted file mode 100644 index d9f81acc..00000000 --- a/src/components/Tinymce/index.vue +++ /dev/null @@ -1,210 +0,0 @@ -