diff --git a/CHANGELOG.md b/CHANGELOG.md index 734a5ed5..e368ae70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,26 +8,36 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed -- moves emoji pack configuration from the main menu to settings tab, redesigns it and fixes bugs +- **breaking** PleromaFE login feature relies on `admin` scope presence in PleromaFE token (older versions of PleromaFE don't support it) +- Moves emoji pack configuration from the main menu to settings tab, redesigns it and fixes bugs - `mailerEnabled` must be set to `true` in order to require password reset (password reset currently only works via email) -- remove fetching initial data for configuring server settings +- Remove fetching initial data for configuring server settings - Actions in users module (ActivateUsers, AddRight, DeactivateUsers, DeleteRight, DeleteUsers) now accept an array of users instead of one user - Leave dropdown menu open after clicking an action +- Move current try/catch error handling from view files to module, add it where necessary ### Added - Optimistic update for actions in users module and fetching users after api function finished its execution - Relay management +- Ability to fetch all statuses from a given instance +- Grouped reports: now you can view reports, which are grouped by status (pagination is not implemented yet, though) +- Ability to confirm users' emails and resend confirmation emails +- Report notes +- Ability to moderate users on the statuses page ### Fixed - Show checkmarks when tag is applied +- Reports update (also, now it's optimistic) +- Remove duplicated success message ## [1.2.0] - 2019-09-27 ### Added - Emoji pack configuration +- Statuses page: fetch all statuses from a given instance - Ability to require user's password reset – Ability to track admin/moderator actions, a.k.a. "the moderation log" diff --git a/README.md b/README.md index f6b359c6..84ce067d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,13 @@ Admin UI for pleroma instance owners. +### Branches + +There are two main branches here: + +- `develop`: ongoing work and all merge requests go here, *unstable* +- `master`: after `develop` is stabilized it is merged to `master`, `master` is *stable*, allegedly + ### 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 @@ -18,13 +25,17 @@ You can have any combination of these features (i.e. you can disable anything, b ## Usage +### Bundled + +AdminFE is bundled with Pleroma, i.e. you can just visit `https://your.instance/pleroma/admin/` to try it out. + ### Development To run AdminFE locally execute `yarn dev` ### Build -To compile everything for production run `yarn build:prod`. +To compile everything for production run `yarn build:prod`, this will build admin-fe into `dist` folder, which you will need to upload to your server and/or point your webserver of choice to. #### Disabling features diff --git a/index.html b/index.html index 2f5a3a50..20e31863 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,6 @@ Admin FE -
diff --git a/package.json b/package.json index 0327b45b..73c2fc16 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "driver.js": "0.8.1", "dropzone": "5.2.0", "echarts": "4.1.0", - "element-ui": "^2.10.0", + "element-ui": "^2.13.0", "file-saver": "1.3.8", "fuse.js": "3.4.2", "js-cookie": "2.2.0", diff --git a/src/api/__mocks__/login.js b/src/api/__mocks__/login.js index 4ce54a8f..a980f88b 100644 --- a/src/api/__mocks__/login.js +++ b/src/api/__mocks__/login.js @@ -8,7 +8,7 @@ export async function loginByUsername(username, password, authHost) { const verifyHost = user.authHost === authHost const data = { 'token_type': 'Bearer', - 'scope': 'read write follow', + 'scope': 'read write follow push admin', 'refresh_token': 'foo123', 'me': 'bob', 'expires_in': 600, diff --git a/src/api/__mocks__/reports.js b/src/api/__mocks__/reports.js index a337df9a..ba4e412e 100644 --- a/src/api/__mocks__/reports.js +++ b/src/api/__mocks__/reports.js @@ -11,20 +11,42 @@ const reports = [ { created_at: '2019-05-18T13:01:33.000Z', account: { acct: 'nick', display_name: 'Nick Keys', tags: [] }, actor: { acct: 'admin' }, state: 'closed', id: '4', content: '', statuses: [] } ] -export async function fetchReports(limit, max_id, authHost, token) { - const paginatedReports = max_id.length > 0 ? reports.slice(5) : reports.slice(0, 5) - return Promise.resolve({ data: { reports: paginatedReports }}) +const groupedReports = [ + { account: { avatar: 'http://localhost:4000/images/avi.png', confirmation_pending: false, deactivated: false, display_name: 'leo', id: '9oG0YghgBi94EATI9I', local: true, nickname: 'leo', roles: { admin: false, moderator: false }, tags: [] }, + actors: [{ acct: 'admin', avatar: 'http://localhost:4000/images/avi.png', deactivated: false, display_name: 'admin', id: '9oFz4pTauG0cnJ581w', local: true, nickname: 'admin', roles: { admin: false, moderator: false }, tags: [], url: 'http://localhost:4000/users/admin', username: 'admin' }], + date: '2019-11-23T12:56:11.969772Z', + reports: [ + { created_at: '2019-05-21T21:35:33.000Z', account: { acct: 'benj', display_name: 'Benjamin Fame', tags: [] }, actor: { acct: 'admin' }, state: 'open', id: '2', content: 'This is a report', statuses: [] }, + { created_at: '2019-05-20T22:45:33.000Z', account: { acct: 'alice', display_name: 'Alice Pool', tags: [] }, actor: { acct: 'admin2' }, state: 'resolved', id: '7', content: 'Please block this user', statuses: [ + { account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'public', sensitive: false, id: '11', content: 'Hey!', url: '', created_at: '2019-05-10T21:35:33.000Z' }, + { account: { display_name: 'Alice Pool', avatar: '' }, visibility: 'unlisted', sensitive: true, id: '10', content: 'Bye!', url: '', created_at: '2019-05-10T21:00:33.000Z' } + ] } + ], + status: { + account: { acct: 'leo' }, + content: 'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis', + created_at: '2019-11-23T12:55:20.000Z', + id: '9pFoQO69piu7cUDnJg', + url: 'http://localhost:4000/notice/9pFoQO69piu7cUDnJg', + visibility: 'unlisted', + sensitive: true + }, + status_deleted: false + } +] + +export async function fetchReports(filter, page, pageSize, authHost, token) { + return filter.length > 0 + ? Promise.resolve({ data: { reports: reports.filter(report => report.state === filter) }}) + : Promise.resolve({ data: { reports }}) } -export async function filterReports(filter, limit, max_id, authHost, token) { - const filteredReports = reports.filter(report => report.state === filter) - const paginatedReports = max_id.length > 0 ? filteredReports.slice(5) : filteredReports.slice(0, 5) - return Promise.resolve({ data: { reports: paginatedReports }}) +export async function fetchGroupedReports(authHost, token) { + return Promise.resolve({ data: { reports: groupedReports }}) } -export async function changeState(state, id, authHost, token) { - const report = reports.find(report => report.id === id) - return Promise.resolve({ data: { ...report, state }}) +export async function changeState(reportsData, authHost, token) { + return Promise.resolve({ data: '' }) } export async function changeStatusScope(id, sensitive, visibility, authHost, token) { diff --git a/src/api/__mocks__/status.js b/src/api/__mocks__/status.js new file mode 100644 index 00000000..0e7bc9fb --- /dev/null +++ b/src/api/__mocks__/status.js @@ -0,0 +1,7 @@ +export async function changeStatusScope(id, sensitive, visibility, authHost, token) { + return Promise.resolve() +} + +export async function deleteStatus(id, authHost, token) { + return Promise.resolve() +} diff --git a/src/api/__mocks__/users.js b/src/api/__mocks__/users.js index b5aca6a1..31657293 100644 --- a/src/api/__mocks__/users.js +++ b/src/api/__mocks__/users.js @@ -4,6 +4,10 @@ export let users = [ { active: false, deactivated: true, id: 'abc', nickname: 'john', local: true, external: false, roles: { admin: false, moderator: false }, tags: ['strip_media'] } ] +const userProfile = { avatar: 'avatar.jpg', display_name: 'Allis', nickname: 'allis', id: '2', tags: [], roles: { admin: true, moderator: false }, local: true, external: false } + +const userStatuses = [] + const filterUsers = (str) => { const filters = str.split(',').filter(item => item.length > 0) if (filters.length === 0) { @@ -20,6 +24,10 @@ const filterUsers = (str) => { return applyFilters([], filters, users) } +export async function fetchUser(id, authHost, token) { + return Promise.resolve({ data: userProfile }) +} + export async function fetchUsers(filters, authHost, token, page = 1) { const filteredUsers = filterUsers(filters) return Promise.resolve({ data: { @@ -29,6 +37,10 @@ export async function fetchUsers(filters, authHost, token, page = 1) { }}) } +export async function fetchUserStatuses(id, authHost, godmode, token) { + return Promise.resolve({ data: userStatuses }) +} + export async function getPasswordResetToken(nickname, authHost, token) { return Promise.resolve({ data: { token: 'g05lxnBJQnL', link: 'http://url/api/pleroma/password_reset/g05lxnBJQnL' }}) } diff --git a/src/api/login.js b/src/api/login.js index d5ef3b38..28421f29 100644 --- a/src/api/login.js +++ b/src/api/login.js @@ -9,7 +9,7 @@ export async function loginByUsername(username, password, authHost) { data: { client_name: `AdminFE_${Math.random()}`, redirect_uris: `${window.location.origin}/oauth-callback`, - scopes: 'read write follow' + scopes: 'read write follow push admin' } }) diff --git a/src/api/peers.js b/src/api/peers.js new file mode 100644 index 00000000..4b80d7ab --- /dev/null +++ b/src/api/peers.js @@ -0,0 +1,14 @@ +import request from '@/utils/request' +import { getToken } from '@/utils/auth' +import { baseName } from './utils' + +export async function fetchPeers(authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/v1/instance/peers`, + method: 'get', + headers: authHeaders(token) + }) +} + +const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} diff --git a/src/api/reports.js b/src/api/reports.js index c84a5bfd..3454da2f 100644 --- a/src/api/reports.js +++ b/src/api/reports.js @@ -2,51 +2,54 @@ import request from '@/utils/request' import { getToken } from '@/utils/auth' import { baseName } from './utils' -export async function changeState(state, id, authHost, token) { +export async function changeState(reports, authHost, token) { return await request({ baseURL: baseName(authHost), - url: `/api/pleroma/admin/reports/${id}`, - method: 'put', + url: `/api/pleroma/admin/reports`, + method: 'patch', headers: authHeaders(token), - data: { state } + data: { reports } }) } -export async function changeStatusScope(id, sensitive, visibility, authHost, token) { +export async function fetchReports(filter, page, pageSize, authHost, token) { + const url = filter.length > 0 + ? `/api/pleroma/admin/reports?state=${filter}&page=${page}&page_size=${pageSize}` + : `/api/pleroma/admin/reports?page=${page}&page_size=${pageSize}` return await request({ baseURL: baseName(authHost), - url: `/api/pleroma/admin/statuses/${id}`, - method: 'put', - headers: authHeaders(token), - data: { sensitive, visibility } - }) -} - -export async function deleteStatus(id, authHost, token) { - return await request({ - baseURL: baseName(authHost), - url: `/api/pleroma/admin/statuses/${id}`, - method: 'delete', - headers: authHeaders(token) - }) -} - -export async function fetchReports(limit, max_id, authHost, token) { - return await request({ - baseURL: baseName(authHost), - url: `/api/pleroma/admin/reports?limit=${limit}&max_id=${max_id}`, + url, method: 'get', headers: authHeaders(token) }) } -export async function filterReports(filter, limit, max_id, authHost, token) { +export async function fetchGroupedReports(authHost, token) { return await request({ baseURL: baseName(authHost), - url: `/api/pleroma/admin/reports?state=${filter}&limit=${limit}&max_id=${max_id}`, + url: `/api/pleroma/admin/grouped_reports`, method: 'get', headers: authHeaders(token) }) } +export async function createNote(content, reportID, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/reports/${reportID}/notes`, + method: `post`, + headers: authHeaders(token), + data: { content } + }) +} + +export async function deleteNote(noteID, reportID, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/reports/${reportID}/notes/${noteID}`, + method: `delete`, + headers: authHeaders(token) + }) +} + const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} diff --git a/src/api/status.js b/src/api/status.js new file mode 100644 index 00000000..ab9334e5 --- /dev/null +++ b/src/api/status.js @@ -0,0 +1,33 @@ +import request from '@/utils/request' +import { getToken } from '@/utils/auth' +import { baseName } from './utils' + +export async function changeStatusScope(id, sensitive, visibility, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/statuses/${id}`, + method: 'put', + headers: authHeaders(token), + data: { sensitive, visibility } + }) +} + +export async function deleteStatus(id, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/statuses/${id}`, + method: 'delete', + headers: authHeaders(token) + }) +} + +export async function fetchStatusesByInstance(instance, authHost, token, pageSize, page = 1) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/instances/${instance}/statuses?page=${page}&page_size=${pageSize}`, + method: 'get', + headers: authHeaders(token) + }) +} + +const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} diff --git a/src/api/users.js b/src/api/users.js index fb168d6c..3755ee7c 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -136,4 +136,24 @@ export async function fetchUserStatuses(id, authHost, godmode, token) { }) } +export async function confirmUserEmail(nicknames, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: '/api/pleroma/admin/users/confirm_email', + method: 'patch', + headers: authHeaders(token), + data: { nicknames } + }) +} + +export async function resendConfirmationEmail(nicknames, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: '/api/pleroma/admin/users/resend_confirmation_email', + method: 'patch', + headers: authHeaders(token), + data: { nicknames } + }) +} + const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} diff --git a/src/components/Status/index.vue b/src/components/Status/index.vue new file mode 100644 index 00000000..5ff766b4 --- /dev/null +++ b/src/components/Status/index.vue @@ -0,0 +1,278 @@ + + + + + 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 @@ -