From 2d86e26aea85821678b0c798c9d0f0006a6d746f Mon Sep 17 00:00:00 2001 From: Angelina Filippova Date: Tue, 21 Jan 2020 18:16:56 +0000 Subject: [PATCH] Ability to remove settings from db --- package.json | 1 - src/api/settings.js | 12 - src/lang/en.js | 6 +- src/store/modules/normalizers.js | 67 ++-- src/store/modules/settings.js | 63 ++-- src/views/settings/components/ActivityPub.vue | 7 +- .../settings/components/Authentication.vue | 11 +- src/views/settings/components/AutoLinker.vue | 5 +- src/views/settings/components/Captcha.vue | 7 +- src/views/settings/components/Database.vue | 59 ---- src/views/settings/components/Endpoint.vue | 81 ----- src/views/settings/components/Esshd.vue | 5 +- src/views/settings/components/Frontend.vue | 18 +- src/views/settings/components/Gopher.vue | 5 +- src/views/settings/components/Http.vue | 13 +- src/views/settings/components/Inputs.vue | 72 ++-- src/views/settings/components/Instance.vue | 19 +- src/views/settings/components/JobQueue.vue | 9 +- src/views/settings/components/Logger.vue | 11 +- src/views/settings/components/MRF.vue | 19 +- src/views/settings/components/Mailer.vue | 9 +- src/views/settings/components/MediaProxy.vue | 5 +- src/views/settings/components/Metadata.vue | 7 +- src/views/settings/components/Other.vue | 16 +- .../settings/components/RateLimiters.vue | 5 +- src/views/settings/components/Setting.vue | 51 ++- src/views/settings/components/Upload.vue | 23 +- src/views/settings/components/WebPush.vue | 5 +- src/views/settings/components/index.js | 2 - .../inputComponents/EditableKeywordInput.vue | 4 +- .../components/inputComponents/IconsInput.vue | 11 +- .../inputComponents/MascotsInput.vue | 4 +- .../inputComponents/RateLimitInput.vue | 13 +- .../inputComponents/SslOptionsInput.vue | 93 ----- .../components/inputComponents/index.js | 1 - src/views/settings/index.vue | 10 +- src/views/settings/styles/main.scss | 29 +- .../normalizers/wrapUpdatedSettings.test.js | 334 +++++++++++++++++- yarn.lock | 12 - 39 files changed, 635 insertions(+), 489 deletions(-) delete mode 100644 src/views/settings/components/Database.vue delete mode 100644 src/views/settings/components/Endpoint.vue delete mode 100644 src/views/settings/components/inputComponents/SslOptionsInput.vue diff --git a/package.json b/package.json index 73c2fc16..8d5cd449 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 c1bbb182..9d1c2890 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 3dcd9bdd..1a8a8c90 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 17b7c5f0..6d0efe29 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 7efc29b5..c102c6eb 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 - }, 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.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 4c99296d..2eb9a654 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 c37fe537..f88b5b40 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 29c1f69b..e5fb7529 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 0a61f0bf..5a942793 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 773472f1..00000000 --- a/src/views/settings/components/Database.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - - - diff --git a/src/views/settings/components/Endpoint.vue b/src/views/settings/components/Endpoint.vue deleted file mode 100644 index 6becdbb2..00000000 --- a/src/views/settings/components/Endpoint.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - - - diff --git a/src/views/settings/components/Esshd.vue b/src/views/settings/components/Esshd.vue index c66ec01b..6ca3ae15 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 fb969532..72c236a8 100644 --- a/src/views/settings/components/Frontend.vue +++ b/src/views/settings/components/Frontend.vue @@ -1,11 +1,6 @@