Commit 9206538f authored by feld's avatar feld

Merge branch 'feature/update-server-configuration' into 'develop'

Update server configuration

See merge request pleroma/admin-fe!65
parents d8fdf187 306c79ea
......@@ -2,6 +2,15 @@ import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function fetchDescription(authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/config/descriptions`,
method: 'get',
headers: authHeaders(token)
})
}
export async function fetchSettings(authHost, token) {
return await request({
baseURL: baseName(authHost),
......@@ -21,6 +30,16 @@ export async function updateSettings(configs, authHost, token) {
})
}
export async function removeSettings(configs, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/config`,
method: 'post',
headers: authHeaders(token),
data: { configs }
})
}
export async function uploadMedia(file, authHost, token) {
const formData = new FormData()
formData.append('file', file)
......
......@@ -17,86 +17,6 @@ const getters = {
errorLogs: state => state.errorLog.logs,
users: state => state.users.fetchedUsers,
authHost: state => state.user.authHost,
activityPub: state => state.settings.settings['activitypub'],
adminToken: state => state.settings.settings['admin_token'],
assets: state => state.settings.settings['assets'],
auth: state => state.settings.settings['auth'],
autoLinker: state => state.settings.settings['auto_linker'],
captcha: state => state.settings.settings['Pleroma.Captcha'],
chat: state => state.settings.settings['chat'],
consoleLogger: state => state.settings.settings['console'],
corsPlugCredentials: state => state.settings.settings['credentials'],
corsPlugExpose: state => state.settings.settings['expose'],
corsPlugHeaders: state => state.settings.settings['headers'],
corsPlugMaxAge: state => state.settings.settings['max_age'],
corsPlugMethods: state => state.settings.settings['methods'],
database: state => state.settings.settings['database'],
ectoRepos: state => state.settings.settings['ecto_repos'],
emailNotifications: state => state.settings.settings['email_notifications'],
emoji: state => state.settings.settings['emoji'],
enabled: state => state.settings.settings['enabled'],
endpoint: state => state.settings.settings['Pleroma.Web.Endpoint'],
exsyslogger: state => state.settings.settings['ex_syslogger'],
facebook: state => state.settings.settings['Ueberauth.Strategy.Facebook.OAuth'],
fetchInitialPosts: state => state.settings.settings['fetch_initial_posts'],
formatEncoders: state => state.settings.settings['format_encoders'],
frontend: state => state.settings.settings['frontend_configurations'],
google: state => state.settings.settings['Ueberauth.Strategy.Google.OAuth'],
gopher: state => state.settings.settings['gopher'],
hackneyPools: state => state.settings.settings['hackney_pools'],
handler: state => state.settings.settings['handler'],
http: state => state.settings.settings['http'],
httpSecurity: state => state.settings.settings['http_security'],
instance: state => state.settings.settings['instance'],
instances: state => state.peers.fetchedPeers,
kocaptcha: state => state.settings.settings['Pleroma.Captcha.Kocaptcha'],
level: state => state.settings.settings['level'],
ldap: state => state.settings.settings['ldap'],
loggerBackends: state => state.settings.settings['backends'],
mailer: state => state.settings.settings['Pleroma.Emails.Mailer'],
markup: state => state.settings.settings['markup'],
mediaProxy: state => state.settings.settings['media_proxy'],
meta: state => state.settings.settings['meta'],
metadata: state => state.settings.settings['Pleroma.Web.Metadata'],
microsoft: state => state.settings.settings['Ueberauth.Strategy.Microsoft.OAuth'],
mimeTypesConfig: state => state.settings.settings['types'],
mrfHellthread: state => state.settings.settings['mrf_hellthread'],
mrfKeyword: state => state.settings.settings['mrf_keyword'],
mrfMention: state => state.settings.settings['mrf_mention'],
mrfNormalizeMarkup: state => state.settings.settings['mrf_normalize_markup'],
mrfRejectnonpublic: state => state.settings.settings['mrf_rejectnonpublic'],
mrfSimple: state => state.settings.settings['mrf_simple'],
mrfSubchain: state => state.settings.settings['mrf_subchain'],
mrfUserAllowlist: state => state.settings.settings['mrf_user_allowlist'],
mrfVocabulary: state => state.settings.settings['mrf_vocabulary'],
oauth2: state => state.settings.settings['oauth2'],
passwordAuthenticator: state => state.settings.settings['password_authenticator'],
pleromaAuthenticator: state => state.settings.settings['Pleroma.Web.Auth.Authenticator'],
pleromaRepo: state => state.settings.settings['Pleroma.Repo'],
pleromaUser: state => state.settings.settings['Pleroma.User'],
port: state => state.settings.settings['port'],
privDir: state => state.settings.settings['priv_dir'],
queues: state => state.settings.settings['queues'],
rateLimiters: state => state.settings.settings['rate_limit'],
retryQueue: state => state.settings.settings['Pleroma.Web.Federator.RetryQueue'],
richMedia: state => state.settings.settings['rich_media'],
suggestions: state => state.settings.settings['suggestions'],
scheduledActivity: state => state.settings.settings['Pleroma.ScheduledActivity'],
statuses: state => state.status.fetchedStatuses,
teslaAdapter: state => state.settings.settings['adapter'],
twitter: state => state.settings.settings['Ueberauth.Strategy.Twitter.OAuth'],
ueberauth: state => state.settings.settings['Ueberauth'],
uploadAnonymizeFilename: state => state.settings.settings['Pleroma.Upload.Filter.AnonymizeFilename'],
upload: state => state.settings.settings['Pleroma.Upload'],
uploadFilterMogrify: state => state.settings.settings['Pleroma.Upload.Filter.Mogrify'],
uploadersLocal: state => state.settings.settings['Pleroma.Uploaders.Local'],
uploadMDII: state => state.settings.settings['Pleroma.Uploaders.MDII'],
uploadS3: state => state.settings.settings['Pleroma.Uploaders.S3'],
uriSchemes: state => state.settings.settings['uri_schemes'],
user: state => state.settings.settings['user'],
userEmail: state => state.settings.settings['Pleroma.Emails.UserEmail'],
vapidDetails: state => state.settings.settings['vapid_details'],
webhookUrl: state => state.settings.settings['webhook_url']
settings: state => state.settings
}
export default getters
This diff is collapsed.
import i18n from '@/lang'
import { fetchSettings, updateSettings, uploadMedia } from '@/api/settings'
import { filterIgnored, parseTuples, valueHasTuples, wrapConfig } from './normalizers'
import { Message } from 'element-ui'
import { fetchDescription, fetchSettings, removeSettings, updateSettings, uploadMedia } from '@/api/settings'
import { checkPartialUpdate, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers'
const settings = {
state: {
description: [],
settings: {
'activitypub': {},
'adapter': {},
'admin_token': {},
'assets': { mascots: {}},
'auth': {},
'auto_linker': { opts: {}},
'backends': {},
'chat': {},
'console': { colors: {}},
'credentials': {},
'database': {},
'ecto_repos': {},
'email_notifications': { digest: {}},
'emoji': { groups: {}},
'enabled': {},
'ex_syslogger': {},
'expose': {},
'fetch_initial_posts': {},
'format_encoders': {},
'frontend_configurations': { pleroma_fe: {}, masto_fe: {}},
'gopher': {},
'hackney_pools': { federation: {}, media: {}, upload: {}},
'handler': {},
'headers': {},
'http': { adapter: {}},
'http_security': {},
'instance': { poll_limits: {}},
'level': {},
'ldap': {},
'markup': {},
'max_age': {},
'media_proxy': { proxy_opts: {}},
'meta': {},
'methods': {},
'mrf_hellthread': {},
'mrf_keyword': { replace: {}},
'mrf_mention': {},
'mrf_normalize_markup': {},
'mrf_rejectnonpublic': {},
'mrf_simple': {},
'mrf_subchain': { match_actor: {}},
'mrf_user_allowlist': {},
'mrf_vocabulary': {},
'oauth2': {},
'password_authenticator': {},
'Pleroma.Captcha': {},
'Pleroma.Captcha.Kocaptcha': {},
'Pleroma.Emails.Mailer': {},
'Pleroma.Emails.UserEmail': { styling: {}},
'Pleroma.Repo': {},
'Pleroma.ScheduledActivity': {},
'Pleroma.Upload': { proxy_opts: {}},
'Pleroma.Upload.Filter.AnonymizeFilename': {},
'Pleroma.Upload.Filter.Mogrify': {},
'Pleroma.Uploaders.Local': {},
'Pleroma.Uploaders.MDII': {},
'Pleroma.Uploaders.S3': {},
'Pleroma.User': {},
'Pleroma.Web.Auth.Authenticator': {},
'Pleroma.Web.Endpoint':
{ http: false, url: {}, render_errors: {}, pubsub: {}},
'Pleroma.Web.Federator.RetryQueue': {},
'Pleroma.Web.Metadata': {},
'port': {},
'priv_dir': {},
'queues': {},
'rate_limit': {},
'rich_media': {},
'suggestions': {},
'types': { value: {}},
'Ueberauth': {},
'Ueberauth.Strategy.Facebook.OAuth': {},
'Ueberauth.Strategy.Google.OAuth': {},
'Ueberauth.Strategy.Microsoft.OAuth': {},
'Ueberauth.Strategy.Twitter.OAuth': {},
'user': {},
'uri_schemes': {},
'vapid_details': {},
'webhook_url': {}
':auto_linker': {},
':cors_plug': {},
':esshd': {},
':http_signatures': {},
':logger': {},
':mime': {},
':phoenix': {},
':pleroma': {},
':prometheus': {},
':quack': {},
':tesla': {},
':ueberauth': {},
':web_push_encryption': {}
},
updatedSettings: {},
ignoredIfNotEnabled: ['enabled', 'handler', 'password_authenticator', 'port', 'priv_dir'],
loading: true
},
mutations: {
REWRITE_CONFIG: (state, { tab, data }) => {
state.settings[tab] = data
CLEAR_UPDATED_SETTINGS: (state) => {
state.updatedSettings = {}
},
SET_DESCRIPTION: (state, data) => {
state.description = data
},
SET_LOADING: (state, status) => {
state.loading = status
},
SET_SETTINGS: (state, data) => {
const newSettings = data.reduce((acc, config) => {
const key = config.key[0] === ':' ? config.key.substr(1) : config.key
const value = valueHasTuples(key, config.value) ? { value: config.value } : parseTuples(config.value, key)
acc[key] = { ...acc[key], ...value }
const newSettings = data.reduce((acc, { group, key, value }) => {
const parsedValue = valueHasTuples(key, value)
? { value: parseNonTuples(key, value) }
: parseTuples(value, key)
acc[group][key] = { ...acc[group][key], ...parsedValue }
return acc
}, state.settings)
state.settings = newSettings
},
UPDATE_SETTINGS: (state, { tab, data }) => {
Object.keys(state.settings).map(configName => {
if (configName === tab) {
state.settings[configName] = { ...state.settings[configName], ...data }
}
})
UPDATE_SETTINGS: (state, { group, key, input, value, type }) => {
const updatedSetting = !state.updatedSettings[group] || (key === 'Pleroma.Emails.Mailer' && input === ':adapter')
? { [key]: { [input]: [type, value] }}
: { [key]: { ...state.updatedSettings[group][key], ...{ [input]: [type, value] }}}
state.updatedSettings[group] = { ...state.updatedSettings[group], ...updatedSetting }
},
UPDATE_STATE: (state, { group, key, input, value }) => {
const updatedState = key === 'Pleroma.Emails.Mailer' && input === ':adapter'
? { [key]: { [input]: value }}
: { [key]: { ...state.settings[group][key], ...{ [input]: value }}}
state.settings[group] = { ...state.settings[group], ...updatedState }
}
},
actions: {
async FetchSettings({ commit, dispatch, getters }) {
async FetchSettings({ commit, getters }) {
commit('SET_LOADING', true)
const response = await fetchSettings(getters.authHost, getters.token)
const description = await fetchDescription(getters.authHost, getters.token)
commit('SET_DESCRIPTION', description.data)
commit('SET_SETTINGS', response.data.configs)
commit('SET_LOADING', false)
},
RewriteConfig({ commit }, { tab, data }) {
commit('REWRITE_CONFIG', { tab, data })
async RemoveSetting({ getters }, configs) {
await removeSettings(configs, getters.authHost, getters.token)
},
async SubmitChanges({ getters, commit, state }, data) {
const filteredSettings = filterIgnored(state.settings, state.ignoredIfNotEnabled)
const configs = data || wrapConfig(filteredSettings)
try {
const response = await updateSettings(configs, getters.authHost, getters.token)
commit('SET_SETTINGS', response.data.configs)
} catch (_e) {
return
}
Message({
message: i18n.t('settings.success'),
type: 'success',
duration: 5 * 1000
})
async SubmitChanges({ getters, commit, state }) {
const updatedData = checkPartialUpdate(state.settings, state.updatedSettings, state.description)
const configs = Object.keys(updatedData).reduce((acc, group) => {
return [...acc, ...wrapUpdatedSettings(group, updatedData[group], state.settings)]
}, [])
const response = await updateSettings(configs, getters.authHost, getters.token)
commit('SET_SETTINGS', response.data.configs)
commit('CLEAR_UPDATED_SETTINGS')
},
UpdateSettings({ commit }, { tab, data }) {
commit('UPDATE_SETTINGS', { tab, data })
UpdateSettings({ commit }, { group, key, input, value, type }) {
key
? commit('UPDATE_SETTINGS', { group, key, input, value, type })
: commit('UPDATE_SETTINGS', { group, key: input, input: '_value', value, type })
},
UpdateState({ commit, dispatch, state }, { group, key, input, value }) {
if (key === 'Pleroma.Emails.Mailer' && input === ':adapter') {
const subkeys = Object.keys(state.settings[group][key]).filter(el => el !== ':adapter')
const emailsValue = subkeys.map(el => {
return { 'tuple': [el, state.settings[group][key][el]] }
})
dispatch('RemoveSetting', [{ group, key, value: emailsValue, delete: true, subkeys }])
}
key
? commit('UPDATE_STATE', { group, key, input, value })
: commit('UPDATE_STATE', { group, key: input, input: 'value', value })
},
async UploadMedia({ dispatch, getters, state }, { file, tab, inputName, childName }) {
const response = await uploadMedia(file, getters.authHost, getters.token)
......
......@@ -70,7 +70,7 @@ export default {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
localPacks() {
return this.$store.state.emojiPacks.localPacks
......
<template>
<div>
<el-form ref="activityPub" :model="activityPub" :label-width="labelWidth">
<el-form-item label="Unfollow blocked">
<el-switch :value="activityPub.unfollow_blocked" @change="updateSetting($event, 'activitypub', 'unfollow_blocked')"/>
<p class="expl">Whether blocks result in people getting unfollowed</p>
</el-form-item>
<el-form-item label="Outgoing blocks">
<el-switch :value="activityPub.outgoing_blocks" @change="updateSetting($event, 'activitypub', 'outgoing_blocks')"/>
<p class="expl">Whether to federate blocks to other instances</p>
</el-form-item>
<el-form-item label="Follow handshake timeout">
<el-input-number
:value="activityPub.follow_handshake_timeout"
:step="100"
:min="0"
size="large"
class="top-margin"
@change="updateSetting($event, 'activitypub', 'follow_handshake_timeout')"/>
</el-form-item>
<el-form-item label="Sign object fetches">
<el-switch :value="activityPub.sign_object_fetches" @change="updateSetting($event, 'activitypub', 'sign_object_fetches')"/>
<p class="expl">Sign object fetches with HTTP signatures</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="activitypubData" :model="activitypubData" :label-width="labelWidth">
<setting :setting-group="activitypub" :data="activitypubData"/>
</el-form>
<el-form ref="user" :model="user" :label-width="labelWidth">
<el-form-item label="Deny follow blocked">
<el-switch :value="user.deny_follow_blocked" @change="updateSetting($event, 'user', 'deny_follow_blocked')"/>
<p class="expl">Whether to disallow following an account that has blocked the user in question</p>
</el-form-item>
<div class="line"/>
<el-form ref="userData" :model="userData" :label-width="labelWidth">
<setting :setting-group="user" :data="userData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
......@@ -37,27 +15,49 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'ActivityPub',
components: { Setting },
computed: {
...mapGetters([
'activityPub',
'user'
'settings'
]),
activitypub() {
return this.settings.description.find(setting => setting.key === ':activitypub')
},
activitypubData() {
return this.settings.settings[':pleroma'][':activitypub']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.$store.state.settings.loading
},
user() {
return this.settings.description.find(setting => setting.key === ':user')
},
userData() {
return this.settings.settings[':pleroma'][':user']
}
},
methods: {
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')
})
}
}
}
......
<template>
<el-form v-if="!loading" ref="autoLinker" :model="autoLinker" :label-width="labelWidth">
<el-form-item label="Class">
<el-switch :value="booleanClass" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'class')"/>
<p v-if="!booleanClass" class="expl">Specify the class to be added to the generated link. False to clear.</p>
</el-form-item>
<el-form-item v-if="booleanClass">
<el-input :value="getStringValue('class')" @input="processTwoTypeValue($event, 'auto_linker', 'opts', 'class')"/>
<p class="expl">Specify the class to be added to the generated link. False to clear.</p>
</el-form-item>
<el-form-item label="Rel">
<el-switch :value="booleanRel" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'rel')"/>
<p v-if="!booleanRel" class="expl">Override the rel attribute. False to clear</p>
</el-form-item>
<el-form-item v-if="booleanRel">
<el-input :value="getStringValue('rel')" @input="processTwoTypeValue($event, 'auto_linker', 'opts', 'rel')"/>
<p class="expl">Override the rel attribute. False to clear</p>
</el-form-item>
<el-form-item label="New window">
<el-switch :value="autoLinker.opts.new_window" @change="processNestedData($event, 'auto_linker', 'opts', 'new_window')"/>
<p class="expl">Set to false to remove <span class="code">target='_blank'</span> attribute</p>
</el-form-item>
<el-form-item label="Scheme">
<el-switch :value="autoLinker.opts.scheme" @change="processNestedData($event, 'auto_linker', 'opts', 'scheme')"/>
<p class="expl">Set to true to link urls with schema <span class="code">http://google.com</span></p>
</el-form-item>
<el-form-item label="Truncate">
<el-switch :value="booleanTruncate" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'truncate')"/>
<p v-if="!booleanTruncate" class="expl">Set to a number to truncate urls longer then the number.
Truncated urls will end in <span class="code">..</span></p>
</el-form-item>
<el-form-item v-if="booleanTruncate">
<el-input-number :value="getStringValue('truncate')" :step="1" :min="0" size="large" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'truncate')"/>
<p class="expl">Specify the class to be added to the generated link. False to clear.</p>
</el-form-item>
<el-form-item label="Strip prefix">
<el-switch :value="autoLinker.opts.strip_prefix" @change="processNestedData($event, 'auto_linker', 'opts', 'strip_prefix')"/>
<p class="expl">Strip the scheme prefix</p>
</el-form-item>
<el-form-item label="Extra">
<el-switch :value="autoLinker.opts.extra" @change="processNestedData($event, 'auto_linker', 'opts', 'extra')"/>
<p class="expl">Link urls with rarely used schemes (magnet, ipfs, irc, etc.)</p>
</el-form-item>
<el-form-item label="Validate TLD">
<el-switch :value="autoLinker.opts.validate_tld" @change="processNestedData($event, 'auto_linker', 'opts', 'validate_tld')"/>
</el-form-item>
<el-form v-if="!loading" ref="autoLinker" :model="autoLinkerData" :label-width="labelWidth">
<setting :setting-group="autoLinker" :data="autoLinkerData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
......@@ -52,62 +9,43 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'AutoLinker',
components: { Setting },
computed: {
...mapGetters([
'autoLinker'
'settings'
]),
autoLinker() {
return this.settings.description.find(setting => setting.key === ':opts')
},
autoLinkerData() {
return this.settings.settings[':auto_linker'][':opts']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.$store.state.settings.loading
},
booleanClass() {
return this.getBooleanValue('class')
},
booleanRel() {
return this.getBooleanValue('rel')
},
booleanTruncate() {
return this.getBooleanValue('truncate')
return this.settings.loading
}
},
methods: {
getBooleanValue(name) {
const value = this.autoLinker.opts[name]
return typeof value === 'string' || typeof value === 'number'
},
getNumValue(name) {
const value = this.autoLinker.opts[name]
return value || 0
},
getStringValue(name) {
const value = this.autoLinker.opts[name]
return value || ''
},
processTwoTypeValue(value, tab, inputName, childName) {
if (value === true) {
const data = childName === 'truncate' ? 0 : ''
this.processNestedData(data, tab, inputName, childName)
} else {
this.processNestedData(value, tab, inputName, childName)
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
},
processNestedData(value, tab, inputName, childName) {
const updatedValue = { ...this.$store.state.settings.settings[tab][inputName], ...{ [childName]: value }}
this.updateSetting(updatedValue, tab, inputName)
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
......
<template>
<div>
<el-form ref="captcha" :model="captcha" :label-width="labelWidth">
<el-form-item label="Enabled">
<el-switch :value="captcha.enabled" @change="updateSetting($event, 'Pleroma.Captcha', 'enabled')"/>