Ability to remove settings from db

This commit is contained in:
Angelina Filippova 2020-01-21 18:16:56 +00:00
parent 9206538f61
commit 2d86e26aea
39 changed files with 635 additions and 489 deletions

View file

@ -65,7 +65,6 @@
"vue-i18n": "^8.9.0", "vue-i18n": "^8.9.0",
"vue-router": "3.0.2", "vue-router": "3.0.2",
"vue-splitpane": "1.0.2", "vue-splitpane": "1.0.2",
"vue2-ace-editor": "^0.0.13",
"vuedraggable": "^2.16.0", "vuedraggable": "^2.16.0",
"vuex": "3.0.1", "vuex": "3.0.1",
"xlsx": "^0.11.16" "xlsx": "^0.11.16"

View file

@ -40,16 +40,4 @@ export async function removeSettings(configs, authHost, token) {
}) })
} }
export async function uploadMedia(file, authHost, token) {
const formData = new FormData()
formData.append('file', file)
return await request({
baseURL: baseName(authHost),
url: `/api/v1/media`,
method: 'post',
headers: authHeaders(token),
data: formData
})
}
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {} const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}

View file

@ -339,12 +339,10 @@ export default {
mediaProxy: 'Media Proxy', mediaProxy: 'Media Proxy',
metadata: 'Metadata', metadata: 'Metadata',
gopher: 'Gopher', gopher: 'Gopher',
endpoint: 'Endpoint',
jobQueue: 'Job queue', jobQueue: 'Job queue',
webPush: 'Web push encryption', webPush: 'Web push encryption',
esshd: 'BBS / SSH access', esshd: 'BBS / SSH access',
rateLimiters: 'Rate limiters', rateLimiters: 'Rate limiters',
database: 'Database',
other: 'Other', other: 'Other',
relays: 'Relays', relays: 'Relays',
follow: 'Follow', follow: 'Follow',
@ -383,6 +381,7 @@ export default {
file: 'File', file: 'File',
update: 'Update', update: 'Update',
remove: 'Remove', remove: 'Remove',
removeFromDB: 'Remove setting from the DB',
selectLocalPack: 'Select the local pack to copy to', selectLocalPack: 'Select the local pack to copy to',
localPack: 'Local pack', localPack: 'Local pack',
specifyShortcode: 'Specify a custom shortcode', specifyShortcode: 'Specify a custom shortcode',
@ -405,7 +404,8 @@ export default {
nowNewPacksToImport: 'No new packs to import', nowNewPacksToImport: 'No new packs to import',
successfullyUpdated: 'Successfully updated', successfullyUpdated: 'Successfully updated',
metadatLowerCase: 'metadata', metadatLowerCase: 'metadata',
files: 'files' files: 'files',
successfullyRemoved: 'Setting removed successfully!'
}, },
invites: { invites: {
inviteTokens: 'Invite tokens', inviteTokens: 'Invite tokens',

View file

@ -1,3 +1,5 @@
import _ from 'lodash'
export const checkPartialUpdate = (settings, updatedSettings, description) => { export const checkPartialUpdate = (settings, updatedSettings, description) => {
return Object.keys(updatedSettings).reduce((acc, group) => { return Object.keys(updatedSettings).reduce((acc, group) => {
acc[group] = Object.keys(updatedSettings[group]).reduce((acc, key) => { acc[group] = Object.keys(updatedSettings[group]).reduce((acc, key) => {
@ -20,12 +22,22 @@ export const checkPartialUpdate = (settings, updatedSettings, description) => {
}, {}) }, {})
} }
const getCurrentValue = (object, keys) => { const getCurrentValue = (type, value, path) => {
if (keys.length === 0) { if (type === 'state') {
return object return _.get(value, path)
} else {
const [firstSettingName, ...restKeys] = path
const firstSegment = value[firstSettingName]
if (restKeys.length === 0 || !firstSegment) {
return firstSegment || false
} else {
const secondSegment = (value, keys) => {
const [element, ...rest] = keys
return keys.length === 0 ? value : secondSegment(value[1][element], rest)
}
return secondSegment(firstSegment, restKeys)
}
} }
const [currentKey, ...restKeys] = keys
return getCurrentValue(object[currentKey], restKeys)
} }
const getValueWithoutKey = (key, [type, value]) => { const getValueWithoutKey = (key, [type, value]) => {
@ -136,24 +148,26 @@ export const processNested = (valueForState, valueForUpdatedSettings, group, par
const [{ key, type }, ...otherParents] = parents const [{ key, type }, ...otherParents] = parents
const path = [group, parentKey, ...parents.reverse().map(parent => parent.key).slice(0, -1)] const path = [group, parentKey, ...parents.reverse().map(parent => parent.key).slice(0, -1)]
let updatedValueForState = valueExists(settings, path) let updatedValueForState = valueExists('state', settings, path)
? { ...getCurrentValue(settings[group][parentKey], parents.map(el => el.key).slice(0, -1)), ? { ...getCurrentValue('state', settings[group][parentKey], parents.map(el => el.key).slice(0, -1)),
...{ [key]: valueForState }} ...{ [key]: valueForState }}
: { [key]: valueForState } : { [key]: valueForState }
let updatedValueForUpdatedSettings = valueExists(updatedSettings, path) let updatedValueForUpdatedSettings = valueExists('updatedSettings', updatedSettings, path)
? { ...getCurrentValue(updatedSettings[group][parentKey], parents.map(el => el.key).slice(0, -1))[1], ? { ...getCurrentValue('updatedSettings', updatedSettings[group][parentKey], parents.map(el => el.key).slice(0, -1))[1],
...{ [key]: [type, valueForUpdatedSettings] }} ...{ [key]: [type, valueForUpdatedSettings] }}
: { [key]: [type, valueForUpdatedSettings] } : { [key]: [type, valueForUpdatedSettings] }
if (group === ':mime' && parents[0].key === ':types') { if (group === ':mime' && parents[0].key === ':types') {
updatedValueForState = { ...settings[group][parents[0].key].value, ...updatedValueForState } updatedValueForState = settings[group][parents[0].key]
updatedValueForUpdatedSettings = { ? { ...settings[group][parents[0].key].value, ...updatedValueForState }
...Object.keys(settings[group][parents[0].key].value) : updatedValueForState
updatedValueForUpdatedSettings = settings[group][parents[0].key]
? { ...Object.keys(settings[group][parents[0].key].value)
.reduce((acc, el) => { .reduce((acc, el) => {
return { ...acc, [el]: [type, settings[group][parents[0].key].value[el]] } return { ...acc, [el]: [type, settings[group][parents[0].key].value[el]] }
}, {}), }, {}),
...updatedValueForUpdatedSettings ...updatedValueForUpdatedSettings }
} : updatedValueForUpdatedSettings
} }
return otherParents.length === 1 return otherParents.length === 1
@ -161,12 +175,25 @@ export const processNested = (valueForState, valueForUpdatedSettings, group, par
: processNested(updatedValueForState, updatedValueForUpdatedSettings, group, parentKey, otherParents, settings, updatedSettings) : processNested(updatedValueForState, updatedValueForUpdatedSettings, group, parentKey, otherParents, settings, updatedSettings)
} }
const valueExists = (value, path) => { const valueExists = (type, value, path) => {
if (path.length === 0) { if (type === 'state') {
return true return _.get(value, path)
} else {
const [group, key, firstSettingName, ...restKeys] = path
const firstSegment = _.get(value, [group, key, firstSettingName])
if (restKeys.length === 0 || !firstSegment) {
return firstSegment || false
} else {
const secondSegment = (value, keys) => {
if (keys.length === 0) {
return true
}
const [element, ...rest] = keys
return value[1][element] ? secondSegment(value[1][element], rest) : false
}
return secondSegment(firstSegment, restKeys)
}
} }
const [element, ...rest] = path
return value[element] ? valueExists(value[element], rest) : false
} }
export const valueHasTuples = (key, value) => { export const valueHasTuples = (key, value) => {
@ -218,8 +245,6 @@ const wrapValues = (settings, currentState) => {
} else if (setting === ':ip') { } else if (setting === ':ip') {
const ip = value.split('.').map(s => parseInt(s, 10)) const ip = value.split('.').map(s => parseInt(s, 10))
return { 'tuple': [setting, { 'tuple': ip }] } return { 'tuple': [setting, { 'tuple': ip }] }
} else if (setting === ':ssl_options') {
return { 'tuple': [setting, wrapValues(value, currentState)] }
} else if (setting === ':args') { } else if (setting === ':args') {
const index = value.findIndex(el => el === 'implode') const index = value.findIndex(el => el === 'implode')
const updatedArray = value.slice() const updatedArray = value.slice()

View file

@ -1,32 +1,25 @@
import { fetchDescription, fetchSettings, removeSettings, updateSettings, uploadMedia } from '@/api/settings' import { fetchDescription, fetchSettings, removeSettings, updateSettings } from '@/api/settings'
import { checkPartialUpdate, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers' import { checkPartialUpdate, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers'
import _ from 'lodash'
const settings = { const settings = {
state: { state: {
description: [], description: [],
settings: { settings: {},
':auto_linker': {},
':cors_plug': {},
':esshd': {},
':http_signatures': {},
':logger': {},
':mime': {},
':phoenix': {},
':pleroma': {},
':prometheus': {},
':quack': {},
':tesla': {},
':ueberauth': {},
':web_push_encryption': {}
},
updatedSettings: {}, updatedSettings: {},
ignoredIfNotEnabled: ['enabled', 'handler', 'password_authenticator', 'port', 'priv_dir'], db: {},
loading: true loading: true
}, },
mutations: { mutations: {
CLEAR_UPDATED_SETTINGS: (state) => { CLEAR_UPDATED_SETTINGS: (state) => {
state.updatedSettings = {} state.updatedSettings = {}
}, },
REMOVE_SETTING_FROM_UPDATED: (state, { group, key, subkeys }) => {
if (_.get(state.updatedSettings, [group, key, subkeys[0]])) {
const { [subkeys[0]]: value, ...updatedSettings } = state.updatedSettings[group][key]
state.updatedSettings = updatedSettings
}
},
SET_DESCRIPTION: (state, data) => { SET_DESCRIPTION: (state, data) => {
state.description = data state.description = data
}, },
@ -38,10 +31,19 @@ const settings = {
const parsedValue = valueHasTuples(key, value) const parsedValue = valueHasTuples(key, value)
? { value: parseNonTuples(key, value) } ? { value: parseNonTuples(key, value) }
: parseTuples(value, key) : parseTuples(value, key)
acc[group][key] = { ...acc[group][key], ...parsedValue } acc[group] = acc[group] ? { ...acc[group], [key]: parsedValue } : { [key]: parsedValue }
return acc return acc
}, state.settings) }, {})
const newDbSettings = data.reduce((acc, { group, key, db }) => {
if (db) {
acc[group] = acc[group] ? { ...acc[group], [key]: db } : { [key]: db }
}
return acc
}, {})
state.settings = newSettings state.settings = newSettings
state.db = newDbSettings
}, },
UPDATE_SETTINGS: (state, { group, key, input, value, type }) => { UPDATE_SETTINGS: (state, { group, key, input, value, type }) => {
const updatedSetting = !state.updatedSettings[group] || (key === 'Pleroma.Emails.Mailer' && input === ':adapter') const updatedSetting = !state.updatedSettings[group] || (key === 'Pleroma.Emails.Mailer' && input === ':adapter')
@ -66,8 +68,12 @@ const settings = {
commit('SET_SETTINGS', response.data.configs) commit('SET_SETTINGS', response.data.configs)
commit('SET_LOADING', false) commit('SET_LOADING', false)
}, },
async RemoveSetting({ getters }, configs) { async RemoveSetting({ commit, getters }, configs) {
await removeSettings(configs, getters.authHost, getters.token) await removeSettings(configs, getters.authHost, getters.token)
const response = await fetchSettings(getters.authHost, getters.token)
const { group, key, subkeys } = configs[0]
commit('SET_SETTINGS', response.data.configs)
commit('REMOVE_SETTING_FROM_UPDATED', { group, key, subkeys: subkeys || [] })
}, },
async SubmitChanges({ getters, commit, state }) { async SubmitChanges({ getters, commit, state }) {
const updatedData = checkPartialUpdate(state.settings, state.updatedSettings, state.description) const updatedData = checkPartialUpdate(state.settings, state.updatedSettings, state.description)
@ -75,7 +81,8 @@ const settings = {
return [...acc, ...wrapUpdatedSettings(group, updatedData[group], state.settings)] return [...acc, ...wrapUpdatedSettings(group, updatedData[group], state.settings)]
}, []) }, [])
const response = await updateSettings(configs, getters.authHost, getters.token) await updateSettings(configs, getters.authHost, getters.token)
const response = await fetchSettings(getters.authHost, getters.token)
commit('SET_SETTINGS', response.data.configs) commit('SET_SETTINGS', response.data.configs)
commit('CLEAR_UPDATED_SETTINGS') commit('CLEAR_UPDATED_SETTINGS')
}, },
@ -84,24 +91,14 @@ const settings = {
? commit('UPDATE_SETTINGS', { group, key, input, value, type }) ? commit('UPDATE_SETTINGS', { group, key, input, value, type })
: commit('UPDATE_SETTINGS', { group, key: input, input: '_value', value, type }) : commit('UPDATE_SETTINGS', { group, key: input, input: '_value', value, type })
}, },
UpdateState({ commit, dispatch, state }, { group, key, input, value }) { async UpdateState({ commit, getters, state }, { group, key, input, value }) {
if (key === 'Pleroma.Emails.Mailer' && input === ':adapter') { if (key === 'Pleroma.Emails.Mailer' && input === ':adapter') {
const subkeys = Object.keys(state.settings[group][key]).filter(el => el !== ':adapter') const subkeys = Object.keys(state.settings[group][key]).filter(el => el !== ':adapter')
const emailsValue = subkeys.map(el => { await removeSettings([{ group, key, delete: true, subkeys }], getters.authHost, getters.token)
return { 'tuple': [el, state.settings[group][key][el]] }
})
dispatch('RemoveSetting', [{ group, key, value: emailsValue, delete: true, subkeys }])
} }
key key
? commit('UPDATE_STATE', { group, key, input, value }) ? commit('UPDATE_STATE', { group, key, input, value })
: commit('UPDATE_STATE', { group, key: input, input: 'value', 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)
const updatedValue = childName
? { ...state.settings[tab][inputName], ...{ [childName]: response.data.url }}
: response.data.url
dispatch('UpdateSettings', { tab, data: { [inputName]: updatedValue }})
} }
} }
} }

View file

@ -17,6 +17,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'ActivityPub', name: 'ActivityPub',
@ -29,13 +30,13 @@ export default {
return this.settings.description.find(setting => setting.key === ':activitypub') return this.settings.description.find(setting => setting.key === ':activitypub')
}, },
activitypubData() { activitypubData() {
return this.settings.settings[':pleroma'][':activitypub'] return _.get(this.settings.settings, [':pleroma', ':activitypub']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.$store.state.settings.loading return this.$store.state.settings.loading
@ -44,7 +45,7 @@ export default {
return this.settings.description.find(setting => setting.key === ':user') return this.settings.description.find(setting => setting.key === ':user')
}, },
userData() { userData() {
return this.settings.settings[':pleroma'][':user'] return _.get(this.settings.settings, [':pleroma', ':user']) || {}
} }
}, },
methods: { methods: {

View file

@ -25,6 +25,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Authentication', name: 'Authentication',
@ -37,19 +38,19 @@ export default {
return this.settings.description.find(setting => setting.key === ':auth') return this.settings.description.find(setting => setting.key === ':auth')
}, },
authData() { authData() {
return this.settings.settings[':pleroma'][':auth'] return _.get(this.settings.settings, [':pleroma', ':auth']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
ldap() { ldap() {
return this.settings.description.find(setting => setting.key === ':ldap') return this.settings.description.find(setting => setting.key === ':ldap')
}, },
ldapData() { ldapData() {
return this.settings.settings[':pleroma'][':ldap'] return _.get(this.settings.settings, [':pleroma', ':ldap']) || {}
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -58,13 +59,13 @@ export default {
return this.settings.description.find(setting => setting.key === ':oauth2') return this.settings.description.find(setting => setting.key === ':oauth2')
}, },
oauth2Data() { oauth2Data() {
return this.settings.settings[':pleroma'][':oauth2'] return _.get(this.settings.settings, [':pleroma', ':oauth2']) || {}
}, },
pleromaAuthenticator() { pleromaAuthenticator() {
return this.settings.description.find(setting => setting.description === 'Authenticator') return this.settings.description.find(setting => setting.description === 'Authenticator')
}, },
pleromaAuthenticatorData() { pleromaAuthenticatorData() {
return this.settings.settings[':pleroma']['Pleroma.Web.Auth.Authenticator'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Web.Auth.Authenticator']) || {}
} }
}, },
methods: { methods: {

View file

@ -11,6 +11,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'AutoLinker', name: 'AutoLinker',
@ -23,13 +24,13 @@ export default {
return this.settings.description.find(setting => setting.key === ':opts') return this.settings.description.find(setting => setting.key === ':opts')
}, },
autoLinkerData() { autoLinkerData() {
return this.settings.settings[':auto_linker'][':opts'] return _.get(this.settings.settings, [':auto_linker', ':opts']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading

View file

@ -17,6 +17,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Captcha', name: 'Captcha',
@ -29,7 +30,7 @@ export default {
return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha') return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha')
}, },
captchaData() { captchaData() {
return this.settings.settings[':pleroma']['Pleroma.Captcha'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Captcha']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
@ -38,10 +39,10 @@ export default {
return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha.Kocaptcha') return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha.Kocaptcha')
}, },
kocaptchaData() { kocaptchaData() {
return this.settings.settings[':pleroma']['Pleroma.Captcha.Kocaptcha'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Captcha.Kocaptcha']) || {}
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading

View file

@ -1,59 +0,0 @@
<template>
<div v-if="!loading">
<el-form ref="databaseData" :model="databaseData" :label-width="labelWidth">
<setting :setting-group="database" :data="databaseData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Database',
components: { Setting },
computed: {
...mapGetters([
'settings'
]),
database() {
return this.settings.description.find(setting => setting.key === ':database')
},
databaseData() {
return this.settings.settings[':pleroma'][':database']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
}
},
methods: {
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View file

@ -1,81 +0,0 @@
<template>
<div v-if="!loading">
<el-form ref="endpointData" :model="endpointData" :label-width="labelWidth">
<setting :setting-group="endpoint" :data="endpointData"/>
</el-form>
<div class="line"/>
<el-form ref="endpointMetricsExporter" :model="endpointMetricsExporterData" :label-width="labelWidth">
<setting :setting-group="endpointMetricsExporter" :data="endpointMetricsExporterData"/>
</el-form>
<div class="line"/>
<el-form ref="remoteIp" :model="remoteIpData" :label-width="labelWidth">
<setting :setting-group="remoteIp" :data="remoteIpData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Endpoint',
components: {
Setting
},
computed: {
...mapGetters([
'settings'
]),
endpoint() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Endpoint')
},
endpointData() {
return this.settings.settings[':pleroma']['Pleroma.Web.Endpoint']
},
endpointMetricsExporter() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Endpoint.MetricsExporter')
},
endpointMetricsExporterData() {
return this.settings.settings[':prometheus']['Pleroma.Web.Endpoint.MetricsExporter']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
remoteIp() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Plugs.RemoteIp')
},
remoteIpData() {
return this.settings.settings[':pleroma']['Pleroma.Plugs.RemoteIp']
}
},
methods: {
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View file

@ -20,6 +20,7 @@
import i18n from '@/lang' import i18n from '@/lang'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Esshd', name: 'Esshd',
@ -32,13 +33,13 @@ export default {
return this.settings.description.find(setting => setting.group === ':esshd') return this.settings.description.find(setting => setting.group === ':esshd')
}, },
esshdData() { esshdData() {
return this.settings.settings[':esshd'] return _.get(this.settings.settings, [':esshd']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading

View file

@ -1,11 +1,6 @@
<template> <template>
<div v-if="!loading"> <div v-if="!loading">
<el-form ref="frontendData" :model="frontendData" :label-width="labelWidth"> <el-form ref="frontendData" :model="frontendData" :label-width="labelWidth">
<el-form-item>
<p class="expl">This form can be used to configure a keyword list that keeps the configuration data for any kind of frontend.
By default, settings for <span class="code">pleroma_fe</span> and <span class="code">masto_fe</span> are configured.
If you want to add your own configuration your settings need to be complete as they will override the defaults.</p>
</el-form-item>
<setting :setting-group="frontend" :data="frontendData"/> <setting :setting-group="frontend" :data="frontendData"/>
</el-form> </el-form>
<el-form ref="assetsData" :model="assetsData" :label-width="labelWidth"> <el-form ref="assetsData" :model="assetsData" :label-width="labelWidth">
@ -36,6 +31,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Frontend', name: 'Frontend',
@ -48,37 +44,37 @@ export default {
return this.settings.description.find(setting => setting.key === ':assets') return this.settings.description.find(setting => setting.key === ':assets')
}, },
assetsData() { assetsData() {
return this.settings.settings[':pleroma'][':assets'] return _.get(this.settings.settings, [':pleroma', ':assets']) || {}
}, },
chat() { chat() {
return this.settings.description.find(setting => setting.key === ':chat') return this.settings.description.find(setting => setting.key === ':chat')
}, },
chatData() { chatData() {
return this.settings.settings[':pleroma'][':chat'] return _.get(this.settings.settings, [':pleroma', ':chat']) || {}
}, },
emoji() { emoji() {
return this.settings.description.find(setting => setting.key === ':emoji') return this.settings.description.find(setting => setting.key === ':emoji')
}, },
emojiData() { emojiData() {
return this.settings.settings[':pleroma'][':emoji'] return _.get(this.settings.settings, [':pleroma', ':emoji']) || {}
}, },
frontend() { frontend() {
return this.settings.description.find(setting => setting.key === ':frontend_configurations') return this.settings.description.find(setting => setting.key === ':frontend_configurations')
}, },
frontendData() { frontendData() {
return this.settings.settings[':pleroma'][':frontend_configurations'] return _.get(this.settings.settings, [':pleroma', ':frontend_configurations']) || {}
}, },
markup() { markup() {
return this.settings.description.find(setting => setting.key === ':markup') return this.settings.description.find(setting => setting.key === ':markup')
}, },
markupData() { markupData() {
return this.settings.settings[':pleroma'][':markup'] return _.get(this.settings.settings, [':pleroma', ':markup']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading

View file

@ -11,6 +11,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Gopher', name: 'Gopher',
@ -23,13 +24,13 @@ export default {
return this.settings.description.find(setting => setting.key === ':gopher') return this.settings.description.find(setting => setting.key === ':gopher')
}, },
gopherData() { gopherData() {
return this.settings.settings[':pleroma'][':gopher'] return _.get(this.settings.settings, [':pleroma', ':gopher']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading

View file

@ -29,6 +29,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'HTTP', name: 'HTTP',
@ -41,31 +42,31 @@ export default {
return this.settings.description.find(setting => setting.group === ':cors_plug') return this.settings.description.find(setting => setting.group === ':cors_plug')
}, },
corsPlugData() { corsPlugData() {
return this.settings.settings[':cors_plug'] return _.get(this.settings.settings, [':cors_plug']) || {}
}, },
http() { http() {
return this.settings.description.find(setting => setting.key === ':http') return this.settings.description.find(setting => setting.key === ':http')
}, },
httpData() { httpData() {
return this.settings.settings[':pleroma'][':http'] return _.get(this.settings.settings, [':pleroma', ':http']) || {}
}, },
httpSecurity() { httpSecurity() {
return this.settings.description.find(setting => setting.key === ':http_security') return this.settings.description.find(setting => setting.key === ':http_security')
}, },
httpSecurityData() { httpSecurityData() {
return this.settings.settings[':pleroma'][':http_security'] return _.get(this.settings.settings, [':pleroma', ':http_security']) || {}
}, },
httpSignatures() { httpSignatures() {
return this.settings.description.find(setting => setting.group === ':http_signatures') return this.settings.description.find(setting => setting.group === ':http_signatures')
}, },
httpSignaturesData() { httpSignaturesData() {
return this.settings.settings[':http_signatures'] return _.get(this.settings.settings, [':http_signatures']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -74,7 +75,7 @@ export default {
return this.settings.description.find(setting => setting.key === ':web_cache_ttl') return this.settings.description.find(setting => setting.key === ':web_cache_ttl')
}, },
webCacheTtlData() { webCacheTtlData() {
return this.settings.settings[':pleroma'][':web_cache_ttl'] return _.get(this.settings.settings, [':pleroma', ':web_cache_ttl']) || {}
} }
}, },
methods: { methods: {

View file

@ -1,7 +1,13 @@
<template> <template>
<el-form-item :label="setting.label" :label-width="customLabelWidth" :class="labelClass"> <el-form-item :label-width="customLabelWidth" :class="labelClass">
<span slot="label">
{{ setting.label }}
<el-tooltip v-if="canBeDeleted" :content="$t('settings.removeFromDB')" placement="bottom-end">
<el-button icon="el-icon-delete" circle size="mini" style="margin-left:5px" @click="removeSetting"/>
</el-tooltip>
</span>
<el-input <el-input
v-if="setting.type === 'string'" v-if="setting.type === 'string' || (setting.type.includes('string') && setting.type.includes('atom'))"
:value="inputValue" :value="inputValue"
:placeholder="setting.suggestions ? setting.suggestions[0] : null" :placeholder="setting.suggestions ? setting.suggestions[0] : null"
@input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/> @input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/>
@ -36,13 +42,6 @@
@change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"> @change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)">
<el-option v-for="(option, index) in setting.suggestions" :key="index" :value="option"/> <el-option v-for="(option, index) in setting.suggestions" :key="index" :value="option"/>
</el-select> </el-select>
<editor
v-if="setting.key === ':dispatch'"
v-model="editorContent"
height="150"
width="100%"
lang="elixir"
theme="chrome"/>
<el-input <el-input
v-if="setting.key === ':ip'" v-if="setting.key === ':ip'"
:value="inputValue" :value="inputValue"
@ -62,7 +61,7 @@
:setting-parent="[...settingParent, subSetting]" :setting-parent="[...settingParent, subSetting]"
:setting="subSetting" :setting="subSetting"
:data="data[setting.key]" :data="data[setting.key]"
:custom-label-width="'100px'" :custom-label-width="'140px'"
:label-class="'center-label'" :label-class="'center-label'"
:input-class="'keyword-inner-input'" :input-class="'keyword-inner-input'"
:nested="true"/> :nested="true"/>
@ -70,11 +69,10 @@
</div> </div>
<!-- special inputs --> <!-- special inputs -->
<auto-linker-input v-if="settingGroup.group === ':auto_linker'" :data="data" :setting-group="settingGroup" :setting="setting"/> <auto-linker-input v-if="settingGroup.group === ':auto_linker'" :data="data" :setting-group="settingGroup" :setting="setting"/>
<mascots-input v-if="setting.key === ':mascots'" :data="data" :setting-group="settingGroup" :setting="setting"/> <mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
<editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="data" :setting-group="settingGroup" :setting="setting"/> <editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
<icons-input v-if="setting.key === ':icons'" :data="data[':icons']" :setting-group="settingGroup" :setting="setting"/> <icons-input v-if="setting.key === ':icons'" :data="iconsData" :setting-group="settingGroup" :setting="setting"/>
<proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/> <proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
<!-- <ssl-options-input v-if="setting.key === ':ssl_options'" :setting-group="settingGroup" :setting-parent="settingParent" :setting="setting" :data="data" :nested="true" :custom-label-width="'100px'"/> -->
<multiple-select v-if="setting.key === ':backends' || setting.key === ':args'" :data="data" :setting-group="settingGroup" :setting="setting"/> <multiple-select v-if="setting.key === ':backends' || setting.key === ':args'" :data="data" :setting-group="settingGroup" :setting="setting"/>
<prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/> <prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
<rate-limit-input v-if="settingGroup.key === ':rate_limit'" :data="data" :setting-group="settingGroup" :setting="setting"/> <rate-limit-input v-if="settingGroup.key === ':rate_limit'" :data="data" :setting-group="settingGroup" :setting="setting"/>
@ -84,16 +82,14 @@
</template> </template>
<script> <script>
import AceEditor from 'vue2-ace-editor' import i18n from '@/lang'
import 'brace/mode/elixir' import { AutoLinkerInput, EditableKeywordInput, IconsInput, MascotsInput, MultipleSelect, ProxyUrlInput, PruneInput, RateLimitInput } from './inputComponents'
import 'default-passive-events'
import { AutoLinkerInput, EditableKeywordInput, IconsInput, MascotsInput, MultipleSelect, ProxyUrlInput, PruneInput, RateLimitInput, SslOptionsInput } from './inputComponents'
import { processNested } from '@/store/modules/normalizers' import { processNested } from '@/store/modules/normalizers'
import _ from 'lodash'
export default { export default {
name: 'Inputs', name: 'Inputs',
components: { components: {
editor: AceEditor,
AutoLinkerInput, AutoLinkerInput,
EditableKeywordInput, EditableKeywordInput,
IconsInput, IconsInput,
@ -101,8 +97,7 @@ export default {
MultipleSelect, MultipleSelect,
ProxyUrlInput, ProxyUrlInput,
PruneInput, PruneInput,
RateLimitInput, RateLimitInput
SslOptionsInput
}, },
props: { props: {
customLabelWidth: { customLabelWidth: {
@ -159,13 +154,13 @@ export default {
} }
}, },
computed: { computed: {
editorContent: { canBeDeleted() {
get: function() { const { group, key } = this.settingGroup
return this.data[this.setting.key] ? this.data[this.setting.key][0] : '' return _.get(this.$store.state.settings.db, [group, key]) &&
}, this.$store.state.settings.db[group][key].includes(this.setting.key)
set: function(value) { },
this.processNestedData([value], this.settingGroup.group, this.settingGroup.key, this.settingParent) iconsData() {
} return Array.isArray(this.data[':icons']) ? this.data[':icons'] : []
}, },
inputValue() { inputValue() {
if ([':esshd', ':cors_plug', ':quack', ':http_signatures', ':tesla'].includes(this.settingGroup.group) && if ([':esshd', ':cors_plug', ':quack', ':http_signatures', ':tesla'].includes(this.settingGroup.group) &&
@ -178,7 +173,7 @@ export default {
this.setting.key === ':admin_token') { this.setting.key === ':admin_token') {
return this.data.value return this.data.value
} else if (this.settingGroup.group === ':mime' && this.settingParent[0].key === ':types') { } else if (this.settingGroup.group === ':mime' && this.settingParent[0].key === ':types') {
return this.data.value[this.setting.key] return this.data.value ? this.data.value[this.setting.key] : []
} else if (this.setting.type === 'atom') { } else if (this.setting.type === 'atom') {
return this.data[this.setting.key] && this.data[this.setting.key][0] === ':' ? this.data[this.setting.key].substr(1) : this.data[this.setting.key] return this.data[this.setting.key] && this.data[this.setting.key][0] === ':' ? this.data[this.setting.key].substr(1) : this.data[this.setting.key]
} else { } else {
@ -186,7 +181,10 @@ export default {
} }
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
},
keywordData() {
return Array.isArray(this.data) ? this.data : []
}, },
rewritePolicyValue() { rewritePolicyValue() {
return typeof this.data[this.setting.key] === 'string' ? [this.data[this.setting.key]] : this.data[this.setting.key] return typeof this.data[this.setting.key] === 'string' ? [this.data[this.setting.key]] : this.data[this.setting.key]
@ -215,6 +213,20 @@ export default {
this.$store.dispatch('UpdateState', this.$store.dispatch('UpdateState',
{ group, key: parentKey, input: setting.key, value: valueForState }) { group, key: parentKey, input: setting.key, value: valueForState })
}, },
async removeSetting() {
const config = this.settingGroup.key
? [{ group: this.settingGroup.group, key: this.settingGroup.key, delete: true, subkeys: [this.setting.key] }]
: [{ group: this.settingGroup.group, key: this.setting.key, delete: true }]
try {
await this.$store.dispatch('RemoveSetting', config)
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.successfullyRemoved')
})
},
renderMultipleSelect(type) { renderMultipleSelect(type) {
return Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && ( return Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && (
type.includes('module') || type.includes('module') ||

View file

@ -42,6 +42,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Instance', name: 'Instance',
@ -56,25 +57,25 @@ export default {
return this.settings.description.find(setting => setting.description === `Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter`) return this.settings.description.find(setting => setting.description === `Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter`)
}, },
adminTokenData() { adminTokenData() {
return this.settings.settings[':pleroma'][':admin_token'] return _.get(this.settings.settings, [':pleroma', ':admin_token']) || {}
}, },
fetchInitialPosts() { fetchInitialPosts() {
return this.settings.description.find(setting => setting.key === ':fetch_initial_posts') return this.settings.description.find(setting => setting.key === ':fetch_initial_posts')
}, },
fetchInitialPostsData() { fetchInitialPostsData() {
return this.settings.settings[':pleroma'][':fetch_initial_posts'] return _.get(this.settings.settings, [':pleroma', ':fetch_initial_posts']) || {}
}, },
instance() { instance() {
return this.settings.description.find(setting => setting.key === ':instance') return this.settings.description.find(setting => setting.key === ':instance')
}, },
instanceData() { instanceData() {
return this.settings.settings[':pleroma'][':instance'] return _.get(this.settings.settings, [':pleroma', ':instance']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -83,31 +84,31 @@ export default {
return this.settings.description.find(setting => setting.key === ':manifest') return this.settings.description.find(setting => setting.key === ':manifest')
}, },
manifestData() { manifestData() {
return this.settings.settings[':pleroma'][':manifest'] return _.get(this.settings.settings, [':pleroma', ':manifest']) || {}
}, },
pleromaUser() { pleromaUser() {
return this.settings.description.find(setting => setting.key === 'Pleroma.User') return this.settings.description.find(setting => setting.key === 'Pleroma.User')
}, },
pleromaUserData() { pleromaUserData() {
return this.settings.settings[':pleroma']['Pleroma.User'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.User']) || {}
}, },
scheduledActivity() { scheduledActivity() {
return this.$store.state.settings.description.find(setting => setting.key === 'Pleroma.ScheduledActivity') return this.$store.state.settings.description.find(setting => setting.key === 'Pleroma.ScheduledActivity')
}, },
scheduledActivityData() { scheduledActivityData() {
return this.settings.settings[':pleroma']['Pleroma.ScheduledActivity'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.ScheduledActivity']) || {}
}, },
suggestions() { suggestions() {
return this.$store.state.settings.description.find(setting => setting.key === ':suggestions') return this.$store.state.settings.description.find(setting => setting.key === ':suggestions')
}, },
suggestionsData() { suggestionsData() {
return this.settings.settings[':pleroma'][':suggestions'] return _.get(this.settings.settings, [':pleroma', ':suggestions']) || {}
}, },
uriSchemes() { uriSchemes() {
return this.$store.state.settings.description.find(setting => setting.key === ':uri_schemes') return this.$store.state.settings.description.find(setting => setting.key === ':uri_schemes')
}, },
uriSchemesData() { uriSchemesData() {
return this.settings.settings[':pleroma'][':uri_schemes'] return _.get(this.settings.settings, [':pleroma', ':uri_schemes']) || {}
} }
}, },
methods: { methods: {

View file

@ -19,6 +19,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'JobQueue', name: 'JobQueue',
@ -31,13 +32,13 @@ export default {
return this.settings.description.find(setting => setting.key === 'Pleroma.ActivityExpiration') return this.settings.description.find(setting => setting.key === 'Pleroma.ActivityExpiration')
}, },
activityExpirationData() { activityExpirationData() {
return this.settings.settings[':pleroma']['Pleroma.ActivityExpiration'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.ActivityExpiration']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -46,13 +47,13 @@ export default {
return this.settings.description.find(setting => setting.key === 'Oban') return this.settings.description.find(setting => setting.key === 'Oban')
}, },
obanQueuesData() { obanQueuesData() {
return this.settings.settings[':pleroma']['Oban'] return _.get(this.settings.settings, [':pleroma', 'Oban']) || {}
}, },
workers() { workers() {
return this.settings.description.find(setting => setting.key === ':workers') return this.settings.description.find(setting => setting.key === ':workers')
}, },
workersData() { workersData() {
return this.settings.settings[':pleroma'][':workers'] return _.get(this.settings.settings, [':pleroma', ':workers']) || {}
} }
}, },
methods: { methods: {

View file

@ -26,6 +26,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Logger', name: 'Logger',
@ -38,19 +39,19 @@ export default {
return this.settings.description.find(setting => setting.key === ':console') return this.settings.description.find(setting => setting.key === ':console')
}, },
consoleData() { consoleData() {
return this.settings.settings[':logger'][':console'] return _.get(this.settings.settings, [':logger', ':console']) || {}
}, },
exsyslogger() { exsyslogger() {
return this.settings.description.find(setting => setting.key === ':ex_syslogger') return this.settings.description.find(setting => setting.key === ':ex_syslogger')
}, },
exsysloggerData() { exsysloggerData() {
return this.settings.settings[':logger'][':ex_syslogger'] return _.get(this.settings.settings, [':logger', ':ex_syslogger']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -59,13 +60,13 @@ export default {
return this.settings.description.find(setting => setting.group === ':logger') return this.settings.description.find(setting => setting.group === ':logger')
}, },
loggerData() { loggerData() {
return this.settings.settings[':logger'][':backends'] return _.get(this.settings.settings, [':logger', ':backends']) || {}
}, },
quack() { quack() {
return this.settings.description.find(setting => setting.group === ':quack') return this.settings.description.find(setting => setting.group === ':quack')
}, },
quackData() { quackData() {
return this.settings.settings[':quack'] return _.get(this.settings.settings, [':quack']) || {}
} }
}, },
methods: { methods: {

View file

@ -39,6 +39,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'MRF', name: 'MRF',
@ -51,7 +52,7 @@ export default {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -60,49 +61,49 @@ export default {
return this.settings.description.find(setting => setting.key === ':mrf_simple') return this.settings.description.find(setting => setting.key === ':mrf_simple')
}, },
mrfSimpleData() { mrfSimpleData() {
return this.settings.settings[':pleroma'][':mrf_simple'] return _.get(this.settings.settings, [':pleroma', ':mrf_simple']) || {}
}, },
mrfRejectnonpublic() { mrfRejectnonpublic() {
return this.settings.description.find(setting => setting.key === ':mrf_rejectnonpublic') return this.settings.description.find(setting => setting.key === ':mrf_rejectnonpublic')
}, },
mrfRejectnonpublicData() { mrfRejectnonpublicData() {
return this.settings.settings[':pleroma'][':mrf_rejectnonpublic'] return _.get(this.settings.settings, [':pleroma', ':mrf_rejectnonpublic']) || {}
}, },
mrfHellthread() { mrfHellthread() {
return this.settings.description.find(setting => setting.key === ':mrf_hellthread') return this.settings.description.find(setting => setting.key === ':mrf_hellthread')
}, },
mrfHellthreadData() { mrfHellthreadData() {
return this.settings.settings[':pleroma'][':mrf_hellthread'] return _.get(this.settings.settings, [':pleroma', ':mrf_hellthread']) || {}
}, },
mrfKeyword() { mrfKeyword() {
return this.settings.description.find(setting => setting.key === ':mrf_keyword') return this.settings.description.find(setting => setting.key === ':mrf_keyword')
}, },
mrfKeywordData() { mrfKeywordData() {
return this.settings.settings[':pleroma'][':mrf_keyword'] return _.get(this.settings.settings, [':pleroma', ':mrf_keyword']) || {}
}, },
mrfSubchain() { mrfSubchain() {
return this.settings.description.find(setting => setting.key === ':mrf_subchain') return this.settings.description.find(setting => setting.key === ':mrf_subchain')
}, },
mrfSubchainData() { mrfSubchainData() {
return this.settings.settings[':pleroma'][':mrf_subchain'] return _.get(this.settings.settings, [':pleroma', ':mrf_subchain']) || {}
}, },
mrfMention() { mrfMention() {
return this.settings.description.find(setting => setting.key === ':mrf_mention') return this.settings.description.find(setting => setting.key === ':mrf_mention')
}, },
mrfMentionData() { mrfMentionData() {
return this.settings.settings[':pleroma'][':mrf_mention'] return _.get(this.settings.settings, [':pleroma', ':mrf_mention']) || {}
}, },
mrfNormalizeMarkup() { mrfNormalizeMarkup() {
return this.settings.description.find(setting => setting.key === ':mrf_normalize_markup') return this.settings.description.find(setting => setting.key === ':mrf_normalize_markup')
}, },
mrfNormalizeMarkupData() { mrfNormalizeMarkupData() {
return this.settings.settings[':pleroma'][':mrf_normalize_markup'] return _.get(this.settings.settings, [':pleroma', ':mrf_normalize_markup']) || {}
}, },
mrfVocabulary() { mrfVocabulary() {
return this.settings.description.find(setting => setting.key === ':mrf_vocabulary') return this.settings.description.find(setting => setting.key === ':mrf_vocabulary')
}, },
mrfVocabularyData() { mrfVocabularyData() {
return this.settings.settings[':pleroma'][':mrf_vocabulary'] return _.get(this.settings.settings, [':pleroma', ':mrf_vocabulary']) || {}
} }
}, },
methods: { methods: {

View file

@ -20,6 +20,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Mailer', name: 'Mailer',
@ -34,13 +35,13 @@ export default {
return this.settings.description.find(setting => setting.key === ':email_notifications') return this.settings.description.find(setting => setting.key === ':email_notifications')
}, },
emailNotificationsData() { emailNotificationsData() {
return this.settings.settings[':pleroma'][':email_notifications'] return _.get(this.settings.settings, [':pleroma', ':email_notifications']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.$store.state.settings.loading return this.$store.state.settings.loading
@ -49,13 +50,13 @@ export default {
return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.Mailer') return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.Mailer')
}, },
mailerData() { mailerData() {
return this.settings.settings[':pleroma']['Pleroma.Emails.Mailer'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Emails.Mailer']) || {}
}, },
userEmail() { userEmail() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.UserEmail') return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.UserEmail')
}, },
userEmailData() { userEmailData() {
return this.settings.settings[':pleroma']['Pleroma.Emails.UserEmail'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Emails.UserEmail']) || {}
} }
}, },
methods: { methods: {

View file

@ -11,6 +11,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'MediaProxy', name: 'MediaProxy',
@ -23,7 +24,7 @@ export default {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -32,7 +33,7 @@ export default {
return this.settings.description.find(setting => setting.key === ':media_proxy') return this.settings.description.find(setting => setting.key === ':media_proxy')
}, },
mediaProxyData() { mediaProxyData() {
return this.settings.settings[':pleroma'][':media_proxy'] return _.get(this.settings.settings, [':pleroma', ':media_proxy']) || {}
} }
}, },
methods: { methods: {

View file

@ -17,6 +17,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Metadata', name: 'Metadata',
@ -29,7 +30,7 @@ export default {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -38,13 +39,13 @@ export default {
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Metadata') return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Metadata')
}, },
metadataData() { metadataData() {
return this.settings.settings[':pleroma']['Pleroma.Web.Metadata'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Web.Metadata']) || {}
}, },
richMedia() { richMedia() {
return this.settings.description.find(setting => setting.key === ':rich_media') return this.settings.description.find(setting => setting.key === ':rich_media')
}, },
richMediaData() { richMediaData() {
return this.settings.settings[':pleroma'][':rich_media'] return _.get(this.settings.settings, [':pleroma', ':rich_media']) || {}
} }
}, },
methods: { methods: {

View file

@ -6,6 +6,9 @@
<div class="line"/> <div class="line"/>
<el-form ref="mimeTypes" :model="mimeTypesData" :label-width="labelWidth"> <el-form ref="mimeTypes" :model="mimeTypesData" :label-width="labelWidth">
<setting :setting-group="mimeTypes" :data="mimeTypesData"/> <setting :setting-group="mimeTypes" :data="mimeTypesData"/>
</el-form>
<el-form ref="remoteIp" :model="remoteIpData" :label-width="labelWidth">
<setting :setting-group="remoteIp" :data="remoteIpData"/>
<el-form-item> <el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button> <el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item> </el-form-item>
@ -17,6 +20,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Other', name: 'Other',
@ -29,7 +33,7 @@ export default {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -38,13 +42,19 @@ export default {
return this.settings.description.find(setting => setting.group === ':mime') return this.settings.description.find(setting => setting.group === ':mime')
}, },
mimeTypesData() { mimeTypesData() {
return this.settings.settings[':mime'] return _.get(this.settings.settings, [':mime']) || {}
},
remoteIp() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Plugs.RemoteIp')
},
remoteIpData() {
return _.get(this.settings.settings, [':pleroma', 'Pleroma.Plugs.RemoteIp']) || {}
}, },
teslaAdapter() { teslaAdapter() {
return this.settings.description.find(setting => setting.group === ':tesla') return this.settings.description.find(setting => setting.group === ':tesla')
}, },
teslaAdapterData() { teslaAdapterData() {
return this.settings.settings[':tesla'] return _.get(this.settings.settings, [':tesla']) || {}
} }
}, },
methods: { methods: {

View file

@ -11,6 +11,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'RateLimiters', name: 'RateLimiters',
@ -23,13 +24,13 @@ export default {
return this.settings.description.find(setting => setting.key === ':rate_limit') return this.settings.description.find(setting => setting.key === ':rate_limit')
}, },
rateLimitersData() { rateLimitersData() {
return this.settings.settings[':pleroma'][':rate_limit'] return _.get(this.settings.settings, [':pleroma', ':rate_limit']) || {}
}, },
isMobile() { isMobile() {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.$store.state.settings.loading return this.$store.state.settings.loading

View file

@ -29,17 +29,30 @@
:nested="false"/> :nested="false"/>
</div> </div>
<div v-if="compound(setting)"> <div v-if="compound(setting)">
<el-form-item :label="`${setting.label}:`"/> <div v-if="!setting.children">
<div v-for="subSetting in setting.children" :key="subSetting.key">
<inputs <inputs
:setting-group="settingGroup" :setting-group="settingGroup"
:setting-parent="[setting, subSetting]" :setting="setting"
:setting="subSetting"
:data="data[setting.key]" :data="data[setting.key]"
:nested="true"/> :nested="true"/>
</div> </div>
<div v-if="!setting.children"> <div v-else>
<inputs :setting-group="settingGroup" :setting="setting" :data="data[setting.key]" :nested="true"/> <el-form-item>
<span slot="label">
{{ setting.label }}:
<el-tooltip v-if="canBeDeleted(setting.key)" :content="$t('settings.removeFromDB')" placement="bottom-end">
<el-button icon="el-icon-delete" circle size="mini" style="margin-left:5px" @click="removeSetting(setting.key)"/>
</el-tooltip>
</span>
</el-form-item>
<div v-for="subSetting in setting.children" :key="subSetting.key">
<inputs
:setting-group="settingGroup"
:setting-parent="[setting, subSetting]"
:setting="subSetting"
:data="data[setting.key]"
:nested="true"/>
</div>
</div> </div>
<div class="line"/> <div class="line"/>
</div> </div>
@ -49,15 +62,13 @@
</template> </template>
<script> <script>
import AceEditor from 'vue2-ace-editor'
import Inputs from './Inputs' import Inputs from './Inputs'
import 'brace/mode/elixir' import i18n from '@/lang'
import 'default-passive-events' import _ from 'lodash'
export default { export default {
name: 'Setting', name: 'Setting',
components: { components: {
editor: AceEditor,
Inputs Inputs
}, },
props: { props: {
@ -84,12 +95,32 @@ export default {
} }
}, },
methods: { methods: {
canBeDeleted(settingKey) {
const { group, key } = this.settingGroup
const existingKey = key || settingKey
return _.get(this.$store.state.settings.db, [group, existingKey]) &&
this.$store.state.settings.db[group][existingKey].includes(settingKey)
},
compound({ type, key, children }) { compound({ type, key, children }) {
return type === 'keyword' || return type === 'keyword' ||
type === 'map' || type === 'map' ||
type.includes('keyword') || type.includes('keyword') ||
key === ':replace' key === ':replace'
}, },
async removeSetting(key) {
const config = this.settingGroup.key
? [{ group: this.settingGroup.group, key: this.settingGroup.key, delete: true, subkeys: [key] }]
: [{ group: this.settingGroup.group, key, delete: true }]
try {
await this.$store.dispatch('RemoveSetting', config)
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.successfullyRemoved')
})
},
updateSetting(value, tab, input) { updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
} }

View file

@ -11,10 +11,6 @@
<setting :setting-group="uploadersS3" :data="uploadersS3Data"/> <setting :setting-group="uploadersS3" :data="uploadersS3Data"/>
</el-form> </el-form>
<div class="line"/> <div class="line"/>
<el-form ref="uploadersMDII" :model="uploadersMDIIData" :label-width="labelWidth">
<setting :setting-group="uploadersMDII" :data="uploadersMDIIData"/>
</el-form>
<div class="line"/>
<el-form ref="uploadFilterMogrify" :model="uploadFilterMogrifyData" :label-width="labelWidth"> <el-form ref="uploadFilterMogrify" :model="uploadFilterMogrifyData" :label-width="labelWidth">
<setting :setting-group="uploadFilterMogrify" :data="uploadFilterMogrifyData"/> <setting :setting-group="uploadFilterMogrify" :data="uploadFilterMogrifyData"/>
</el-form> </el-form>
@ -32,6 +28,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'Upload', name: 'Upload',
@ -44,7 +41,7 @@ export default {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -53,37 +50,31 @@ export default {
return this.settings.description.find(setting => setting.key === 'Pleroma.Upload') return this.settings.description.find(setting => setting.key === 'Pleroma.Upload')
}, },
uploadData() { uploadData() {
return this.settings.settings[':pleroma']['Pleroma.Upload'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Upload']) || {}
}, },
uploadersLocal() { uploadersLocal() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.Local') return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.Local')
}, },
uploadersLocalData() { uploadersLocalData() {
return this.settings.settings[':pleroma']['Pleroma.Uploaders.Local'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Uploaders.Local']) || {}
}, },
uploadersS3() { uploadersS3() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.S3') return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.S3')
}, },
uploadersS3Data() { uploadersS3Data() {
return this.settings.settings[':pleroma']['Pleroma.Uploaders.S3'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Uploaders.S3']) || {}
},
uploadersMDII() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.MDII')
},
uploadersMDIIData() {
return this.settings.settings[':pleroma']['Pleroma.Uploaders.MDII']
}, },
uploadFilterMogrify() { uploadFilterMogrify() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.Mogrify') return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.Mogrify')
}, },
uploadFilterMogrifyData() { uploadFilterMogrifyData() {
return this.settings.settings[':pleroma']['Pleroma.Upload.Filter.Mogrify'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Upload.Filter.Mogrify']) || {}
}, },
uploadAnonymizeFilename() { uploadAnonymizeFilename() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.AnonymizeFilename') return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.AnonymizeFilename')
}, },
uploadAnonymizeFilenameData() { uploadAnonymizeFilenameData() {
return this.settings.settings[':pleroma']['Pleroma.Upload.Filter.AnonymizeFilename'] return _.get(this.settings.settings, [':pleroma', 'Pleroma.Upload.Filter.AnonymizeFilename']) || {}
} }
}, },
methods: { methods: {

View file

@ -11,6 +11,7 @@
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import i18n from '@/lang' import i18n from '@/lang'
import Setting from './Setting' import Setting from './Setting'
import _ from 'lodash'
export default { export default {
name: 'WebPush', name: 'WebPush',
@ -23,7 +24,7 @@ export default {
return this.$store.state.app.device === 'mobile' return this.$store.state.app.device === 'mobile'
}, },
labelWidth() { labelWidth() {
return this.isMobile ? '100px' : '240px' return this.isMobile ? '100px' : '280px'
}, },
loading() { loading() {
return this.settings.loading return this.settings.loading
@ -32,7 +33,7 @@ export default {
return this.settings.description.find(setting => setting.key === ':vapid_details') return this.settings.description.find(setting => setting.key === ':vapid_details')
}, },
vapidDetailsData() { vapidDetailsData() {
return this.settings.settings[':web_push_encryption'][':vapid_details'] return _.get(this.settings.settings, [':web_push_encryption', ':vapid_details']) || {}
} }
}, },
methods: { methods: {

View file

@ -2,8 +2,6 @@ export { default as ActivityPub } from './ActivityPub'
export { default as Authentication } from './Authentication' export { default as Authentication } from './Authentication'
export { default as AutoLinker } from './AutoLinker' export { default as AutoLinker } from './AutoLinker'
export { default as Captcha } from './Captcha' export { default as Captcha } from './Captcha'
export { default as Database } from './Database'
export { default as Endpoint } from './Endpoint'
export { default as Esshd } from './Esshd' export { default as Esshd } from './Esshd'
export { default as Frontend } from './Frontend' export { default as Frontend } from './Frontend'
export { default as Gopher } from './Gopher' export { default as Gopher } from './Gopher'

View file

@ -32,7 +32,7 @@ export default {
name: 'EditableKeywordInput', name: 'EditableKeywordInput',
props: { props: {
data: { data: {
type: [Object, Array], type: Array,
default: function() { default: function() {
return {} return {}
} }
@ -95,7 +95,7 @@ export default {
updateSetting(value, group, key, input, type) { updateSetting(value, group, key, input, type) {
const updatedSettings = type !== 'map' const updatedSettings = type !== 'map'
? value.reduce((acc, element) => { ? value.reduce((acc, element) => {
return { ...acc, [Object.keys(element)[0]]: [['list'], Object.values(element)[0].value] } return { ...acc, [Object.keys(element)[0]]: ['list', Object.values(element)[0].value] }
}, {}) }, {})
: value.reduce((acc, element) => { : value.reduce((acc, element) => {
return { ...acc, [Object.keys(element)[0]]: Object.values(element)[0].value } return { ...acc, [Object.keys(element)[0]]: Object.values(element)[0].value }

View file

@ -28,7 +28,7 @@ export default {
name: 'EditableKeywordInput', name: 'EditableKeywordInput',
props: { props: {
data: { data: {
type: [Object, Array], type: Array,
default: function() { default: function() {
return {} return {}
} }
@ -45,14 +45,11 @@ export default {
return {} return {}
} }
} }
},
computed: {
}, },
methods: { methods: {
addIconToIcons() { addIconToIcons() {
const updatedValue = [...this.data, [{ key: '', value: '', id: this.generateID() }]] const updatedValue = [...this.data, [{ key: '', value: '', id: this.generateID() }]]
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key) this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
}, },
addValueToIcons(index) { addValueToIcons(index) {
const updatedValue = this.data.map((icon, i) => { const updatedValue = this.data.map((icon, i) => {
@ -61,11 +58,11 @@ export default {
} }
return icon return icon
}) })
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key) this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
}, },
deleteIcondRow(index) { deleteIcondRow(index) {
const filteredValues = this.data.filter((icon, i) => i !== index) const filteredValues = this.data.filter((icon, i) => i !== index)
this.updateSetting(filteredValues, this.settingGroup.group, this.settingGroup.key, this.setting.key) this.updateSetting(filteredValues, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
}, },
generateID() { generateID() {
return `f${(~~(Math.random() * 1e8)).toString(16)}` return `f${(~~(Math.random() * 1e8)).toString(16)}`

View file

@ -23,7 +23,7 @@ export default {
name: 'MascotsInput', name: 'MascotsInput',
props: { props: {
data: { data: {
type: [Object, Array], type: Array,
default: function() { default: function() {
return {} return {}
} }
@ -88,7 +88,7 @@ export default {
updateSetting(value, group, key, input, type) { updateSetting(value, group, key, input, type) {
const mascotsWithoutIDs = value.reduce((acc, mascot) => { const mascotsWithoutIDs = value.reduce((acc, mascot) => {
const { id, ...mascotValue } = Object.values(mascot)[0] const { id, ...mascotValue } = Object.values(mascot)[0]
return { ...acc, [Object.keys(mascot)[0]]: mascotValue } return { ...acc, [Object.keys(mascot)[0]]: ['', mascotValue] }
}, {}) }, {})
this.$store.dispatch('UpdateSettings', { group, key, input, value: mascotsWithoutIDs, type }) this.$store.dispatch('UpdateSettings', { group, key, input, value: mascotsWithoutIDs, type })
this.$store.dispatch('UpdateState', { group, key, input, value }) this.$store.dispatch('UpdateState', { group, key, input, value })

View file

@ -5,7 +5,8 @@
:value="rateLimitAllUsers[0]" :value="rateLimitAllUsers[0]"
placeholder="scale" placeholder="scale"
class="scale-input" class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'oneLimit', rateLimitAllUsers)"/> : @input="parseRateLimiter($event, setting.key, 'scale', 'oneLimit', rateLimitAllUsers)"/>
<span>:</span>
<el-input <el-input
:value="rateLimitAllUsers[1]" :value="rateLimitAllUsers[1]"
placeholder="limit" placeholder="limit"
@ -17,24 +18,26 @@
</div> </div>
</div> </div>
<div v-if="rateLimitAuthUsers"> <div v-if="rateLimitAuthUsers">
<el-form-item label="Unauthenticated users:"> <el-form-item label="Unauthenticated users:" label-width="180px" class="rate-limit">
<el-input <el-input
:value="rateLimitUnauthUsers[0]" :value="rateLimitUnauthUsers[0]"
placeholder="scale" placeholder="scale"
class="scale-input" class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/> : @input="parseRateLimiter($event, setting.key, 'scale', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/>
<span>:</span>
<el-input <el-input
:value="rateLimitUnauthUsers[1]" :value="rateLimitUnauthUsers[1]"
placeholder="limit" placeholder="limit"
class="limit-input" class="limit-input"
@input="parseRateLimiter($event, setting.key, 'limit', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/> @input="parseRateLimiter($event, setting.key, 'limit', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/>
</el-form-item> </el-form-item>
<el-form-item label="Authenticated users:"> <el-form-item label="Authenticated users:" label-width="180px" class="rate-limit">
<el-input <el-input
:value="rateLimitAuthUsers[0]" :value="rateLimitAuthUsers[0]"
placeholder="scale" placeholder="scale"
class="scale-input" class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'authUserslimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/> : @input="parseRateLimiter($event, setting.key, 'scale', 'authUserslimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/>
<span>:</span>
<el-input <el-input
:value="rateLimitAuthUsers[1]" :value="rateLimitAuthUsers[1]"
placeholder="limit" placeholder="limit"

View file

@ -1,93 +0,0 @@
<template>
<div>
<div v-for="subSetting in setting.children" :key="subSetting.key">
<el-form-item :label="subSetting.label" :label-width="customLabelWidth">
<el-select
v-if="subSetting.type.includes('list') && subSetting.type.includes('atom')"
:value="subSettingValue(subSetting)"
multiple
filterable
allow-create
@change="update($event, subSetting.key)">
<el-option v-for="(option, index) in subSetting.suggestions" :key="index" :value="option"/>
</el-select>
<p class="expl">{{ subSetting.description }}</p>
</el-form-item>
</div>
</div>
</template>
<script>
export default {
name: 'SslOptionsInput',
props: {
customLabelWidth: {
type: String,
default: function() {
return this.labelWidth
},
required: false
},
data: {
type: [Object, Array],
default: function() {
return {}
}
},
nested: {
type: Boolean,
default: function() {
return false
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
},
settingParent: {
type: Object,
default: function() {
return {}
},
required: false
}
},
methods: {
inputValue(key) {
return this.data[this.setting.key][key]
},
subSettingValue(subSetting) {
return this.data && this.data[this.setting.key] ? this.data[this.setting.key][subSetting.key] : []
},
update(value, childKey) {
const [group, key, parentKey, input] = [this.settingGroup.group, this.settingGroup.key, this.setting.key, this.settingParent.key]
const { updatedSettings, description } = this.$store.state.settings
const type = description
.find(element => element.group === group && element.key === key).children
.find(child => child.key === ':adapter').children.find(child => child.key === ':ssl_options').children
.find(child => child.key === childKey).type
const updatedState = { ...this.data, [parentKey]: { ...this.data[parentKey], [childKey]: value }}
const updatedSetting = !updatedSettings[group] || !updatedSettings[group][key]
? { [parentKey]: ['keyword', { [childKey]: [type, value] }] }
: { ...updatedSettings[group][key][parentKey], [parentKey]: { ...updatedSettings[group][key][parentKey], [childKey]: [type, value] }}
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSetting, type: this.settingParent.type })
this.$store.dispatch('UpdateState', { group, key, input, value: updatedState })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -6,4 +6,3 @@ export { default as MultipleSelect } from './MultipleSelect'
export { default as ProxyUrlInput } from './ProxyUrlInput' export { default as ProxyUrlInput } from './ProxyUrlInput'
export { default as PruneInput } from './PruneInput' export { default as PruneInput } from './PruneInput'
export { default as RateLimitInput } from './RateLimitInput' export { default as RateLimitInput } from './RateLimitInput'
export { default as SslOptionsInput } from './SslOptionsInput'

View file

@ -17,15 +17,9 @@
<el-tab-pane :label="$t('settings.captcha')" lazy> <el-tab-pane :label="$t('settings.captcha')" lazy>
<captcha/> <captcha/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('settings.database')" lazy>
<database/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.emojiPacks')" lazy> <el-tab-pane :label="$t('settings.emojiPacks')" lazy>
<emoji-packs/> <emoji-packs/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :label="$t('settings.endpoint')" lazy>
<endpoint/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.frontend')" lazy> <el-tab-pane :label="$t('settings.frontend')" lazy>
<frontend/> <frontend/>
</el-tab-pane> </el-tab-pane>
@ -76,11 +70,11 @@
</template> </template>
<script> <script>
import { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush } from './components' import { ActivityPub, Authentication, AutoLinker, Captcha, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush } from './components'
import EmojiPacks from '../emojiPacks/index' import EmojiPacks from '../emojiPacks/index'
export default { export default {
components: { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, EmojiPacks, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush }, components: { ActivityPub, Authentication, AutoLinker, Captcha, EmojiPacks, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush },
data() { data() {
return { return {
activeTab: 'instance' activeTab: 'instance'

View file

@ -23,6 +23,9 @@
.el-form-item { .el-form-item {
margin-right: 30px; margin-right: 30px;
} }
.el-form-item .rate-limit {
margin-right: 0;
}
.center-label label { .center-label label {
text-align: center; text-align: center;
} }
@ -95,8 +98,8 @@
margin-left: 10px; margin-left: 10px;
} }
.limit-input { .limit-input {
width: 48%; width: 47%;
margin: 0 0 5px 8px margin: 0 0 5px 1%
} }
.line { .line {
width: 100%; width: 100%;
@ -130,6 +133,15 @@
margin-left: 8px; margin-left: 8px;
margin-right: 10px margin-right: 10px
} }
.replacement-input {
width: 80%;
margin-left: 8px;
margin-right: 10px
}
.scale-input {
width: 47%;
margin: 0 1% 5px 0
}
.setting-input { .setting-input {
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;
@ -137,22 +149,13 @@
.single-input { .single-input {
margin-right: 10px margin-right: 10px
} }
.scale-input { .ssl-tls-opts {
width: 48%; margin: 36px 0 0 0;
margin: 0 8px 5px 0
}
.replacement-input {
width: 80%;
margin-left: 8px;
margin-right: 10px
} }
.text { .text {
line-height: 20px; line-height: 20px;
margin-right: 15px margin-right: 15px
} }
.ssl-tls-opts {
margin: 36px 0 0 0;
}
.upload-container { .upload-container {
display: flex; display: flex;
align-items: baseline; align-items: baseline;

View file

@ -31,7 +31,8 @@ describe('Wrap settings', () => {
const result = wrapUpdatedSettings(':mime', settings, {}) const result = wrapUpdatedSettings(':mime', settings, {})
const expectedResult = [{ const expectedResult = [{
group: ':mime', group: ':mime',
key: ':types', value: { key: ':types',
value: {
'application/ld+json': ['activity+json'], 'application/ld+json': ['activity+json'],
'application/xml': ['xml'], 'application/xml': ['xml'],
'application/xrd+xml': ['xrd+xml'] 'application/xrd+xml': ['xrd+xml']
@ -39,4 +40,335 @@ describe('Wrap settings', () => {
}] }]
expect(_.isEqual(result, expectedResult)).toBeTruthy() expect(_.isEqual(result, expectedResult)).toBeTruthy()
}) })
it('wraps :mascots setting in group :assets', () => {
const settings = { ':assets': { ':mascots': [['keyword', 'map'], {
':pleroma_fox_tan_shy': ['', { ':mime_type': 'image/png', ':url': '/images/pleroma-fox-tan-shy.png' }],
':pleroma_fox_tan': ['', { ':mime_type': 'image/png', ':url': '/images/pleroma-fox-tan-smol.png' }],
}]}}
const state = { ':pleroma': { ':assets': {}}}
const result = wrapUpdatedSettings(':pleroma', settings, state)
const expectedResult = [{
group: ':pleroma',
key: ':assets',
value: [{ tuple: [':mascots', [
{ tuple: [':pleroma_fox_tan_shy', { ':mime_type': 'image/png', ':url': '/images/pleroma-fox-tan-shy.png'}] },
{ tuple: [':pleroma_fox_tan', { ':mime_type': 'image/png', ':url': '/images/pleroma-fox-tan-smol.png'}] }
]]}]
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
it('wraps settings with type keyword', () => {
const settings1 = { 'Pleroma.Upload': { ':proxy_opts':
[ 'keyword', {
':redirect_on_failure': ['boolean', true],
':http': ['keyword', { ':proxy_url': [['string', 'tuple'], 'localhost:3090' ]}]
}]
}}
const state1 = { ':pleroma': { 'Pleroma.Upload': {}}}
const result1 = wrapUpdatedSettings(':pleroma', settings1, state1)
const expectedResult1 = [{
group: ':pleroma',
key: 'Pleroma.Upload',
value: [{ tuple: [':proxy_opts', [
{ tuple: [':redirect_on_failure', true] },
{ tuple: [':http', [{ tuple: [':proxy_url', 'localhost:3090'] }]] }
]]}]
}]
const settings2 = { ':media_proxy': { ':proxy_opts':
['keyword', {
':max_body_length': ['integer', 26210000],
':http': ['keyword', {
':proxy_url': [['string', 'tuple'], [':socks5', '127.0.0.1', '9020']],
':adapter': ['keyword', {
':ssl_options': ['keyword', {
':versions': [['list', 'atom'], [':tlsv1', ':tlsv1.1']]
}]
}]
}]
}]
}}
const state2 = { ':pleroma': { ':media_proxy': {}}}
const result2 = wrapUpdatedSettings(':pleroma', settings2, state2)
const expectedResult2 = [{
group: ':pleroma',
key: ':media_proxy',
value: [{ tuple: [':proxy_opts', [
{ tuple: [':max_body_length', 26210000] },
{ tuple: [':http',
[{ tuple: [ ':proxy_url', { tuple: [ ':socks5', '127.0.0.1', '9020' ] }]},
{ tuple: [':adapter',
[{ tuple: [':ssl_options', [{ tuple: [':versions', [':tlsv1', ':tlsv1.1']]}]]}]
]}]
]}
]]}]
}]
expect(_.isEqual(result1, expectedResult1)).toBeTruthy()
expect(_.isEqual(result2, expectedResult2)).toBeTruthy()
})
it('wraps settings that includes keyword in type', () => {
const settings1 = { 'Oban': { ':queues': [
['keyword', 'integer'],
{ ':activity_expiration': ['integer', 15],
':background': ['integer', 10],
':federator_incoming': ['integer', 30]}
]}}
const state1 = { ':pleroma': { 'Oban': {}}}
const result1 = wrapUpdatedSettings(':pleroma', settings1, state1)
const expectedResult1 = [{
group: ':pleroma',
key: 'Oban',
value: [{ tuple: [':queues', [
{ tuple: [':activity_expiration', 15] },
{ tuple: [':background', 10] },
{ tuple: [':federator_incoming', 30]}
]]}]
}]
const settings2 = { ':emoji': { ':groups': [
['keyword', 'string', ['list', 'string']],
{ ':custom': [['list'], ['/emoji/*.png', '/emoji/**/*.png']],
':another_group': ['list', ['/custom_emoji/*.png']]}
]}}
const state2 = { ':pleroma': { ':emoji': {}}}
const result2 = wrapUpdatedSettings(':pleroma', settings2, state2)
const expectedResult2 = [{
group: ':pleroma',
key: ':emoji',
value: [{ tuple: [':groups', [
{ tuple: [':custom', ['/emoji/*.png', '/emoji/**/*.png']]},
{ tuple: [':another_group', ['/custom_emoji/*.png']]}
]]}]
}]
expect(_.isEqual(result1, expectedResult1)).toBeTruthy()
expect(_.isEqual(result2, expectedResult2)).toBeTruthy()
})
it('wraps :replace setting', () => {
const settings = { ':mrf_keyword': { ':replace': [
[['tuple', 'string', 'string'], ['tuple', 'regex', 'string']],
{ 'pattern': ['list', 'replacement'],
'/\w+/': ['list', 'test_replacement']}
]}}
const state = { ':pleroma': { ':mrf_keyword': {}}}
const result = wrapUpdatedSettings(':pleroma', settings, state)
const expectedResult = [{
group: ':pleroma',
key: ':mrf_keyword',
value: [{ tuple: [':replace', [
{ tuple: ['pattern', 'replacement'] },
{ tuple: ['/\w+/', 'test_replacement'] }
]]}]
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
it('wraps settings with type atom', () => {
const settings = {
':ldap': { ':sslopts': [ 'keyword', { ':verify': ['atom', 'verify_peer']}]},
':assets': { ':default_mascot': ['atom', 'pleroma_fox_tan_test']}
}
const state = { ':pleroma': { ':sslopts': {}, ':assets': {}}}
const result = wrapUpdatedSettings(':pleroma', settings, state)
const expectedResult = [{
group: ':pleroma',
key: ':ldap',
value: [{ tuple: [':sslopts', [{tuple: [':verify', ':verify_peer']}]]}]
}, {
group: ':pleroma',
key: ':assets',
value: [{ tuple: [':default_mascot', ':pleroma_fox_tan_test']}]
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
it('wraps settings with type string and tuple', () => {
const settings1 = { ':media_proxy': { ':proxy_opts':
['keyword', {
':http': ['keyword', { ':proxy_url': [['string', 'tuple'], 'localhost:9020']}]
}]
}}
const state1 = { ':pleroma': { ':media_proxy': {}}}
const result1 = wrapUpdatedSettings(':pleroma', settings1, state1)
const expectedResult1 = [{
group: ':pleroma',
key: ':media_proxy',
value: [{ tuple: [':proxy_opts', [
{ tuple: [':http',
[{ tuple: [':proxy_url', 'localhost:9020'] }]
]}
]]}]
}]
const settings2 = { ':media_proxy': { ':proxy_opts':
['keyword', {
':http': ['keyword', { ':proxy_url': [['string', 'tuple'], [':socks5', '127.0.0.1', '9020']]}]
}]
}}
const state2 = { ':pleroma': { ':media_proxy': {}}}
const result2 = wrapUpdatedSettings(':pleroma', settings2, state2)
const expectedResult2 = [{
group: ':pleroma',
key: ':media_proxy',
value: [{ tuple: [':proxy_opts', [
{ tuple: [':http',
[{ tuple: [ ':proxy_url', { tuple: [':socks5', '127.0.0.1', '9020'] }]}]
]}
]]}]
}]
expect(_.isEqual(result1, expectedResult1)).toBeTruthy()
expect(_.isEqual(result2, expectedResult2)).toBeTruthy()
})
it('wraps settings with type atom and tuple', () => {
const settings1 = { 'Oban': { ':prune': [['atom', 'tuple'], ':disabled']}}
const state1 = { ':pleroma': { 'Oban': {}}}
const result1 = wrapUpdatedSettings(':pleroma', settings1, state1)
const expectedResult1 = [{
group: ':pleroma',
key: 'Oban',
value: [{ tuple: [':prune', ':disabled']}]
}]
const settings2 = { 'Oban': { ':prune':
[['atom', 'tuple'], [':maxlen', 1500]]
}}
const state2 = { ':pleroma': { 'Oban': {}}}
const result2 = wrapUpdatedSettings(':pleroma', settings2, state2)
const expectedResult2 = [{
group: ':pleroma',
key: 'Oban',
value: [{ tuple: [':prune', {tuple: [':maxlen', 1500]}]}]
}]
expect(_.isEqual(result1, expectedResult1)).toBeTruthy()
expect(_.isEqual(result2, expectedResult2)).toBeTruthy()
})
it('wraps settings with type map', () => {
const settings1 = { ':instance': { ':poll_limits': ['map', { ':min_expiration': ['integer', 100] }]}}
const state1 = { ':pleroma': { ':instance': { ':poll_limits': {
':max_expiration': 31536000,
':max_option_chars': 200,
':max_options': 20,
':min_expiration': 100
}}}}
const result1 = wrapUpdatedSettings(':pleroma', settings1, state1)
const expectedResult1 = [{
group: ':pleroma',
key: ':instance',
value: [{ tuple: [':poll_limits', {
':max_expiration': 31536000,
':max_option_chars': 200,
':max_options': 20,
':min_expiration': 100
}]}]
}]
const settings2 = { ':email_notifications': { ':digest': ['map', {
':active': ['boolean', true],
':schedule': ['string', '0 0 0'],
':inactivity_threshold': ['integer', 10]
}]}}
const state2 = { ':pleroma': { ':email_notifications': { ':digest': {
':active': true,
':inactivity_threshold': 10,
':interval': 7,
':schedule': '0 0 0'
}}}}
const result2 = wrapUpdatedSettings(':pleroma', settings2, state2)
const expectedResult2 = [{
group: ':pleroma',
key: ':email_notifications',
value: [{ tuple: [':digest', {
':active': true,
':inactivity_threshold': 10,
':interval': 7,
':schedule': '0 0 0'
}]}]
}]
const settings3 = { ':mrf_subchain': { ':match_actor': ['map', {
'~r/https:\/\/example.com/s': ['Elixir.Pleroma.Web.ActivityPub.MRF.DropPolicy'],
'~r/https:\/\/test.com': ['Elixir.Pleroma.Web.ActivityPub.MRF.TestPolicy']
}]}}
const state3 = { ':pleroma': { ':mrf_subchain': { ':match_actor': [
{ '~r/https:\/\/example.com/s': ['Elixir.Pleroma.Web.ActivityPub.MRF.DropPolicy'] },
{ '~r/https:\/\/test.com': ['Elixir.Pleroma.Web.ActivityPub.MRF.TestPolicy'] }
]
}}}
const result3 = wrapUpdatedSettings(':pleroma', settings3, state3)
const expectedResult3 = [{
group: ':pleroma',
key: ':mrf_subchain',
value: [{ tuple: [':match_actor', {
'~r/https:\/\/example.com/s': ['Elixir.Pleroma.Web.ActivityPub.MRF.DropPolicy'],
'~r/https:\/\/test.com': ['Elixir.Pleroma.Web.ActivityPub.MRF.TestPolicy']
}]}]
}]
expect(_.isEqual(result1, expectedResult1)).toBeTruthy()
expect(_.isEqual(result2, expectedResult2)).toBeTruthy()
expect(_.isEqual(result3, expectedResult3)).toBeTruthy()
})
it('wraps IP setting', () => {
const settings = { ':gopher': { ':ip': ['tuple', '127.0.0.1']}}
const state = { ':pleroma': { ':gopher': {}}}
const result = wrapUpdatedSettings(':pleroma', settings, state)
const expectedResult = [{
group: ':pleroma',
key: ':gopher',
value: [{ tuple: [':ip', { tuple: [127, 0, 0, 1] }]}]
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
it('wraps args setting in Pleroma.Upload.Filter.Mogrify group', () => {
const settings = { 'Pleroma.Upload.Filter.Mogrify': { ':args': [
['string', ['list', 'string'], ['list', 'tuple']],
['strip', 'implode']
]}}
const state = { ':pleroma': { 'Pleroma.Upload.Filter.Mogrify': {}}}
const result = wrapUpdatedSettings(':pleroma', settings, state)
const expectedResult = [{
group: ':pleroma',
key: 'Pleroma.Upload.Filter.Mogrify',
value: [{tuple: [':args', ['strip', {tuple: ['implode', '1']}]]}]
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
it('wraps regular settings', () => {
const settings = { ':http_security': {
':report_uri': ["string", "https://test.com"],
':ct_max_age': ["integer", 150000],
':sts': ["boolean", true],
':methods': [["list", "string"], ["POST", "PUT", "PATCH"]]
}}
const state = { ':pleroma': { ':http_security': {}}}
const result = wrapUpdatedSettings(':pleroma', settings, state)
const expectedResult = [{
group: ':pleroma',
key: ':http_security',
value: [
{ tuple: [":report_uri", "https://test.com"] },
{ tuple: [":ct_max_age", 150000] },
{ tuple: [":sts", true] },
{ tuple: [":methods", ["POST", "PUT", "PATCH"]] }
]
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
}) })

View file

@ -1868,11 +1868,6 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"
brace@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58"
integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=
braces@^2.2.2, braces@^2.3.1, braces@^2.3.2: braces@^2.2.2, braces@^2.3.1, braces@^2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
@ -10207,13 +10202,6 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue2-ace-editor@^0.0.13:
version "0.0.13"
resolved "https://registry.yarnpkg.com/vue2-ace-editor/-/vue2-ace-editor-0.0.13.tgz#5528998ce2c13e8ed3a294f714298199fd107dc2"
integrity sha512-uQICyvJzYNix16xeYjNAINuNUQhPbqMR7UQsJeI+ycpEd2otsiNNU73jcZqHkpjuz0uaHDHnrpzQuI/RApsKXA==
dependencies:
brace "^0.11.0"
vue@^2.6.8: vue@^2.6.8:
version "2.6.8" version "2.6.8"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.8.tgz#f21cbc536bfc14f7d1d792a137bb12f69e60ea91" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.8.tgz#f21cbc536bfc14f7d1d792a137bb12f69e60ea91"