diff --git a/src/api/settings.js b/src/api/settings.js index a0da39cd..c1bbb182 100644 --- a/src/api/settings.js +++ b/src/api/settings.js @@ -2,6 +2,15 @@ import request from '@/utils/request' import { getToken } from '@/utils/auth' import { baseName } from './utils' +export async function fetchDescription(authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/config/descriptions`, + method: 'get', + headers: authHeaders(token) + }) +} + export async function fetchSettings(authHost, token) { return await request({ baseURL: baseName(authHost), @@ -21,6 +30,16 @@ export async function updateSettings(configs, authHost, token) { }) } +export async function removeSettings(configs, authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/config`, + method: 'post', + headers: authHeaders(token), + data: { configs } + }) +} + export async function uploadMedia(file, authHost, token) { const formData = new FormData() formData.append('file', file) diff --git a/src/store/getters.js b/src/store/getters.js index 159fb48c..f58f0c6b 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -17,86 +17,6 @@ const getters = { errorLogs: state => state.errorLog.logs, users: state => state.users.fetchedUsers, authHost: state => state.user.authHost, - - activityPub: state => state.settings.settings['activitypub'], - adminToken: state => state.settings.settings['admin_token'], - assets: state => state.settings.settings['assets'], - auth: state => state.settings.settings['auth'], - autoLinker: state => state.settings.settings['auto_linker'], - captcha: state => state.settings.settings['Pleroma.Captcha'], - chat: state => state.settings.settings['chat'], - consoleLogger: state => state.settings.settings['console'], - corsPlugCredentials: state => state.settings.settings['credentials'], - corsPlugExpose: state => state.settings.settings['expose'], - corsPlugHeaders: state => state.settings.settings['headers'], - corsPlugMaxAge: state => state.settings.settings['max_age'], - corsPlugMethods: state => state.settings.settings['methods'], - database: state => state.settings.settings['database'], - ectoRepos: state => state.settings.settings['ecto_repos'], - emailNotifications: state => state.settings.settings['email_notifications'], - emoji: state => state.settings.settings['emoji'], - enabled: state => state.settings.settings['enabled'], - endpoint: state => state.settings.settings['Pleroma.Web.Endpoint'], - exsyslogger: state => state.settings.settings['ex_syslogger'], - facebook: state => state.settings.settings['Ueberauth.Strategy.Facebook.OAuth'], - fetchInitialPosts: state => state.settings.settings['fetch_initial_posts'], - formatEncoders: state => state.settings.settings['format_encoders'], - frontend: state => state.settings.settings['frontend_configurations'], - google: state => state.settings.settings['Ueberauth.Strategy.Google.OAuth'], - gopher: state => state.settings.settings['gopher'], - hackneyPools: state => state.settings.settings['hackney_pools'], - handler: state => state.settings.settings['handler'], - http: state => state.settings.settings['http'], - httpSecurity: state => state.settings.settings['http_security'], - instance: state => state.settings.settings['instance'], - instances: state => state.peers.fetchedPeers, - kocaptcha: state => state.settings.settings['Pleroma.Captcha.Kocaptcha'], - level: state => state.settings.settings['level'], - ldap: state => state.settings.settings['ldap'], - loggerBackends: state => state.settings.settings['backends'], - mailer: state => state.settings.settings['Pleroma.Emails.Mailer'], - markup: state => state.settings.settings['markup'], - mediaProxy: state => state.settings.settings['media_proxy'], - meta: state => state.settings.settings['meta'], - metadata: state => state.settings.settings['Pleroma.Web.Metadata'], - microsoft: state => state.settings.settings['Ueberauth.Strategy.Microsoft.OAuth'], - mimeTypesConfig: state => state.settings.settings['types'], - mrfHellthread: state => state.settings.settings['mrf_hellthread'], - mrfKeyword: state => state.settings.settings['mrf_keyword'], - mrfMention: state => state.settings.settings['mrf_mention'], - mrfNormalizeMarkup: state => state.settings.settings['mrf_normalize_markup'], - mrfRejectnonpublic: state => state.settings.settings['mrf_rejectnonpublic'], - mrfSimple: state => state.settings.settings['mrf_simple'], - mrfSubchain: state => state.settings.settings['mrf_subchain'], - mrfUserAllowlist: state => state.settings.settings['mrf_user_allowlist'], - mrfVocabulary: state => state.settings.settings['mrf_vocabulary'], - oauth2: state => state.settings.settings['oauth2'], - passwordAuthenticator: state => state.settings.settings['password_authenticator'], - pleromaAuthenticator: state => state.settings.settings['Pleroma.Web.Auth.Authenticator'], - pleromaRepo: state => state.settings.settings['Pleroma.Repo'], - pleromaUser: state => state.settings.settings['Pleroma.User'], - port: state => state.settings.settings['port'], - privDir: state => state.settings.settings['priv_dir'], - queues: state => state.settings.settings['queues'], - rateLimiters: state => state.settings.settings['rate_limit'], - retryQueue: state => state.settings.settings['Pleroma.Web.Federator.RetryQueue'], - richMedia: state => state.settings.settings['rich_media'], - suggestions: state => state.settings.settings['suggestions'], - scheduledActivity: state => state.settings.settings['Pleroma.ScheduledActivity'], - statuses: state => state.status.fetchedStatuses, - teslaAdapter: state => state.settings.settings['adapter'], - twitter: state => state.settings.settings['Ueberauth.Strategy.Twitter.OAuth'], - ueberauth: state => state.settings.settings['Ueberauth'], - uploadAnonymizeFilename: state => state.settings.settings['Pleroma.Upload.Filter.AnonymizeFilename'], - upload: state => state.settings.settings['Pleroma.Upload'], - uploadFilterMogrify: state => state.settings.settings['Pleroma.Upload.Filter.Mogrify'], - uploadersLocal: state => state.settings.settings['Pleroma.Uploaders.Local'], - uploadMDII: state => state.settings.settings['Pleroma.Uploaders.MDII'], - uploadS3: state => state.settings.settings['Pleroma.Uploaders.S3'], - uriSchemes: state => state.settings.settings['uri_schemes'], - user: state => state.settings.settings['user'], - userEmail: state => state.settings.settings['Pleroma.Emails.UserEmail'], - vapidDetails: state => state.settings.settings['vapid_details'], - webhookUrl: state => state.settings.settings['webhook_url'] + settings: state => state.settings } export default getters diff --git a/src/store/modules/normalizers.js b/src/store/modules/normalizers.js index c2a9d3e8..17b7c5f0 100644 --- a/src/store/modules/normalizers.js +++ b/src/store/modules/normalizers.js @@ -1,248 +1,234 @@ -const nonAtomsTuples = ['replace', ':replace'] -const nonAtomsObjects = ['match_actor', ':match_actor'] -const objects = ['digest', 'pleroma_fe', 'masto_fe', 'poll_limits', 'styling'] -const objectParents = ['mascots'] -const groups = { - 'cors_plug': [ - 'credentials', - 'expose', - 'headers', - 'max_age', - 'methods' - ], - 'esshd': [ - 'enabled', - 'handler', - 'password_authenticator', - 'port', - 'priv_dir' - ], - 'logger': ['backends', 'console', 'ex_syslogger'], - 'mime': ['types'], - 'phoenix': ['format_encoders'], - 'pleroma': [ - 'Pleroma.Captcha', - 'Pleroma.Captcha.Kocaptcha', - 'Pleroma.Emails.Mailer', - 'Pleroma.Emails.UserEmail', - 'Pleroma.Repo', - 'Pleroma.ScheduledActivity', - 'Pleroma.Upload', - 'Pleroma.Upload.Filter.AnonymizeFilename', - 'Pleroma.Upload.Filter.Mogrify', - 'Pleroma.Uploaders.Local', - 'Pleroma.Uploaders.MDII', - 'Pleroma.Uploaders.S3', - 'Pleroma.User', - 'Pleroma.Web.Auth.Authenticator', - 'Pleroma.Web.Endpoint', - 'Pleroma.Web.Federator.RetryQueue', - 'Pleroma.Web.Metadata', - 'activitypub', - 'admin_token', - 'assets', - 'auth', - 'auto_linker', - 'chat', - 'database', - 'ecto_repos', - 'email_notifications', - 'emoji', - 'env', - 'fetch_initial_posts', - 'frontend_configurations', - 'gopher', - 'hackney_pools', - 'http', - 'http_security', - 'instance', - 'ldap', - 'markup', - 'media_proxy', - 'mrf_hellthread', - 'mrf_keyword', - 'mrf_mention', - 'mrf_normalize_markup', - 'mrf_rejectnonpublic', - 'mrf_simple', - 'mrf_subchain', - 'mrf_user_allowlist', - 'mrf_vocabulary', - 'oauth2', - 'rate_limit', - 'rich_media', - 'suggestions', - 'uri_schemes', - 'user' - ], - 'pleroma_job_queue': ['queues'], - 'quack': ['level', 'meta', 'webhook_url'], - 'tesla': ['adapter'], - 'ueberauth': [ - 'Ueberauth', - 'Ueberauth.Strategy.Facebook.OAuth', - 'Ueberauth.Strategy.Google.OAuth', - 'Ueberauth.Strategy.Microsoft.OAuth', - 'Ueberauth.Strategy.Twitter.OAuth' - ], - 'web_push_encryption': ['vapid_details'] +export const checkPartialUpdate = (settings, updatedSettings, description) => { + return Object.keys(updatedSettings).reduce((acc, group) => { + acc[group] = Object.keys(updatedSettings[group]).reduce((acc, key) => { + if (!partialUpdate(group, key)) { + const updated = Object.keys(settings[group][key]).reduce((acc, settingName) => { + const setting = description + .find(element => element.group === group && element.key === key).children + .find(child => child.key === settingName) + const type = setting ? setting.type : '' + acc[settingName] = [type, settings[group][key][settingName]] + return acc + }, {}) + acc[key] = updated + return acc + } + acc[key] = updatedSettings[group][key] + return acc + }, {}) + return acc + }, {}) } -export const filterIgnored = (settings, ignored) => { - if (settings.enabled.value === true) { - return settings +const getCurrentValue = (object, keys) => { + if (keys.length === 0) { + return object } - - return ignored.reduce((acc, name) => { - const { [name]: ignored, ...newAcc } = acc - - return newAcc - }, settings) + const [currentKey, ...restKeys] = keys + return getCurrentValue(object[currentKey], restKeys) } +const getValueWithoutKey = (key, [type, value]) => { + if (type === 'atom' && value.length > 1) { + return `:${value}` + } else if (key === ':backends') { + const index = value.findIndex(el => el === ':ex_syslogger') + const updatedArray = value.slice() + if (index !== -1) { + updatedArray[index] = { 'tuple': ['ExSyslogger', ':ex_syslogger'] } + } + return updatedArray + } else if (key === ':types') { + return Object.keys(value).reduce((acc, key) => { return { ...acc, [key]: value[key][1] } }, {}) + } + return value +} + +export const parseNonTuples = (key, value) => { + if (key === ':backends') { + const index = value.findIndex(el => typeof el === 'object' && el.tuple.includes(':ex_syslogger')) + const updated = value.map((el, i) => i === index ? ':ex_syslogger' : el) + return updated + } + if (key === ':args') { + const index = value.findIndex(el => typeof el === 'object' && el.tuple.includes('implode')) + const updated = value.map((el, i) => i === index ? 'implode' : el) + return updated + } + return value +} // REFACTOR export const parseTuples = (tuples, key) => { return tuples.reduce((accum, item) => { - if (key === 'rate_limit') { - accum[item.tuple[0].substr(1)] = item.tuple[1] + if (key === ':rate_limit') { + accum[item.tuple[0]] = Array.isArray(item.tuple[1]) + ? item.tuple[1].map(el => el.tuple) + : item.tuple[1].tuple + } else if (item.tuple[0] === ':mascots') { + accum[item.tuple[0]] = item.tuple[1].reduce((acc, mascot) => { + return [...acc, { [mascot.tuple[0]]: { ...mascot.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] + }, []) + } else if (item.tuple[0] === ':groups' || item.tuple[0] === ':replace' || item.tuple[0] === ':retries') { + accum[item.tuple[0]] = item.tuple[1].reduce((acc, group) => { + return [...acc, { [group.tuple[0]]: { value: group.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] + }, []) + } else if (item.tuple[0] === ':match_actor') { + accum[item.tuple[0]] = Object.keys(item.tuple[1]).reduce((acc, regex) => { + return [...acc, { [regex]: { value: item.tuple[1][regex], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}] + }, []) + } else if (item.tuple[0] === ':icons') { + accum[item.tuple[0]] = item.tuple[1].map(icon => { + return Object.keys(icon).map(name => { + return { key: name, value: icon[name], id: `f${(~~(Math.random() * 1e8)).toString(16)}` } + }) + }, []) + } else if (item.tuple[0] === ':prune') { + accum[item.tuple[0]] = item.tuple[1] === ':disabled' ? [item.tuple[1]] : item.tuple[1].tuple + } else if (item.tuple[0] === ':proxy_url') { + accum[item.tuple[0]] = parseProxyUrl(item.tuple[1]) + } else if (item.tuple[0] === ':args') { + accum[item.tuple[0]] = parseNonTuples(item.tuple[0], item.tuple[1]) } else if (Array.isArray(item.tuple[1]) && - (typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) { - nonAtomsTuples.includes(item.tuple[0]) - ? accum[item.tuple[0].substr(1)] = parseNonAtomTuples(item.tuple[1]) - : accum[item.tuple[0].substr(1)] = parseTuples(item.tuple[1]) + (typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) { + accum[item.tuple[0]] = parseTuples(item.tuple[1], item.tuple[0]) } else if (Array.isArray(item.tuple[1])) { - accum[item.tuple[0].substr(1)] = item.tuple[1] - } else if (item.tuple[1] && typeof item.tuple[1] === 'object' && 'tuple' in item.tuple[1]) { - accum[item.tuple[0].substr(1)] = item.tuple[1]['tuple'].join('.') + accum[item.tuple[0]] = item.tuple[1] + } else if (item.tuple[0] === ':ip') { + accum[item.tuple[0]] = item.tuple[1].tuple.join('.') } else if (item.tuple[1] && typeof item.tuple[1] === 'object') { - nonAtomsObjects.includes(item.tuple[0]) - ? accum[item.tuple[0].substr(1)] = parseNonAtomObject(item.tuple[1]) - : accum[item.tuple[0].substr(1)] = parseObject(item.tuple[1]) + accum[item.tuple[0]] = parseObject(item.tuple[1]) } else { - key === 'mrf_user_allowlist' - ? accum[item.tuple[0]] = item.tuple[1] - : accum[item.tuple[0].substr(1)] = item.tuple[1] + accum[item.tuple[0]] = item.tuple[1] } return accum }, {}) } -const parseNonAtomTuples = (tuples) => { - return tuples.reduce((acc, item) => { - acc[item.tuple[0]] = item.tuple[1] - return acc - }, {}) -} - -const parseNonAtomObject = (object) => { +const parseObject = object => { return Object.keys(object).reduce((acc, item) => { acc[item] = object[item] return acc }, {}) } -const parseObject = (object) => { - return Object.keys(object).reduce((acc, item) => { - acc[item.substr(1)] = object[item] - return acc - }, {}) +const parseProxyUrl = value => { + if (value && !Array.isArray(value) && + typeof value === 'object' && + value.tuple.length === 3 && + value.tuple[0] === ':socks5') { + const [, host, port] = value.tuple + return { socks5: true, host, port } + } else if (typeof value === 'string') { + const [host, port] = value.split(':') + return { socks5: false, host, port } + } + return { socks5: false, host: null, port: null } +} + +const partialUpdate = (group, key) => { + if (group === ':auto_linker' && key === ':opts') { + return false + } + return true +} + +export const processNested = (valueForState, valueForUpdatedSettings, group, parentKey, parents, settings, updatedSettings) => { + 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)), + ...{ [key]: valueForState }} + : { [key]: valueForState } + let updatedValueForUpdatedSettings = valueExists(updatedSettings, path) + ? { ...getCurrentValue(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) + .reduce((acc, el) => { + return { ...acc, [el]: [type, settings[group][parents[0].key].value[el]] } + }, {}), + ...updatedValueForUpdatedSettings + } + } + + return otherParents.length === 1 + ? { valueForState: updatedValueForState, valueForUpdatedSettings: updatedValueForUpdatedSettings, setting: otherParents[0] } + : processNested(updatedValueForState, updatedValueForUpdatedSettings, group, parentKey, otherParents, settings, updatedSettings) +} + +const valueExists = (value, path) => { + if (path.length === 0) { + return true + } + const [element, ...rest] = path + return value[element] ? valueExists(value[element], rest) : false } export const valueHasTuples = (key, value) => { - const valueIsArrayOfNonObjects = Array.isArray(value) && value.length > 0 && typeof value[0] !== 'object' - return key === 'meta' || - key === 'types' || + const valueIsArrayOfNonObjects = Array.isArray(value) && value.length > 0 && value.every(el => typeof el !== 'object') + return key === ':meta' || + key === ':types' || + key === ':backends' || + key === ':compiled_template_engines' || + key === ':compiled_format_encoders' || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || + value === null || valueIsArrayOfNonObjects } -// REFACTOR -export const wrapConfig = settings => { - return Object.keys(settings).map(config => { - const group = getGroup(config) - const key = config.startsWith('Pleroma') || config.startsWith('Ueberauth') ? config : `:${config}` - const value = (settings[config]['value'] !== undefined) - ? settings[config]['value'] - : Object.keys(settings[config]).reduce((acc, settingName) => { - const data = settings[config][settingName] - if (data === null || data === '') { - return acc - } else if (key === ':rate_limit') { - return [...acc, { 'tuple': [`:${settingName}`, data] }] - } else if (settingName === 'ip') { - const ip = data.split('.').map(s => parseInt(s, 10)) - return [...acc, { 'tuple': [`:${settingName}`, { 'tuple': ip }] }] - } else if (Array.isArray(data) || typeof data !== 'object') { - return key === ':mrf_user_allowlist' - ? [...acc, { 'tuple': [`${settingName}`, data] }] - : [...acc, { 'tuple': [`:${settingName}`, data] }] - } else if (nonAtomsObjects.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapNonAtomsObjects(data)] }] - } else if (objectParents.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapNestedObjects(data)] }] - } else if (objects.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapObjects(data)] }] - } else if (nonAtomsTuples.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapNonAtomsTuples(data)] }] - } else { - return [...acc, { 'tuple': [`:${settingName}`, wrapNestedTuples(data)] }] - } - }, []) - return { group, key, value } +export const wrapUpdatedSettings = (group, settings, currentState) => { + return Object.keys(settings).map((key) => { + return settings[key]._value + ? { group, key, value: getValueWithoutKey(key, settings[key]._value) } + : { group, key, value: wrapValues(settings[key], currentState[group][key]) } }) } -const wrapNestedTuples = setting => { - return Object.keys(setting).reduce((acc, settingName) => { - const data = setting[settingName] - if (data === null || data === '') { - return acc - } else if (settingName === 'ip') { - const ip = data.split('.').map(s => parseInt(s, 10)) - return [...acc, { 'tuple': [`:${settingName}`, { 'tuple': ip }] }] - } else if (Array.isArray(data) || typeof data !== 'object') { - return [...acc, { 'tuple': [`:${settingName}`, data] }] - } else if (nonAtomsObjects.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapNonAtomsObjects(data)] }] - } else if (objectParents.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapNestedObjects(data)] }] - } else if (objects.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapObjects(data)] }] - } else if (nonAtomsTuples.includes(settingName)) { - return [...acc, { 'tuple': [`:${settingName}`, wrapNonAtomsTuples(data)] }] +const wrapValues = (settings, currentState) => { + return Object.keys(settings).map(setting => { + const [type, value] = settings[setting] + if (type === 'keyword' || type.includes('keyword') || setting === ':replace') { + return { 'tuple': [setting, wrapValues(value, currentState)] } + } else if (type === 'atom' && value.length > 0) { + return { 'tuple': [setting, `:${value}`] } + } else if (type.includes('tuple') && (type.includes('string') || type.includes('atom'))) { + return typeof value === 'string' + ? { 'tuple': [setting, value] } + : { 'tuple': [setting, { 'tuple': value }] } + } else if (type.includes('tuple') && type.includes('list')) { + return { 'tuple': [setting, value] } + } else if (type === 'map') { + const mapValue = Object.keys(value).reduce((acc, key) => { + acc[key] = setting === ':match_actor' ? value[key] : value[key][1] + return acc + }, {}) + const mapCurrentState = setting === ':match_actor' + ? currentState[setting].reduce((acc, element) => { + return { ...acc, ...{ [Object.keys(element)[0]]: Object.values(element)[0].value }} + }, {}) + : currentState[setting] + return { 'tuple': [setting, { ...mapCurrentState, ...mapValue }] } + } 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() + if (index !== -1) { + updatedArray[index] = { 'tuple': ['implode', '1'] } + } + return { 'tuple': [setting, updatedArray] } } else { - return [...acc, { 'tuple': [`:${settingName}`, wrapNestedTuples(data)] }] + return { 'tuple': [setting, value] } } - }, []) + }) } - -const wrapNonAtomsTuples = setting => { - return Object.keys(setting).reduce((acc, settingName) => { - return [...acc, { 'tuple': [`${settingName}`, setting[settingName]] }] - }, []) -} - -const wrapNestedObjects = setting => { - return Object.keys(setting).reduce((acc, settingName) => { - return [...acc, { 'tuple': [`:${settingName}`, wrapObjects(setting[settingName])] }] - }, []) -} - -const wrapNonAtomsObjects = setting => { - return Object.keys(setting).reduce((acc, settingName) => { - return { ...acc, [`${settingName}`]: setting[settingName] } - }, {}) -} - -const wrapObjects = setting => { - return Object.keys(setting).reduce((acc, settingName) => { - return { ...acc, [`:${settingName}`]: setting[settingName] } - }, {}) -} - -const getGroup = key => { - return Object.keys(groups).find(i => groups[i].includes(key)) -} - diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js index 9c3a2c79..7efc29b5 100644 --- a/src/store/modules/settings.js +++ b/src/store/modules/settings.js @@ -1,145 +1,100 @@ -import i18n from '@/lang' -import { fetchSettings, updateSettings, uploadMedia } from '@/api/settings' -import { filterIgnored, parseTuples, valueHasTuples, wrapConfig } from './normalizers' -import { Message } from 'element-ui' +import { fetchDescription, fetchSettings, removeSettings, updateSettings, uploadMedia } from '@/api/settings' +import { checkPartialUpdate, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers' const settings = { state: { + description: [], settings: { - 'activitypub': {}, - 'adapter': {}, - 'admin_token': {}, - 'assets': { mascots: {}}, - 'auth': {}, - 'auto_linker': { opts: {}}, - 'backends': {}, - 'chat': {}, - 'console': { colors: {}}, - 'credentials': {}, - 'database': {}, - 'ecto_repos': {}, - 'email_notifications': { digest: {}}, - 'emoji': { groups: {}}, - 'enabled': {}, - 'ex_syslogger': {}, - 'expose': {}, - 'fetch_initial_posts': {}, - 'format_encoders': {}, - 'frontend_configurations': { pleroma_fe: {}, masto_fe: {}}, - 'gopher': {}, - 'hackney_pools': { federation: {}, media: {}, upload: {}}, - 'handler': {}, - 'headers': {}, - 'http': { adapter: {}}, - 'http_security': {}, - 'instance': { poll_limits: {}}, - 'level': {}, - 'ldap': {}, - 'markup': {}, - 'max_age': {}, - 'media_proxy': { proxy_opts: {}}, - 'meta': {}, - 'methods': {}, - 'mrf_hellthread': {}, - 'mrf_keyword': { replace: {}}, - 'mrf_mention': {}, - 'mrf_normalize_markup': {}, - 'mrf_rejectnonpublic': {}, - 'mrf_simple': {}, - 'mrf_subchain': { match_actor: {}}, - 'mrf_user_allowlist': {}, - 'mrf_vocabulary': {}, - 'oauth2': {}, - 'password_authenticator': {}, - 'Pleroma.Captcha': {}, - 'Pleroma.Captcha.Kocaptcha': {}, - 'Pleroma.Emails.Mailer': {}, - 'Pleroma.Emails.UserEmail': { styling: {}}, - 'Pleroma.Repo': {}, - 'Pleroma.ScheduledActivity': {}, - 'Pleroma.Upload': { proxy_opts: {}}, - 'Pleroma.Upload.Filter.AnonymizeFilename': {}, - 'Pleroma.Upload.Filter.Mogrify': {}, - 'Pleroma.Uploaders.Local': {}, - 'Pleroma.Uploaders.MDII': {}, - 'Pleroma.Uploaders.S3': {}, - 'Pleroma.User': {}, - 'Pleroma.Web.Auth.Authenticator': {}, - 'Pleroma.Web.Endpoint': - { http: false, url: {}, render_errors: {}, pubsub: {}}, - 'Pleroma.Web.Federator.RetryQueue': {}, - 'Pleroma.Web.Metadata': {}, - 'port': {}, - 'priv_dir': {}, - 'queues': {}, - 'rate_limit': {}, - 'rich_media': {}, - 'suggestions': {}, - 'types': { value: {}}, - 'Ueberauth': {}, - 'Ueberauth.Strategy.Facebook.OAuth': {}, - 'Ueberauth.Strategy.Google.OAuth': {}, - 'Ueberauth.Strategy.Microsoft.OAuth': {}, - 'Ueberauth.Strategy.Twitter.OAuth': {}, - 'user': {}, - 'uri_schemes': {}, - 'vapid_details': {}, - 'webhook_url': {} + ':auto_linker': {}, + ':cors_plug': {}, + ':esshd': {}, + ':http_signatures': {}, + ':logger': {}, + ':mime': {}, + ':phoenix': {}, + ':pleroma': {}, + ':prometheus': {}, + ':quack': {}, + ':tesla': {}, + ':ueberauth': {}, + ':web_push_encryption': {} }, + updatedSettings: {}, ignoredIfNotEnabled: ['enabled', 'handler', 'password_authenticator', 'port', 'priv_dir'], loading: true }, mutations: { - REWRITE_CONFIG: (state, { tab, data }) => { - state.settings[tab] = data + CLEAR_UPDATED_SETTINGS: (state) => { + state.updatedSettings = {} + }, + SET_DESCRIPTION: (state, data) => { + state.description = data }, SET_LOADING: (state, status) => { state.loading = status }, SET_SETTINGS: (state, data) => { - const newSettings = data.reduce((acc, config) => { - const key = config.key[0] === ':' ? config.key.substr(1) : config.key - const value = valueHasTuples(key, config.value) ? { value: config.value } : parseTuples(config.value, key) - acc[key] = { ...acc[key], ...value } + const newSettings = data.reduce((acc, { group, key, value }) => { + const parsedValue = valueHasTuples(key, value) + ? { value: parseNonTuples(key, value) } + : parseTuples(value, key) + acc[group][key] = { ...acc[group][key], ...parsedValue } return acc }, state.settings) state.settings = newSettings }, - UPDATE_SETTINGS: (state, { tab, data }) => { - Object.keys(state.settings).map(configName => { - if (configName === tab) { - state.settings[configName] = { ...state.settings[configName], ...data } - } - }) + UPDATE_SETTINGS: (state, { group, key, input, value, type }) => { + const updatedSetting = !state.updatedSettings[group] || (key === 'Pleroma.Emails.Mailer' && input === ':adapter') + ? { [key]: { [input]: [type, value] }} + : { [key]: { ...state.updatedSettings[group][key], ...{ [input]: [type, value] }}} + state.updatedSettings[group] = { ...state.updatedSettings[group], ...updatedSetting } + }, + UPDATE_STATE: (state, { group, key, input, value }) => { + const updatedState = key === 'Pleroma.Emails.Mailer' && input === ':adapter' + ? { [key]: { [input]: value }} + : { [key]: { ...state.settings[group][key], ...{ [input]: value }}} + state.settings[group] = { ...state.settings[group], ...updatedState } } }, actions: { - async FetchSettings({ commit, dispatch, getters }) { + async FetchSettings({ commit, getters }) { commit('SET_LOADING', true) const response = await fetchSettings(getters.authHost, getters.token) + const description = await fetchDescription(getters.authHost, getters.token) + + commit('SET_DESCRIPTION', description.data) commit('SET_SETTINGS', response.data.configs) commit('SET_LOADING', false) }, - RewriteConfig({ commit }, { tab, data }) { - commit('REWRITE_CONFIG', { tab, data }) + async RemoveSetting({ getters }, configs) { + await removeSettings(configs, getters.authHost, getters.token) }, - async SubmitChanges({ getters, commit, state }, data) { - const filteredSettings = filterIgnored(state.settings, state.ignoredIfNotEnabled) - const configs = data || wrapConfig(filteredSettings) - try { - const response = await updateSettings(configs, getters.authHost, getters.token) - commit('SET_SETTINGS', response.data.configs) - } catch (_e) { - return + async SubmitChanges({ getters, commit, state }) { + const updatedData = checkPartialUpdate(state.settings, state.updatedSettings, state.description) + const configs = Object.keys(updatedData).reduce((acc, group) => { + return [...acc, ...wrapUpdatedSettings(group, updatedData[group], state.settings)] + }, []) + + const response = await updateSettings(configs, getters.authHost, getters.token) + commit('SET_SETTINGS', response.data.configs) + commit('CLEAR_UPDATED_SETTINGS') + }, + UpdateSettings({ commit }, { group, key, input, value, type }) { + key + ? commit('UPDATE_SETTINGS', { group, key, input, value, type }) + : commit('UPDATE_SETTINGS', { group, key: input, input: '_value', value, type }) + }, + UpdateState({ commit, dispatch, state }, { group, key, input, value }) { + if (key === 'Pleroma.Emails.Mailer' && input === ':adapter') { + const subkeys = Object.keys(state.settings[group][key]).filter(el => el !== ':adapter') + const emailsValue = subkeys.map(el => { + return { 'tuple': [el, state.settings[group][key][el]] } + }) + dispatch('RemoveSetting', [{ group, key, value: emailsValue, delete: true, subkeys }]) } - Message({ - message: i18n.t('settings.success'), - type: 'success', - duration: 5 * 1000 - }) - }, - UpdateSettings({ commit }, { tab, data }) { - commit('UPDATE_SETTINGS', { tab, data }) + 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) diff --git a/src/views/emojiPacks/index.vue b/src/views/emojiPacks/index.vue index 5e2e468d..4be41add 100644 --- a/src/views/emojiPacks/index.vue +++ b/src/views/emojiPacks/index.vue @@ -70,7 +70,7 @@ export default { return this.$store.state.app.device === 'mobile' }, labelWidth() { - return this.isMobile ? '100px' : '210px' + return this.isMobile ? '100px' : '240px' }, localPacks() { return this.$store.state.emojiPacks.localPacks diff --git a/src/views/settings/components/ActivityPub.vue b/src/views/settings/components/ActivityPub.vue index 2067be0a..4c99296d 100644 --- a/src/views/settings/components/ActivityPub.vue +++ b/src/views/settings/components/ActivityPub.vue @@ -1,33 +1,11 @@