Compare commits

...

6 commits

Author SHA1 Message Date
9baa356f56 apply fixes 2022-06-14 19:05:19 +01:00
Haelwenn (lanodan) Monnier
4406dce589
CHANGELOG.md: Release 2.4.0 2021-08-01 08:36:29 +02:00
Haelwenn
a57925fce9 Merge branch 'simplePolicy_reasons_for_instance_specific_policies' into 'develop'
Make MRF and Quarantine work with tuples

See merge request pleroma/admin-fe!187
2021-07-23 03:41:04 +00:00
Ilja
3b7bcea826 Make MRF and Quarantine work with tuples
* I added the keys to normaliser.js
2021-07-23 03:41:03 +00:00
Angelina Filippova
bc5dcfbafb Merge branch 'fix/revert-mod-log' into 'develop'
Revert including links in moderation log messages

Closes #187

See merge request pleroma/admin-fe!204
2021-04-12 22:26:59 +00:00
Angelina Filippova
8b2acaceb5 Revert including links in moderation log messages 2021-04-13 01:18:17 +03:00
10 changed files with 118 additions and 89 deletions

View file

@ -8,6 +8,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
### Changed
### Fixed
## [2.4.0] - 2021-08-01
### Added
- Evicting and banning objects from the MediaProxy cache is disabled if MediaProxy is disabled on the Settings tab. Add ability to enable MediaProxy and Invalidation from MediaProxy tab. - Evicting and banning objects from the MediaProxy cache is disabled if MediaProxy is disabled on the Settings tab. Add ability to enable MediaProxy and Invalidation from MediaProxy tab.
- Allow to upload the custom Terms of Service and Instance Panel HTML pages via Admin API - Allow to upload the custom Terms of Service and Instance Panel HTML pages via Admin API
- Add Report show page and link Moderation log references to the respective reports - Add Report show page and link Moderation log references to the respective reports
@ -26,7 +34,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Hide Tag actions on Users tab if MRF TagPolicy is disabled. Add ability to enable TagPolicy from Moderation menu - Hide Tag actions on Users tab if MRF TagPolicy is disabled. Add ability to enable TagPolicy from Moderation menu
- Move `:restrict_unauthenticated` settings from Authentication tab to Instance tab - Move `:restrict_unauthenticated` settings from Authentication tab to Instance tab
- Replace regular inputs with textareas for setting welcome messages in the Settings section - Replace regular inputs with textareas for setting welcome messages in the Settings section
- Update rendering Moderation Log Messages so that all usernames are links to the pages of the corresponding users in Admin-FE
- Remove Websocket based federation settings - Remove Websocket based federation settings
- Move Settings tab navigation from the tabbed menu to the main sidebar menu. A separate route is created for each tab. - Move Settings tab navigation from the tabbed menu to the main sidebar menu. A separate route is created for each tab.
- Move Emoji packs configuration to the Emoji tab in the Settings section - Move Emoji packs configuration to the Emoji tab in the Settings section

View file

@ -48,7 +48,7 @@ const devWebpackConfig = merge(baseWebpackConfig, {
poll: config.dev.poll poll: config.dev.poll
}, },
headers: { headers: {
'content-security-policy': "script-src 'self' 'unsafe-eval'; base-uri 'self'; frame-ancestors 'none'; img-src 'self' data: https: http:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; manifest-src 'self'" 'content-security-policy': "base-uri 'self'; frame-ancestors 'none'; img-src 'self' data: https: http:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; manifest-src 'self'; script-src 'self';"
} }
}, },
plugins: [ plugins: [
@ -67,12 +67,7 @@ const devWebpackConfig = merge(baseWebpackConfig, {
BASE_URL: devEnv.ASSETS_PUBLIC_PATH + config.dev.assetsSubDirectory, BASE_URL: devEnv.ASSETS_PUBLIC_PATH + config.dev.assetsSubDirectory,
}, },
}), }),
], ]
resolve: {
alias: {
vue: 'vue/dist/vue.js'
}
}
}) })
module.exports = new Promise((resolve, reject) => { module.exports = new Promise((resolve, reject) => {

View file

@ -29,13 +29,13 @@
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/PanJiaChen/vue-element-admin.git" "url": "git+https://akkoma.dev/AkkomaGang/admin-fe.git"
}, },
"resolutions": { "resolutions": {
"prosemirror-model": "1.9.1" "prosemirror-model": "1.9.1"
}, },
"bugs": { "bugs": {
"url": "https://github.com/PanJiaChen/vue-element-admin/issues" "url": "https://akkoma.dev/AkkomaGang/admin-fe/-/issues"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.3.4", "@babel/runtime": "^7.3.4",

View file

@ -28,7 +28,7 @@ const getCurrentValue = (type, value, path) => {
} }
const getValueWithoutKey = (key, [type, value]) => { const getValueWithoutKey = (key, [type, value]) => {
if (prependWithСolon(type, value)) { if (prependWithColon(type, value)) {
return `:${value}` return `:${value}`
} else if (key === ':backends') { } else if (key === ':backends') {
const index = value.findIndex(el => el === ':ex_syslogger') const index = value.findIndex(el => el === ':ex_syslogger')
@ -78,14 +78,25 @@ export const parseTuples = (tuples, key) => {
item.tuple[0] === ':replace' || item.tuple[0] === ':replace' ||
item.tuple[0] === ':retries' || item.tuple[0] === ':retries' ||
(item.tuple[0] === ':headers' && key === 'Pleroma.Web.MediaProxy.Invalidation.Http') || (item.tuple[0] === ':headers' && key === 'Pleroma.Web.MediaProxy.Invalidation.Http') ||
item.tuple[0] === ':crontab')) { item.tuple[0] === ':crontab' ||
item.tuple[0] === ':transparency_exclusions' ||
item.tuple[0] === ':quarantined_instances' ||
key === ':mrf_simple')) {
if (item.tuple[0] === ':crontab') { if (item.tuple[0] === ':crontab') {
accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => { accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => {
return [...acc, { [group.tuple[1]]: { value: group.tuple[0], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] return [...acc, { [group.tuple[1]]: { value: group.tuple[0], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
}, []) }, [])
} else { } else {
accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => { accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => {
/**
* The ':quarantined_instances' and ':mrf_simple' settings have changed to a list of tuples instead of a list of strings.
* This is to have backwards compatibility for instances that still use strings.
*/
if (typeof group === 'string') {
return [...acc, group]
} else {
return [...acc, { [group.tuple[0]]: { value: group.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] return [...acc, { [group.tuple[0]]: { value: group.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
}
}, []) }, [])
} }
} else if (item.tuple[0] === ':icons') { } else if (item.tuple[0] === ':icons') {
@ -102,8 +113,8 @@ export const parseTuples = (tuples, key) => {
accum[item.tuple[0]] = parseNonTuples(item.tuple[0], item.tuple[1]) accum[item.tuple[0]] = parseNonTuples(item.tuple[0], item.tuple[1])
} else if (item.tuple[0] === ':ip_whitelist') { } else if (item.tuple[0] === ':ip_whitelist') {
accum[item.tuple[0]] = item.tuple[1].map(ip => typeof ip === 'string' ? ip : ip.tuple.join('.')) accum[item.tuple[0]] = item.tuple[1].map(ip => typeof ip === 'string' ? ip : ip.tuple.join('.'))
} else if (Array.isArray(item.tuple[1]) && } else if (Array.isArray(item.tuple[1]) && (item.tuple[1][0] !== null &&
(typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) { typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) {
accum[item.tuple[0]] = parseTuples(item.tuple[1], item.tuple[0]) accum[item.tuple[0]] = parseTuples(item.tuple[1], item.tuple[0])
} else if (Array.isArray(item.tuple[1])) { } else if (Array.isArray(item.tuple[1])) {
accum[item.tuple[0]] = item.tuple[1] accum[item.tuple[0]] = item.tuple[1]
@ -156,7 +167,7 @@ const parseStringOrTupleValue = (key, value) => {
} }
} }
const prependWithСolon = (type, value) => { const prependWithColon = (type, value) => {
return (type === 'atom' && value.length > 0) || return (type === 'atom' && value.length > 0) ||
(Array.isArray(type) && type.includes('boolean') && type.includes('atom') && typeof value === 'string') (Array.isArray(type) && type.includes('boolean') && type.includes('atom') && typeof value === 'string')
} }
@ -245,7 +256,7 @@ const wrapValues = (settings, currentState) => {
)) ))
) { ) {
return { 'tuple': [setting, wrapValues(value, currentState)] } return { 'tuple': [setting, wrapValues(value, currentState)] }
} else if (prependWithСolon(type, value)) { } else if (prependWithColon(type, value)) {
return { 'tuple': [setting, `:${value}`] } return { 'tuple': [setting, `:${value}`] }
} else if (type.includes('tuple') && } else if (type.includes('tuple') &&
(type.includes('string') || type.includes('atom') || type.includes('boolean'))) { (type.includes('string') || type.includes('atom') || type.includes('boolean'))) {

View file

@ -55,7 +55,12 @@ export default {
}, },
async handleOpen($event) { async handleOpen($event) {
if ($event === '/settings') { if ($event === '/settings') {
if (!localStorage.getItem('settingsTabs')) { let settingsTabs = localStorage.getItem('settingsTabs')
if (settingsTabs === '[]') {
localStorage.removeItem('settingsTabs')
settingsTabs = null
}
if (!settingsTabs) {
await this.$store.dispatch('FetchSettings') await this.$store.dispatch('FetchSettings')
const menuItems = this.tabs const menuItems = this.tabs
localStorage.setItem('settingsTabs', JSON.stringify(menuItems)) localStorage.setItem('settingsTabs', JSON.stringify(menuItems))

View file

@ -0,0 +1,65 @@
<template>
<span>
<router-link
v-if="propertyExists(actor, 'id')"
:to="{ name: 'UsersShow', params: { id: actor.id }}"
class="router-link">
<span v-if="propertyExists(actor, 'nickname')" style="font-weight: 600">
@{{ actor.nickname }}
</span>
</router-link>
<span v-if="subject.type === 'report' && propertyExists(subject, 'id')">
{{ logEntryMessageWithoutId[0] }}
<router-link
:to="{ name: 'ReportsShow', params: { id: subject.id }}"
class="router-link">
<span style="font-weight: 600">#{{ subject.id }}</span>
</router-link>
{{ logEntryMessageWithoutId[1] }}
</span>
<span v-else>{{ logEntryMessage }}</span>
</span>
</template>
<script>
export default {
name: 'LogEntryMessage',
props: {
actor: {
type: Object,
required: true
},
message: {
type: String,
required: true
},
subject: {
type: [Object, Array],
required: false,
default: function() {
return {}
}
}
},
computed: {
logEntryMessage() {
return this.actor.nickname ? this.message.split(this.actor.nickname)[1] : this.message
},
logEntryMessageWithoutId() {
return this.logEntryMessage.split(`#${this.subject.id}`)
}
},
methods: {
propertyExists(account, property) {
return account[property]
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
.router-link {
text-decoration: none;
}
</style>

View file

@ -1,20 +0,0 @@
<template>
<router-link
:to="{ name: 'ReportsShow', params: { id }}"
class="router-link">
<span style="font-weight: 600">#{{ id }}</span>
</router-link>
</template>
<script>
export default {
name: 'UserLink',
props: {
id: {
type: String,
required: true
}
}
}
</script>

View file

@ -1,20 +0,0 @@
<template>
<router-link
:to="{ name: 'UsersShow', params: { id: actor }}"
class="router-link">
<span style="font-weight: 600">@{{ actor }}</span>
</router-link>
</template>
<script>
export default {
name: 'UserLink',
props: {
actor: {
type: String,
required: true
}
}
}
</script>

View file

@ -43,7 +43,8 @@
v-for="(logEntry, index) in log" v-for="(logEntry, index) in log"
:key="index" :key="index"
:timestamp="normalizeTimestamp(logEntry.time)"> :timestamp="normalizeTimestamp(logEntry.time)">
<component :is="processedMessage(logEntry)"/> <log-entry-message v-if="propertyExists(logEntry.data.actor, 'nickname')" :actor="logEntry.data.actor" :message="logEntry.message" :subject="logEntry.data.subject"/>
<span v-else>{{ logEntry.message }}</span>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
<div class="pagination"> <div class="pagination">
@ -64,14 +65,10 @@ import moment from 'moment'
import _ from 'lodash' import _ from 'lodash'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import RebootButton from '@/components/RebootButton' import RebootButton from '@/components/RebootButton'
import ReportLink from './ReportLink' import LogEntryMessage from './LogEntryMessage'
import UserLink from './UserLink'
import Vue from 'vue'
Vue.component('user-link', UserLink)
Vue.component('report-link', ReportLink)
export default { export default {
components: { RebootButton }, components: { RebootButton, LogEntryMessage },
data() { data() {
return { return {
dateRange: '', dateRange: '',
@ -133,24 +130,6 @@ export default {
normalizeTimestamp(timestamp) { normalizeTimestamp(timestamp) {
return moment(timestamp * 1000).format('YYYY-MM-DD HH:mm') return moment(timestamp * 1000).format('YYYY-MM-DD HH:mm')
}, },
processedMessage(logEntry) {
const html = [...logEntry.message.matchAll(/\@(?<nickname>([\w-]+))/g)].map(res => res.groups.nickname)
.reduce((acc, nickname) => {
return acc.replace(`@${nickname}`, `<user-link actor="${nickname}"/>`)
}, logEntry.message)
if (this.propertyExists(logEntry.data, 'subject') && logEntry.data.subject.type === 'report') {
const updatedHtml = [...html.matchAll(/\#(?<reportId>([\w]+))/g)].map(res => res.groups.reportId)
.reduce((acc, id) => {
return acc.replace(`#${id}`, `<report-link id="${id}"/>`)
}, html)
return {
template: '<div>' + updatedHtml + '</div>'
}
}
return {
template: '<div>' + html + '</div>'
}
},
propertyExists(account, property) { propertyExists(account, property) {
return account[property] return account[property]
} }
@ -194,9 +173,6 @@ h1 {
margin: 0; margin: 0;
width: 145px; width: 145px;
} }
.router-link {
text-decoration: none;
}
.pagination { .pagination {
text-align: center; text-align: center;
} }

View file

@ -87,7 +87,12 @@ export default {
return this.$store.state.app.device === 'desktop' return this.$store.state.app.device === 'desktop'
}, },
keyPlaceholder() { keyPlaceholder() {
return this.setting.key === ':replace' ? 'pattern' : 'key' /**
* We can get 'key_placeholder' from the Pleroma BE. This wasn't always the case.
* We check for the key ':replace' for backwards compatibility for older Pleroma instances who didn't send 'key_placeholder' yet.
* The ':replace' key was the only key where this was needed.
*/
return this.setting.key_placeholder ? this.setting.key_placeholder : (this.setting.key === ':replace' ? 'pattern' : 'key')
}, },
settings() { settings() {
return this.$store.state.settings.settings return this.$store.state.settings.settings
@ -96,7 +101,12 @@ export default {
return this.$store.state.settings.updatedSettings return this.$store.state.settings.updatedSettings
}, },
valuePlaceholder() { valuePlaceholder() {
return this.setting.key === ':replace' ? 'replacement' : 'value' /**
* We can get 'value_placeholder' from the Pleroma BE. This wasn't always the case.
* We check for the key ':replace' for backwards compatibility for older Pleroma instances who didn't send 'value_placeholder' yet.
* The ':replace' key was the only key where this was needed.
*/
return this.setting.value_placeholder ? this.setting.value_placeholder : (this.setting.key === ':replace' ? 'replacement' : 'value')
} }
}, },
methods: { methods: {