diff --git a/package.json b/package.json index 73c2fc16d15b12b7a431c03d270a226d7ce882d6..8d5cd449e3996e4e029c2931b41bf9dab73fe6f7 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "vue-i18n": "^8.9.0", "vue-router": "3.0.2", "vue-splitpane": "1.0.2", - "vue2-ace-editor": "^0.0.13", "vuedraggable": "^2.16.0", "vuex": "3.0.1", "xlsx": "^0.11.16" diff --git a/src/api/settings.js b/src/api/settings.js index c1bbb182be3401e7d6df253e3fe69cca90ccb125..9d1c28902c1d213834a7557767958845ffeddb4d 100644 --- a/src/api/settings.js +++ b/src/api/settings.js @@ -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()}` } : {} diff --git a/src/lang/en.js b/src/lang/en.js index 3dcd9bddd672efafaf27ebdbb9ad24ba53b22c30..1a8a8c9007c725227d3c000078b1427012caaec1 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -339,12 +339,10 @@ export default { mediaProxy: 'Media Proxy', metadata: 'Metadata', gopher: 'Gopher', - endpoint: 'Endpoint', jobQueue: 'Job queue', webPush: 'Web push encryption', esshd: 'BBS / SSH access', rateLimiters: 'Rate limiters', - database: 'Database', other: 'Other', relays: 'Relays', follow: 'Follow', @@ -383,6 +381,7 @@ export default { file: 'File', update: 'Update', remove: 'Remove', + removeFromDB: 'Remove setting from the DB', selectLocalPack: 'Select the local pack to copy to', localPack: 'Local pack', specifyShortcode: 'Specify a custom shortcode', @@ -405,7 +404,8 @@ export default { nowNewPacksToImport: 'No new packs to import', successfullyUpdated: 'Successfully updated', metadatLowerCase: 'metadata', - files: 'files' + files: 'files', + successfullyRemoved: 'Setting removed successfully!' }, invites: { inviteTokens: 'Invite tokens', diff --git a/src/store/modules/normalizers.js b/src/store/modules/normalizers.js index 17b7c5f0f1b32a74a5654ca6305094e1e3a1aff3..6d0efe29f6c3a145d1088fb384a016a23b7fc64d 100644 --- a/src/store/modules/normalizers.js +++ b/src/store/modules/normalizers.js @@ -1,3 +1,5 @@ +import _ from 'lodash' + export const checkPartialUpdate = (settings, updatedSettings, description) => { return Object.keys(updatedSettings).reduce((acc, group) => { acc[group] = Object.keys(updatedSettings[group]).reduce((acc, key) => { @@ -20,12 +22,22 @@ export const checkPartialUpdate = (settings, updatedSettings, description) => { }, {}) } -const getCurrentValue = (object, keys) => { - if (keys.length === 0) { - return object +const getCurrentValue = (type, value, path) => { + if (type === 'state') { + 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]) => { @@ -136,24 +148,26 @@ export const processNested = (valueForState, valueForUpdatedSettings, group, par const [{ key, type }, ...otherParents] = parents const path = [group, parentKey, ...parents.reverse().map(parent => parent.key).slice(0, -1)] - let updatedValueForState = valueExists(settings, path) - ? { ...getCurrentValue(settings[group][parentKey], parents.map(el => el.key).slice(0, -1)), + let updatedValueForState = valueExists('state', settings, path) + ? { ...getCurrentValue('state', settings[group][parentKey], parents.map(el => el.key).slice(0, -1)), ...{ [key]: valueForState }} : { [key]: valueForState } - let updatedValueForUpdatedSettings = valueExists(updatedSettings, path) - ? { ...getCurrentValue(updatedSettings[group][parentKey], parents.map(el => el.key).slice(0, -1))[1], + let updatedValueForUpdatedSettings = valueExists('updatedSettings', updatedSettings, path) + ? { ...getCurrentValue('updatedSettings', updatedSettings[group][parentKey], parents.map(el => el.key).slice(0, -1))[1], ...{ [key]: [type, valueForUpdatedSettings] }} : { [key]: [type, valueForUpdatedSettings] } if (group === ':mime' && parents[0].key === ':types') { - updatedValueForState = { ...settings[group][parents[0].key].value, ...updatedValueForState } - updatedValueForUpdatedSettings = { - ...Object.keys(settings[group][parents[0].key].value) + updatedValueForState = settings[group][parents[0].key] + ? { ...settings[group][parents[0].key].value, ...updatedValueForState } + : updatedValueForState + updatedValueForUpdatedSettings = settings[group][parents[0].key] + ? { ...Object.keys(settings[group][parents[0].key].value) .reduce((acc, el) => { return { ...acc, [el]: [type, settings[group][parents[0].key].value[el]] } }, {}), - ...updatedValueForUpdatedSettings - } + ...updatedValueForUpdatedSettings } + : updatedValueForUpdatedSettings } return otherParents.length === 1 @@ -161,12 +175,25 @@ export const processNested = (valueForState, valueForUpdatedSettings, group, par : processNested(updatedValueForState, updatedValueForUpdatedSettings, group, parentKey, otherParents, settings, updatedSettings) } -const valueExists = (value, path) => { - if (path.length === 0) { - return true +const valueExists = (type, value, path) => { + if (type === 'state') { + 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) => { @@ -218,8 +245,6 @@ const wrapValues = (settings, currentState) => { } else if (setting === ':ip') { const ip = value.split('.').map(s => parseInt(s, 10)) return { 'tuple': [setting, { 'tuple': ip }] } - } else if (setting === ':ssl_options') { - return { 'tuple': [setting, wrapValues(value, currentState)] } } else if (setting === ':args') { const index = value.findIndex(el => el === 'implode') const updatedArray = value.slice() diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js index 7efc29b51b213baab3fac5a969975c3b225ba6c2..c102c6eb94dbb87acd8661dfd0e38f8aa06e776f 100644 --- a/src/store/modules/settings.js +++ b/src/store/modules/settings.js @@ -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 _ from 'lodash' const settings = { state: { description: [], - settings: { - ':auto_linker': {}, - ':cors_plug': {}, - ':esshd': {}, - ':http_signatures': {}, - ':logger': {}, - ':mime': {}, - ':phoenix': {}, - ':pleroma': {}, - ':prometheus': {}, - ':quack': {}, - ':tesla': {}, - ':ueberauth': {}, - ':web_push_encryption': {} - }, + settings: {}, updatedSettings: {}, - ignoredIfNotEnabled: ['enabled', 'handler', 'password_authenticator', 'port', 'priv_dir'], + db: {}, loading: true }, mutations: { CLEAR_UPDATED_SETTINGS: (state) => { 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) => { state.description = data }, @@ -38,10 +31,19 @@ const settings = { const parsedValue = valueHasTuples(key, value) ? { value: parseNonTuples(key, value) } : parseTuples(value, key) - acc[group][key] = { ...acc[group][key], ...parsedValue } + acc[group] = acc[group] ? { ...acc[group], [key]: parsedValue } : { [key]: parsedValue } + return acc + }, {}) + + const newDbSettings = data.reduce((acc, { group, key, db }) => { + if (db) { + acc[group] = acc[group] ? { ...acc[group], [key]: db } : { [key]: db } + } return acc - }, state.settings) + }, {}) + state.settings = newSettings + state.db = newDbSettings }, UPDATE_SETTINGS: (state, { group, key, input, value, type }) => { 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_LOADING', false) }, - async RemoveSetting({ getters }, configs) { + async RemoveSetting({ commit, getters }, configs) { 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 }) { const updatedData = checkPartialUpdate(state.settings, state.updatedSettings, state.description) @@ -75,7 +81,8 @@ const 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('CLEAR_UPDATED_SETTINGS') }, @@ -84,24 +91,14 @@ const settings = { ? commit('UPDATE_SETTINGS', { group, key, input, value, type }) : commit('UPDATE_SETTINGS', { group, key: input, input: '_value', value, type }) }, - UpdateState({ commit, dispatch, state }, { group, key, input, value }) { + async UpdateState({ commit, getters, state }, { group, key, input, value }) { if (key === 'Pleroma.Emails.Mailer' && input === ':adapter') { const subkeys = Object.keys(state.settings[group][key]).filter(el => el !== ':adapter') - const emailsValue = subkeys.map(el => { - return { 'tuple': [el, state.settings[group][key][el]] } - }) - dispatch('RemoveSetting', [{ group, key, value: emailsValue, delete: true, subkeys }]) + await removeSettings([{ group, key, delete: true, subkeys }], getters.authHost, getters.token) } key ? commit('UPDATE_STATE', { group, key, input, value }) : commit('UPDATE_STATE', { group, key: input, input: 'value', value }) - }, - async UploadMedia({ dispatch, getters, state }, { file, tab, inputName, childName }) { - const response = await uploadMedia(file, getters.authHost, getters.token) - const updatedValue = childName - ? { ...state.settings[tab][inputName], ...{ [childName]: response.data.url }} - : response.data.url - dispatch('UpdateSettings', { tab, data: { [inputName]: updatedValue }}) } } } diff --git a/src/views/settings/components/ActivityPub.vue b/src/views/settings/components/ActivityPub.vue index 4c99296d0864d8420354b4a51b60e74338a8ae22..2eb9a654170e36c05ab3c248f4334299b994bf38 100644 --- a/src/views/settings/components/ActivityPub.vue +++ b/src/views/settings/components/ActivityPub.vue @@ -17,6 +17,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'ActivityPub', @@ -29,13 +30,13 @@ export default { return this.settings.description.find(setting => setting.key === ':activitypub') }, activitypubData() { - return this.settings.settings[':pleroma'][':activitypub'] + return _.get(this.settings.settings, [':pleroma', ':activitypub']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.$store.state.settings.loading @@ -44,7 +45,7 @@ export default { return this.settings.description.find(setting => setting.key === ':user') }, userData() { - return this.settings.settings[':pleroma'][':user'] + return _.get(this.settings.settings, [':pleroma', ':user']) || {} } }, methods: { diff --git a/src/views/settings/components/Authentication.vue b/src/views/settings/components/Authentication.vue index c37fe5376b0f4586352fe5a4bd61ed259c0bb9bc..f88b5b40f4d76fe0f3534dbd5da4c914592eb924 100644 --- a/src/views/settings/components/Authentication.vue +++ b/src/views/settings/components/Authentication.vue @@ -25,6 +25,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Authentication', @@ -37,19 +38,19 @@ export default { return this.settings.description.find(setting => setting.key === ':auth') }, authData() { - return this.settings.settings[':pleroma'][':auth'] + return _.get(this.settings.settings, [':pleroma', ':auth']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, ldap() { return this.settings.description.find(setting => setting.key === ':ldap') }, ldapData() { - return this.settings.settings[':pleroma'][':ldap'] + return _.get(this.settings.settings, [':pleroma', ':ldap']) || {} }, loading() { return this.settings.loading @@ -58,13 +59,13 @@ export default { return this.settings.description.find(setting => setting.key === ':oauth2') }, oauth2Data() { - return this.settings.settings[':pleroma'][':oauth2'] + return _.get(this.settings.settings, [':pleroma', ':oauth2']) || {} }, pleromaAuthenticator() { return this.settings.description.find(setting => setting.description === 'Authenticator') }, pleromaAuthenticatorData() { - return this.settings.settings[':pleroma']['Pleroma.Web.Auth.Authenticator'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Web.Auth.Authenticator']) || {} } }, methods: { diff --git a/src/views/settings/components/AutoLinker.vue b/src/views/settings/components/AutoLinker.vue index 29c1f69b2a9dd21daf15f32028144988709f82fd..e5fb7529ef7821dd7bfc9bc5c50a921289099656 100644 --- a/src/views/settings/components/AutoLinker.vue +++ b/src/views/settings/components/AutoLinker.vue @@ -11,6 +11,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'AutoLinker', @@ -23,13 +24,13 @@ export default { return this.settings.description.find(setting => setting.key === ':opts') }, autoLinkerData() { - return this.settings.settings[':auto_linker'][':opts'] + return _.get(this.settings.settings, [':auto_linker', ':opts']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading diff --git a/src/views/settings/components/Captcha.vue b/src/views/settings/components/Captcha.vue index 0a61f0bf6d4b7b1664437937aecec18eb8e729af..5a942793899e054d51b07149f040919bc6741088 100644 --- a/src/views/settings/components/Captcha.vue +++ b/src/views/settings/components/Captcha.vue @@ -17,6 +17,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Captcha', @@ -29,7 +30,7 @@ export default { return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha') }, captchaData() { - return this.settings.settings[':pleroma']['Pleroma.Captcha'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Captcha']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' @@ -38,10 +39,10 @@ export default { return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha.Kocaptcha') }, kocaptchaData() { - return this.settings.settings[':pleroma']['Pleroma.Captcha.Kocaptcha'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Captcha.Kocaptcha']) || {} }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading diff --git a/src/views/settings/components/Database.vue b/src/views/settings/components/Database.vue deleted file mode 100644 index 773472f1d1c7ad6199ca620efcb68592e365f112..0000000000000000000000000000000000000000 --- a/src/views/settings/components/Database.vue +++ /dev/null @@ -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> diff --git a/src/views/settings/components/Endpoint.vue b/src/views/settings/components/Endpoint.vue deleted file mode 100644 index 6becdbb2f6acc5821fca99a9bdc0f7eb942e17be..0000000000000000000000000000000000000000 --- a/src/views/settings/components/Endpoint.vue +++ /dev/null @@ -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> diff --git a/src/views/settings/components/Esshd.vue b/src/views/settings/components/Esshd.vue index c66ec01b35e5c9f1a429a4dbaecba2e489a5e5ad..6ca3ae15e3b2ecfad150a734789af273e6d02eec 100644 --- a/src/views/settings/components/Esshd.vue +++ b/src/views/settings/components/Esshd.vue @@ -20,6 +20,7 @@ import i18n from '@/lang' import { mapGetters } from 'vuex' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Esshd', @@ -32,13 +33,13 @@ export default { return this.settings.description.find(setting => setting.group === ':esshd') }, esshdData() { - return this.settings.settings[':esshd'] + return _.get(this.settings.settings, [':esshd']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading diff --git a/src/views/settings/components/Frontend.vue b/src/views/settings/components/Frontend.vue index fb969532cbd471e301b95bc37b5b592e5312b5d8..72c236a8304e19a95985356ef1d26cdc48fbc527 100644 --- a/src/views/settings/components/Frontend.vue +++ b/src/views/settings/components/Frontend.vue @@ -1,11 +1,6 @@ <template> <div v-if="!loading"> <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"/> </el-form> <el-form ref="assetsData" :model="assetsData" :label-width="labelWidth"> @@ -36,6 +31,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Frontend', @@ -48,37 +44,37 @@ export default { return this.settings.description.find(setting => setting.key === ':assets') }, assetsData() { - return this.settings.settings[':pleroma'][':assets'] + return _.get(this.settings.settings, [':pleroma', ':assets']) || {} }, chat() { return this.settings.description.find(setting => setting.key === ':chat') }, chatData() { - return this.settings.settings[':pleroma'][':chat'] + return _.get(this.settings.settings, [':pleroma', ':chat']) || {} }, emoji() { return this.settings.description.find(setting => setting.key === ':emoji') }, emojiData() { - return this.settings.settings[':pleroma'][':emoji'] + return _.get(this.settings.settings, [':pleroma', ':emoji']) || {} }, frontend() { return this.settings.description.find(setting => setting.key === ':frontend_configurations') }, frontendData() { - return this.settings.settings[':pleroma'][':frontend_configurations'] + return _.get(this.settings.settings, [':pleroma', ':frontend_configurations']) || {} }, markup() { return this.settings.description.find(setting => setting.key === ':markup') }, markupData() { - return this.settings.settings[':pleroma'][':markup'] + return _.get(this.settings.settings, [':pleroma', ':markup']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading diff --git a/src/views/settings/components/Gopher.vue b/src/views/settings/components/Gopher.vue index 80d643400ea19a68c53b7392837b0ec69e5f5496..97b531fc7407f2218732647e9ad7ba7b41ad17ae 100644 --- a/src/views/settings/components/Gopher.vue +++ b/src/views/settings/components/Gopher.vue @@ -11,6 +11,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Gopher', @@ -23,13 +24,13 @@ export default { return this.settings.description.find(setting => setting.key === ':gopher') }, gopherData() { - return this.settings.settings[':pleroma'][':gopher'] + return _.get(this.settings.settings, [':pleroma', ':gopher']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading diff --git a/src/views/settings/components/Http.vue b/src/views/settings/components/Http.vue index a553657de0796f4eb1a8a12f39f864adb81e1a05..7c6292ead48028c29d2282291008649f8262314c 100644 --- a/src/views/settings/components/Http.vue +++ b/src/views/settings/components/Http.vue @@ -29,6 +29,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'HTTP', @@ -41,31 +42,31 @@ export default { return this.settings.description.find(setting => setting.group === ':cors_plug') }, corsPlugData() { - return this.settings.settings[':cors_plug'] + return _.get(this.settings.settings, [':cors_plug']) || {} }, http() { return this.settings.description.find(setting => setting.key === ':http') }, httpData() { - return this.settings.settings[':pleroma'][':http'] + return _.get(this.settings.settings, [':pleroma', ':http']) || {} }, httpSecurity() { return this.settings.description.find(setting => setting.key === ':http_security') }, httpSecurityData() { - return this.settings.settings[':pleroma'][':http_security'] + return _.get(this.settings.settings, [':pleroma', ':http_security']) || {} }, httpSignatures() { return this.settings.description.find(setting => setting.group === ':http_signatures') }, httpSignaturesData() { - return this.settings.settings[':http_signatures'] + return _.get(this.settings.settings, [':http_signatures']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -74,7 +75,7 @@ export default { return this.settings.description.find(setting => setting.key === ':web_cache_ttl') }, webCacheTtlData() { - return this.settings.settings[':pleroma'][':web_cache_ttl'] + return _.get(this.settings.settings, [':pleroma', ':web_cache_ttl']) || {} } }, methods: { diff --git a/src/views/settings/components/Inputs.vue b/src/views/settings/components/Inputs.vue index cfb6d09d793794fb2e042593e5efb2ef34fcb577..760d2c2cd847989d97cd2eb8cbce790a1f0458c8 100644 --- a/src/views/settings/components/Inputs.vue +++ b/src/views/settings/components/Inputs.vue @@ -1,7 +1,13 @@ <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 - v-if="setting.type === 'string'" + v-if="setting.type === 'string' || (setting.type.includes('string') && setting.type.includes('atom'))" :value="inputValue" :placeholder="setting.suggestions ? setting.suggestions[0] : null" @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)"> <el-option v-for="(option, index) in setting.suggestions" :key="index" :value="option"/> </el-select> - <editor - v-if="setting.key === ':dispatch'" - v-model="editorContent" - height="150" - width="100%" - lang="elixir" - theme="chrome"/> <el-input v-if="setting.key === ':ip'" :value="inputValue" @@ -62,7 +61,7 @@ :setting-parent="[...settingParent, subSetting]" :setting="subSetting" :data="data[setting.key]" - :custom-label-width="'100px'" + :custom-label-width="'140px'" :label-class="'center-label'" :input-class="'keyword-inner-input'" :nested="true"/> @@ -70,11 +69,10 @@ </div> <!-- special inputs --> <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"/> - <editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="data" :setting-group="settingGroup" :setting="setting"/> - <icons-input v-if="setting.key === ':icons'" :data="data[':icons']" :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="keywordData" :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"/> - <!-- <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"/> <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"/> @@ -84,16 +82,14 @@ </template> <script> -import AceEditor from 'vue2-ace-editor' -import 'brace/mode/elixir' -import 'default-passive-events' -import { AutoLinkerInput, EditableKeywordInput, IconsInput, MascotsInput, MultipleSelect, ProxyUrlInput, PruneInput, RateLimitInput, SslOptionsInput } from './inputComponents' +import i18n from '@/lang' +import { AutoLinkerInput, EditableKeywordInput, IconsInput, MascotsInput, MultipleSelect, ProxyUrlInput, PruneInput, RateLimitInput } from './inputComponents' import { processNested } from '@/store/modules/normalizers' +import _ from 'lodash' export default { name: 'Inputs', components: { - editor: AceEditor, AutoLinkerInput, EditableKeywordInput, IconsInput, @@ -101,8 +97,7 @@ export default { MultipleSelect, ProxyUrlInput, PruneInput, - RateLimitInput, - SslOptionsInput + RateLimitInput }, props: { customLabelWidth: { @@ -159,13 +154,13 @@ export default { } }, computed: { - editorContent: { - get: function() { - return this.data[this.setting.key] ? this.data[this.setting.key][0] : '' - }, - set: function(value) { - this.processNestedData([value], this.settingGroup.group, this.settingGroup.key, this.settingParent) - } + canBeDeleted() { + const { group, key } = this.settingGroup + return _.get(this.$store.state.settings.db, [group, key]) && + this.$store.state.settings.db[group][key].includes(this.setting.key) + }, + iconsData() { + return Array.isArray(this.data[':icons']) ? this.data[':icons'] : [] }, inputValue() { if ([':esshd', ':cors_plug', ':quack', ':http_signatures', ':tesla'].includes(this.settingGroup.group) && @@ -178,7 +173,7 @@ export default { this.setting.key === ':admin_token') { return this.data.value } 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') { 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 { @@ -186,7 +181,10 @@ export default { } }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' + }, + keywordData() { + return Array.isArray(this.data) ? this.data : [] }, rewritePolicyValue() { 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', { 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) { return Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && ( type.includes('module') || diff --git a/src/views/settings/components/Instance.vue b/src/views/settings/components/Instance.vue index a5af7f9fa66268e100ef7460a13cd651dfd7de77..5a8350bff60f4d81ef3b9d785b4fca3a11936dc1 100644 --- a/src/views/settings/components/Instance.vue +++ b/src/views/settings/components/Instance.vue @@ -42,6 +42,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { 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`) }, adminTokenData() { - return this.settings.settings[':pleroma'][':admin_token'] + return _.get(this.settings.settings, [':pleroma', ':admin_token']) || {} }, fetchInitialPosts() { return this.settings.description.find(setting => setting.key === ':fetch_initial_posts') }, fetchInitialPostsData() { - return this.settings.settings[':pleroma'][':fetch_initial_posts'] + return _.get(this.settings.settings, [':pleroma', ':fetch_initial_posts']) || {} }, instance() { return this.settings.description.find(setting => setting.key === ':instance') }, instanceData() { - return this.settings.settings[':pleroma'][':instance'] + return _.get(this.settings.settings, [':pleroma', ':instance']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -83,31 +84,31 @@ export default { return this.settings.description.find(setting => setting.key === ':manifest') }, manifestData() { - return this.settings.settings[':pleroma'][':manifest'] + return _.get(this.settings.settings, [':pleroma', ':manifest']) || {} }, pleromaUser() { return this.settings.description.find(setting => setting.key === 'Pleroma.User') }, pleromaUserData() { - return this.settings.settings[':pleroma']['Pleroma.User'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.User']) || {} }, scheduledActivity() { return this.$store.state.settings.description.find(setting => setting.key === 'Pleroma.ScheduledActivity') }, scheduledActivityData() { - return this.settings.settings[':pleroma']['Pleroma.ScheduledActivity'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.ScheduledActivity']) || {} }, suggestions() { return this.$store.state.settings.description.find(setting => setting.key === ':suggestions') }, suggestionsData() { - return this.settings.settings[':pleroma'][':suggestions'] + return _.get(this.settings.settings, [':pleroma', ':suggestions']) || {} }, uriSchemes() { return this.$store.state.settings.description.find(setting => setting.key === ':uri_schemes') }, uriSchemesData() { - return this.settings.settings[':pleroma'][':uri_schemes'] + return _.get(this.settings.settings, [':pleroma', ':uri_schemes']) || {} } }, methods: { diff --git a/src/views/settings/components/JobQueue.vue b/src/views/settings/components/JobQueue.vue index 1e31e5365e5ebdca10330393a969dcb73fec852f..41a11015746d787a83acefe576d1d35c3e65f282 100644 --- a/src/views/settings/components/JobQueue.vue +++ b/src/views/settings/components/JobQueue.vue @@ -19,6 +19,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'JobQueue', @@ -31,13 +32,13 @@ export default { return this.settings.description.find(setting => setting.key === 'Pleroma.ActivityExpiration') }, activityExpirationData() { - return this.settings.settings[':pleroma']['Pleroma.ActivityExpiration'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.ActivityExpiration']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -46,13 +47,13 @@ export default { return this.settings.description.find(setting => setting.key === 'Oban') }, obanQueuesData() { - return this.settings.settings[':pleroma']['Oban'] + return _.get(this.settings.settings, [':pleroma', 'Oban']) || {} }, workers() { return this.settings.description.find(setting => setting.key === ':workers') }, workersData() { - return this.settings.settings[':pleroma'][':workers'] + return _.get(this.settings.settings, [':pleroma', ':workers']) || {} } }, methods: { diff --git a/src/views/settings/components/Logger.vue b/src/views/settings/components/Logger.vue index f8f4ed323155ace307961f9252dce610b5efc111..0dd308a416d8b6982c7128b188fbc6ea98d15c2e 100644 --- a/src/views/settings/components/Logger.vue +++ b/src/views/settings/components/Logger.vue @@ -26,6 +26,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Logger', @@ -38,19 +39,19 @@ export default { return this.settings.description.find(setting => setting.key === ':console') }, consoleData() { - return this.settings.settings[':logger'][':console'] + return _.get(this.settings.settings, [':logger', ':console']) || {} }, exsyslogger() { return this.settings.description.find(setting => setting.key === ':ex_syslogger') }, exsysloggerData() { - return this.settings.settings[':logger'][':ex_syslogger'] + return _.get(this.settings.settings, [':logger', ':ex_syslogger']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -59,13 +60,13 @@ export default { return this.settings.description.find(setting => setting.group === ':logger') }, loggerData() { - return this.settings.settings[':logger'][':backends'] + return _.get(this.settings.settings, [':logger', ':backends']) || {} }, quack() { return this.settings.description.find(setting => setting.group === ':quack') }, quackData() { - return this.settings.settings[':quack'] + return _.get(this.settings.settings, [':quack']) || {} } }, methods: { diff --git a/src/views/settings/components/MRF.vue b/src/views/settings/components/MRF.vue index fd3be846982d7c65f437c8ddab8a4fe4db7494e9..3c1584cc0a312d68dc1cd80402403756c2f5d248 100644 --- a/src/views/settings/components/MRF.vue +++ b/src/views/settings/components/MRF.vue @@ -39,6 +39,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'MRF', @@ -51,7 +52,7 @@ export default { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -60,49 +61,49 @@ export default { return this.settings.description.find(setting => setting.key === ':mrf_simple') }, mrfSimpleData() { - return this.settings.settings[':pleroma'][':mrf_simple'] + return _.get(this.settings.settings, [':pleroma', ':mrf_simple']) || {} }, mrfRejectnonpublic() { return this.settings.description.find(setting => setting.key === ':mrf_rejectnonpublic') }, mrfRejectnonpublicData() { - return this.settings.settings[':pleroma'][':mrf_rejectnonpublic'] + return _.get(this.settings.settings, [':pleroma', ':mrf_rejectnonpublic']) || {} }, mrfHellthread() { return this.settings.description.find(setting => setting.key === ':mrf_hellthread') }, mrfHellthreadData() { - return this.settings.settings[':pleroma'][':mrf_hellthread'] + return _.get(this.settings.settings, [':pleroma', ':mrf_hellthread']) || {} }, mrfKeyword() { return this.settings.description.find(setting => setting.key === ':mrf_keyword') }, mrfKeywordData() { - return this.settings.settings[':pleroma'][':mrf_keyword'] + return _.get(this.settings.settings, [':pleroma', ':mrf_keyword']) || {} }, mrfSubchain() { return this.settings.description.find(setting => setting.key === ':mrf_subchain') }, mrfSubchainData() { - return this.settings.settings[':pleroma'][':mrf_subchain'] + return _.get(this.settings.settings, [':pleroma', ':mrf_subchain']) || {} }, mrfMention() { return this.settings.description.find(setting => setting.key === ':mrf_mention') }, mrfMentionData() { - return this.settings.settings[':pleroma'][':mrf_mention'] + return _.get(this.settings.settings, [':pleroma', ':mrf_mention']) || {} }, mrfNormalizeMarkup() { return this.settings.description.find(setting => setting.key === ':mrf_normalize_markup') }, mrfNormalizeMarkupData() { - return this.settings.settings[':pleroma'][':mrf_normalize_markup'] + return _.get(this.settings.settings, [':pleroma', ':mrf_normalize_markup']) || {} }, mrfVocabulary() { return this.settings.description.find(setting => setting.key === ':mrf_vocabulary') }, mrfVocabularyData() { - return this.settings.settings[':pleroma'][':mrf_vocabulary'] + return _.get(this.settings.settings, [':pleroma', ':mrf_vocabulary']) || {} } }, methods: { diff --git a/src/views/settings/components/Mailer.vue b/src/views/settings/components/Mailer.vue index d3215692a01bb0132184fa55d5d456fd23d2e95b..78be7eabaf1e81226bbab91efd6eea51bd8ea7d5 100644 --- a/src/views/settings/components/Mailer.vue +++ b/src/views/settings/components/Mailer.vue @@ -20,6 +20,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Mailer', @@ -34,13 +35,13 @@ export default { return this.settings.description.find(setting => setting.key === ':email_notifications') }, emailNotificationsData() { - return this.settings.settings[':pleroma'][':email_notifications'] + return _.get(this.settings.settings, [':pleroma', ':email_notifications']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.$store.state.settings.loading @@ -49,13 +50,13 @@ export default { return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.Mailer') }, mailerData() { - return this.settings.settings[':pleroma']['Pleroma.Emails.Mailer'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Emails.Mailer']) || {} }, userEmail() { return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.UserEmail') }, userEmailData() { - return this.settings.settings[':pleroma']['Pleroma.Emails.UserEmail'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Emails.UserEmail']) || {} } }, methods: { diff --git a/src/views/settings/components/MediaProxy.vue b/src/views/settings/components/MediaProxy.vue index dac2ea7d6bf133214082e5fa7ca1ac5ecfc00eee..0811df50fd52af54e76c2752a26c4e699a66e8a2 100644 --- a/src/views/settings/components/MediaProxy.vue +++ b/src/views/settings/components/MediaProxy.vue @@ -11,6 +11,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'MediaProxy', @@ -23,7 +24,7 @@ export default { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -32,7 +33,7 @@ export default { return this.settings.description.find(setting => setting.key === ':media_proxy') }, mediaProxyData() { - return this.settings.settings[':pleroma'][':media_proxy'] + return _.get(this.settings.settings, [':pleroma', ':media_proxy']) || {} } }, methods: { diff --git a/src/views/settings/components/Metadata.vue b/src/views/settings/components/Metadata.vue index a13d676bd7be4ec649ac58e6b0242373508ec440..ff4b044819f24fefda1346d4688d0e22ad53a7c4 100644 --- a/src/views/settings/components/Metadata.vue +++ b/src/views/settings/components/Metadata.vue @@ -17,6 +17,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Metadata', @@ -29,7 +30,7 @@ export default { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -38,13 +39,13 @@ export default { return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Metadata') }, metadataData() { - return this.settings.settings[':pleroma']['Pleroma.Web.Metadata'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Web.Metadata']) || {} }, richMedia() { return this.settings.description.find(setting => setting.key === ':rich_media') }, richMediaData() { - return this.settings.settings[':pleroma'][':rich_media'] + return _.get(this.settings.settings, [':pleroma', ':rich_media']) || {} } }, methods: { diff --git a/src/views/settings/components/Other.vue b/src/views/settings/components/Other.vue index 466dc3a1245e03c21709931fafbf5896c7ece1ad..ebb21d51d33a3e034e5ffa9a28faec182dcb5375 100644 --- a/src/views/settings/components/Other.vue +++ b/src/views/settings/components/Other.vue @@ -6,6 +6,9 @@ <div class="line"/> <el-form ref="mimeTypes" :model="mimeTypesData" :label-width="labelWidth"> <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-button type="primary" @click="onSubmit">Submit</el-button> </el-form-item> @@ -17,6 +20,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Other', @@ -29,7 +33,7 @@ export default { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -38,13 +42,19 @@ export default { return this.settings.description.find(setting => setting.group === ':mime') }, 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() { return this.settings.description.find(setting => setting.group === ':tesla') }, teslaAdapterData() { - return this.settings.settings[':tesla'] + return _.get(this.settings.settings, [':tesla']) || {} } }, methods: { diff --git a/src/views/settings/components/RateLimiters.vue b/src/views/settings/components/RateLimiters.vue index cfdeca155ad486af8880540146a3252079345bfc..224d22534f94599f70e21bc7d1773716367214bc 100644 --- a/src/views/settings/components/RateLimiters.vue +++ b/src/views/settings/components/RateLimiters.vue @@ -11,6 +11,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'RateLimiters', @@ -23,13 +24,13 @@ export default { return this.settings.description.find(setting => setting.key === ':rate_limit') }, rateLimitersData() { - return this.settings.settings[':pleroma'][':rate_limit'] + return _.get(this.settings.settings, [':pleroma', ':rate_limit']) || {} }, isMobile() { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.$store.state.settings.loading diff --git a/src/views/settings/components/Setting.vue b/src/views/settings/components/Setting.vue index d11dc723d9bbd449ee6ecd3725834f5bce49bb6b..fb7b2252bbf3f2487c17667dd45501e8cf7b360d 100644 --- a/src/views/settings/components/Setting.vue +++ b/src/views/settings/components/Setting.vue @@ -29,17 +29,30 @@ :nested="false"/> </div> <div v-if="compound(setting)"> - <el-form-item :label="`${setting.label}:`"/> - <div v-for="subSetting in setting.children" :key="subSetting.key"> + <div v-if="!setting.children"> <inputs :setting-group="settingGroup" - :setting-parent="[setting, subSetting]" - :setting="subSetting" + :setting="setting" :data="data[setting.key]" :nested="true"/> </div> - <div v-if="!setting.children"> - <inputs :setting-group="settingGroup" :setting="setting" :data="data[setting.key]" :nested="true"/> + <div v-else> + <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 class="line"/> </div> @@ -49,15 +62,13 @@ </template> <script> -import AceEditor from 'vue2-ace-editor' import Inputs from './Inputs' -import 'brace/mode/elixir' -import 'default-passive-events' +import i18n from '@/lang' +import _ from 'lodash' export default { name: 'Setting', components: { - editor: AceEditor, Inputs }, props: { @@ -84,12 +95,32 @@ export default { } }, 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 }) { return type === 'keyword' || type === 'map' || type.includes('keyword') || 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) { this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }}) } diff --git a/src/views/settings/components/Upload.vue b/src/views/settings/components/Upload.vue index 546456df13fcfd3f127405eb7d11ce74c38aade1..b31990b23df647a04c8a36eb25b7abc77053ecdd 100644 --- a/src/views/settings/components/Upload.vue +++ b/src/views/settings/components/Upload.vue @@ -11,10 +11,6 @@ <setting :setting-group="uploadersS3" :data="uploadersS3Data"/> </el-form> <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"> <setting :setting-group="uploadFilterMogrify" :data="uploadFilterMogrifyData"/> </el-form> @@ -32,6 +28,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'Upload', @@ -44,7 +41,7 @@ export default { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -53,37 +50,31 @@ export default { return this.settings.description.find(setting => setting.key === 'Pleroma.Upload') }, uploadData() { - return this.settings.settings[':pleroma']['Pleroma.Upload'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Upload']) || {} }, uploadersLocal() { return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.Local') }, uploadersLocalData() { - return this.settings.settings[':pleroma']['Pleroma.Uploaders.Local'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Uploaders.Local']) || {} }, uploadersS3() { return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.S3') }, uploadersS3Data() { - return 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'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Uploaders.S3']) || {} }, uploadFilterMogrify() { return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.Mogrify') }, uploadFilterMogrifyData() { - return this.settings.settings[':pleroma']['Pleroma.Upload.Filter.Mogrify'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Upload.Filter.Mogrify']) || {} }, uploadAnonymizeFilename() { return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.AnonymizeFilename') }, uploadAnonymizeFilenameData() { - return this.settings.settings[':pleroma']['Pleroma.Upload.Filter.AnonymizeFilename'] + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Upload.Filter.AnonymizeFilename']) || {} } }, methods: { diff --git a/src/views/settings/components/WebPush.vue b/src/views/settings/components/WebPush.vue index e5f6023927597c99adb470b6a4014dbc1cd57f24..dc03151a5348d0db05d3249fa697fe5c4fe3f297 100644 --- a/src/views/settings/components/WebPush.vue +++ b/src/views/settings/components/WebPush.vue @@ -11,6 +11,7 @@ import { mapGetters } from 'vuex' import i18n from '@/lang' import Setting from './Setting' +import _ from 'lodash' export default { name: 'WebPush', @@ -23,7 +24,7 @@ export default { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '240px' + return this.isMobile ? '100px' : '280px' }, loading() { return this.settings.loading @@ -32,7 +33,7 @@ export default { return this.settings.description.find(setting => setting.key === ':vapid_details') }, vapidDetailsData() { - return this.settings.settings[':web_push_encryption'][':vapid_details'] + return _.get(this.settings.settings, [':web_push_encryption', ':vapid_details']) || {} } }, methods: { diff --git a/src/views/settings/components/index.js b/src/views/settings/components/index.js index 040d67120287bb9ef5721d169498a4a163a1ddf0..165f43f18fc1b95b1baff1c3ff4a5a4b12bc1a4b 100644 --- a/src/views/settings/components/index.js +++ b/src/views/settings/components/index.js @@ -2,8 +2,6 @@ export { default as ActivityPub } from './ActivityPub' export { default as Authentication } from './Authentication' export { default as AutoLinker } from './AutoLinker' 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 Frontend } from './Frontend' export { default as Gopher } from './Gopher' diff --git a/src/views/settings/components/inputComponents/EditableKeywordInput.vue b/src/views/settings/components/inputComponents/EditableKeywordInput.vue index 9f0d77fc2ac061d7608fc64430503022026b90e7..9489c685322c149f1745b2e86cab3bef632fbb01 100644 --- a/src/views/settings/components/inputComponents/EditableKeywordInput.vue +++ b/src/views/settings/components/inputComponents/EditableKeywordInput.vue @@ -32,7 +32,7 @@ export default { name: 'EditableKeywordInput', props: { data: { - type: [Object, Array], + type: Array, default: function() { return {} } @@ -95,7 +95,7 @@ export default { updateSetting(value, group, key, input, type) { const updatedSettings = type !== 'map' ? 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) => { return { ...acc, [Object.keys(element)[0]]: Object.values(element)[0].value } diff --git a/src/views/settings/components/inputComponents/IconsInput.vue b/src/views/settings/components/inputComponents/IconsInput.vue index 6f52042563a47b8bc17e4844dcea52605c20e096..ac994fe1847a74819eacf7c19769dbda5ff1fa0e 100644 --- a/src/views/settings/components/inputComponents/IconsInput.vue +++ b/src/views/settings/components/inputComponents/IconsInput.vue @@ -28,7 +28,7 @@ export default { name: 'EditableKeywordInput', props: { data: { - type: [Object, Array], + type: Array, default: function() { return {} } @@ -45,14 +45,11 @@ export default { return {} } } - }, - computed: { - }, methods: { addIconToIcons() { 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) { const updatedValue = this.data.map((icon, i) => { @@ -61,11 +58,11 @@ export default { } 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) { 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() { return `f${(~~(Math.random() * 1e8)).toString(16)}` diff --git a/src/views/settings/components/inputComponents/MascotsInput.vue b/src/views/settings/components/inputComponents/MascotsInput.vue index 671a6a37d82e0a263099f497032138aa76139a39..96ec5ca7640a15b9507913f6ccf0febf555ec475 100644 --- a/src/views/settings/components/inputComponents/MascotsInput.vue +++ b/src/views/settings/components/inputComponents/MascotsInput.vue @@ -23,7 +23,7 @@ export default { name: 'MascotsInput', props: { data: { - type: [Object, Array], + type: Array, default: function() { return {} } @@ -88,7 +88,7 @@ export default { updateSetting(value, group, key, input, type) { const mascotsWithoutIDs = value.reduce((acc, mascot) => { 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('UpdateState', { group, key, input, value }) diff --git a/src/views/settings/components/inputComponents/RateLimitInput.vue b/src/views/settings/components/inputComponents/RateLimitInput.vue index 3946b25dd73c20c9e032d07e5f908291120102a5..358dcdf6c1379f48926ff735965c76fd8791d80c 100644 --- a/src/views/settings/components/inputComponents/RateLimitInput.vue +++ b/src/views/settings/components/inputComponents/RateLimitInput.vue @@ -5,7 +5,8 @@ :value="rateLimitAllUsers[0]" placeholder="scale" class="scale-input" - @input="parseRateLimiter($event, setting.key, 'scale', 'oneLimit', rateLimitAllUsers)"/> : + @input="parseRateLimiter($event, setting.key, 'scale', 'oneLimit', rateLimitAllUsers)"/> + <span>:</span> <el-input :value="rateLimitAllUsers[1]" placeholder="limit" @@ -17,24 +18,26 @@ </div> </div> <div v-if="rateLimitAuthUsers"> - <el-form-item label="Unauthenticated users:"> + <el-form-item label="Unauthenticated users:" label-width="180px" class="rate-limit"> <el-input :value="rateLimitUnauthUsers[0]" placeholder="scale" 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 :value="rateLimitUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, setting.key, 'limit', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/> </el-form-item> - <el-form-item label="Authenticated users:"> + <el-form-item label="Authenticated users:" label-width="180px" class="rate-limit"> <el-input :value="rateLimitAuthUsers[0]" placeholder="scale" 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 :value="rateLimitAuthUsers[1]" placeholder="limit" diff --git a/src/views/settings/components/inputComponents/SslOptionsInput.vue b/src/views/settings/components/inputComponents/SslOptionsInput.vue deleted file mode 100644 index 970ad9bac256a9b80bde62cc9f62b04655f4550a..0000000000000000000000000000000000000000 --- a/src/views/settings/components/inputComponents/SslOptionsInput.vue +++ /dev/null @@ -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> diff --git a/src/views/settings/components/inputComponents/index.js b/src/views/settings/components/inputComponents/index.js index faf7c1f86f28324c1f95f99f931cdcc0fd32fd18..389b304efeee186f2fc438ddd41aebb633917bcc 100644 --- a/src/views/settings/components/inputComponents/index.js +++ b/src/views/settings/components/inputComponents/index.js @@ -6,4 +6,3 @@ export { default as MultipleSelect } from './MultipleSelect' export { default as ProxyUrlInput } from './ProxyUrlInput' export { default as PruneInput } from './PruneInput' export { default as RateLimitInput } from './RateLimitInput' -export { default as SslOptionsInput } from './SslOptionsInput' diff --git a/src/views/settings/index.vue b/src/views/settings/index.vue index e35de85f22cd163723f2242172367ed94c5f013f..a2edc6869ecb15e863234d09bf29795b008b7479 100644 --- a/src/views/settings/index.vue +++ b/src/views/settings/index.vue @@ -17,15 +17,9 @@ <el-tab-pane :label="$t('settings.captcha')" lazy> <captcha/> </el-tab-pane> - <el-tab-pane :label="$t('settings.database')" lazy> - <database/> - </el-tab-pane> <el-tab-pane :label="$t('settings.emojiPacks')" lazy> <emoji-packs/> </el-tab-pane> - <el-tab-pane :label="$t('settings.endpoint')" lazy> - <endpoint/> - </el-tab-pane> <el-tab-pane :label="$t('settings.frontend')" lazy> <frontend/> </el-tab-pane> @@ -76,11 +70,11 @@ </template> <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' 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() { return { activeTab: 'instance' diff --git a/src/views/settings/styles/main.scss b/src/views/settings/styles/main.scss index 45491ab84d6364c31fef725d7a5793851c58467c..4ed52dfecad8baad3281a172e7f74d22162eb4e6 100644 --- a/src/views/settings/styles/main.scss +++ b/src/views/settings/styles/main.scss @@ -23,6 +23,9 @@ .el-form-item { margin-right: 30px; } + .el-form-item .rate-limit { + margin-right: 0; + } .center-label label { text-align: center; } @@ -95,8 +98,8 @@ margin-left: 10px; } .limit-input { - width: 48%; - margin: 0 0 5px 8px + width: 47%; + margin: 0 0 5px 1% } .line { width: 100%; @@ -130,6 +133,15 @@ margin-left: 8px; margin-right: 10px } + .replacement-input { + width: 80%; + margin-left: 8px; + margin-right: 10px + } + .scale-input { + width: 47%; + margin: 0 1% 5px 0 + } .setting-input { display: flex; margin-bottom: 10px; @@ -137,22 +149,13 @@ .single-input { margin-right: 10px } - .scale-input { - width: 48%; - margin: 0 8px 5px 0 - } - .replacement-input { - width: 80%; - margin-left: 8px; - margin-right: 10px + .ssl-tls-opts { + margin: 36px 0 0 0; } .text { line-height: 20px; margin-right: 15px } - .ssl-tls-opts { - margin: 36px 0 0 0; - } .upload-container { display: flex; align-items: baseline; diff --git a/test/modules/normalizers/wrapUpdatedSettings.test.js b/test/modules/normalizers/wrapUpdatedSettings.test.js index 9f815760e984e387c7f636107b48569495ad7c38..6b360f85c8b90f495c2e943b4ca10346a354bc64 100644 --- a/test/modules/normalizers/wrapUpdatedSettings.test.js +++ b/test/modules/normalizers/wrapUpdatedSettings.test.js @@ -31,7 +31,8 @@ describe('Wrap settings', () => { const result = wrapUpdatedSettings(':mime', settings, {}) const expectedResult = [{ group: ':mime', - key: ':types', value: { + key: ':types', + value: { 'application/ld+json': ['activity+json'], 'application/xml': ['xml'], 'application/xrd+xml': ['xrd+xml'] @@ -39,4 +40,335 @@ describe('Wrap settings', () => { }] 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() + }) }) diff --git a/yarn.lock b/yarn.lock index 6f1bcc6ec6e2b396d6da6653643cc9dc6f451d14..d75e92b1ca3fb3cb6e4aa79aae958494c000164e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1868,11 +1868,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" 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: version "2.3.2" 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" 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: version "2.6.8" resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.8.tgz#f21cbc536bfc14f7d1d792a137bb12f69e60ea91"