diff --git a/CHANGELOG.md b/CHANGELOG.md index 116b247e..2c8eb5cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add ability to disable multi-factor authentication for a user - Add ability to manually evict and ban URLs from the Pleroma MediaProxy cache - Add Invalidation settings on MediaProxy tab -- Ability to configure S3 settings on Upload tab +- Ability to configure S3 settings on Upload tab, Pleroma.Web.ApiSpec.CastAndValidate and :modules settings on Other tab, Pools, Connections pools and Hackney pools settings on Job Queue tab, :restrict_unauthenticated settings on Authentication tab, :favicons and :welcome settings on Instance tab, :frontends settings on Frontend tab - Show number of open reports in Sidebar Menu - Add confirmation message when deleting a user @@ -31,7 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - When rendering user's profile, statuses, reports and notes check if required properties exist - Remove ability to moderate users that don't have valid nickname - Displays both labels and description in the header of group of settiings -- Ability to add custom values in Pleroma.Upload.Filter.Mogrify setting +- Ability to add custom values in Pleroma.Upload.Filter.Mogrify setting in the following format: '{"implode", "1"}' - Change types of the following settings: ':groups', ':replace', ':federated_timeline_removal', ':reject', ':match_actor'. Update functions that parses and wraps settings data according to this change. - Move rendering Crontab setting from a separate component to EditableKeyword component - Show only those MRF settings that have been enabled in MRF Policies setting @@ -42,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Send `true` and `false` as booleans if they are values of single selects on the Settings page - Fix sorting users on Users page if there is an acount with missing nickname or ID +- Add new type of settings: `['string', 'image']`. Render Image upload Input depending on the type of setting, not its key ## [2.0.3] - 2020-04-29 diff --git a/src/store/modules/normalizers.js b/src/store/modules/normalizers.js index c7fc33e8..3ca63ca2 100644 --- a/src/store/modules/normalizers.js +++ b/src/store/modules/normalizers.js @@ -50,12 +50,7 @@ export const parseNonTuples = (key, value) => { return updated } if (key === ':args') { - if (typeof value === 'string') { - return [value] - } - 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 typeof value === 'string' ? [value] : value } return value } @@ -89,8 +84,8 @@ export const parseTuples = (tuples, key) => { }, []) } 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] === ':proxy_url' || item.tuple[0] === ':sender') { + accum[item.tuple[0]] = parseStringOrTupleValue(item.tuple[0], 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]) && @@ -122,18 +117,29 @@ const parseObject = object => { }, {}) } -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 } +const parseStringOrTupleValue = (key, value) => { + if (key === ':proxy_url') { + 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 } + } else if (key === ':sender') { + if (typeof value === 'string') { + return { email: value } + } else if (value && + typeof value === 'object' && + value.tuple.length === 2) { + const [nickname, email] = value.tuple + return { nickname, email } + } } - return { socks5: false, host: null, port: null } } const prependWithСolon = (type, value) => { @@ -248,13 +254,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 === ':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 { 'tuple': [setting, value] } } diff --git a/src/views/settings/components/Authentication.vue b/src/views/settings/components/Authentication.vue index 8ad33f88..3e919c11 100644 --- a/src/views/settings/components/Authentication.vue +++ b/src/views/settings/components/Authentication.vue @@ -11,10 +11,14 @@ - + + + + +
Submit
@@ -81,6 +85,12 @@ export default { }, pleromaAuthenticatorData() { return _.get(this.settings.settings, [':pleroma', 'Pleroma.Web.Auth.Authenticator']) || {} + }, + restrictUnauthenticated() { + return this.settings.description.find(setting => setting.key === ':restrict_unauthenticated') + }, + restrictUnauthenticatedData() { + return _.get(this.settings.settings, [':pleroma', ':restrict_unauthenticated']) || {} } }, methods: { diff --git a/src/views/settings/components/Frontend.vue b/src/views/settings/components/Frontend.vue index 15d29084..ec3675b8 100644 --- a/src/views/settings/components/Frontend.vue +++ b/src/views/settings/components/Frontend.vue @@ -8,6 +8,10 @@ + + + + @@ -66,6 +70,12 @@ export default { frontendData() { return _.get(this.settings.settings, [':pleroma', ':frontend_configurations']) || {} }, + frontends() { + return this.settings.description.find(setting => setting.key === ':frontends') + }, + frontendsData() { + return _.get(this.settings.settings, [':pleroma', ':frontends']) || {} + }, isMobile() { return this.$store.state.app.device === 'mobile' }, diff --git a/src/views/settings/components/Inputs.vue b/src/views/settings/components/Inputs.vue index 0639994e..b412c1e4 100644 --- a/src/views/settings/components/Inputs.vue +++ b/src/views/settings/components/Inputs.vue @@ -112,6 +112,7 @@ + @@ -138,6 +139,7 @@ import { RateLimitInput, RegInvitesInput, SelectInputWithReducedLabels, + SenderInput, SpecificMultipleSelect } from './inputComponents' import { getBooleanValue, processNested } from '@/store/modules/normalizers' import _ from 'lodash' @@ -156,6 +158,7 @@ export default { RateLimitInput, RegInvitesInput, SelectInputWithReducedLabels, + SenderInput, SpecificMultipleSelect }, props: { @@ -292,7 +295,7 @@ export default { return this.$store.state.settings.updatedSettings }, isImageUrl() { - return [':background', ':logo', ':nsfwCensorImage', ':default_user_avatar', ':instance_thumbnail'].includes(this.setting.key) + return Array.isArray(this.setting.type) && this.setting.type.includes('image') } }, methods: { @@ -357,6 +360,9 @@ export default { renderSingleSelect(type) { return !this.reducedSelects && (type === 'module' || (type.includes('atom') && type.includes('dropdown'))) }, + senderInput({ key, type }) { + return Array.isArray(type) && type.includes('string') && type.includes('tuple') && key === ':sender' + }, update(value, group, key, parents, input, type, nested) { const updatedValue = this.renderSingleSelect(type) ? getBooleanValue(value) : value nested diff --git a/src/views/settings/components/Instance.vue b/src/views/settings/components/Instance.vue index 245d53d8..362393fe 100644 --- a/src/views/settings/components/Instance.vue +++ b/src/views/settings/components/Instance.vue @@ -8,6 +8,10 @@ + + + + @@ -20,6 +24,10 @@ + + + + @@ -27,7 +35,7 @@ - + @@ -58,6 +66,12 @@ export default { adminTokenData() { return _.get(this.settings.settings, [':pleroma', ':admin_token']) || {} }, + favicons() { + return this.settings.description.find(setting => setting.key === ':instances_favicons') + }, + faviconsData() { + return _.get(this.settings.settings, [':pleroma', ':instances_favicons']) || {} + }, feed() { return this.settings.description.find(setting => setting.key === ':feed') }, @@ -123,6 +137,12 @@ export default { }, uriSchemesData() { return _.get(this.settings.settings, [':pleroma', ':uri_schemes']) || {} + }, + welcome() { + return this.settings.description.find(setting => setting.key === ':welcome') + }, + welcomeData() { + return _.get(this.settings.settings, [':pleroma', ':welcome']) || {} } }, methods: { diff --git a/src/views/settings/components/JobQueue.vue b/src/views/settings/components/JobQueue.vue index a2c03349..63554eea 100644 --- a/src/views/settings/components/JobQueue.vue +++ b/src/views/settings/components/JobQueue.vue @@ -11,6 +11,18 @@ + + + + + + + + + + + +
Submit
@@ -36,6 +48,18 @@ export default { activityExpirationData() { return _.get(this.settings.settings, [':pleroma', 'Pleroma.ActivityExpiration']) || {} }, + connectionsPools() { + return this.settings.description.find(setting => setting.key === ':connections_pool') + }, + connectionsPoolsData() { + return _.get(this.settings.settings, [':pleroma', ':connections_pool']) || {} + }, + hackneyPools() { + return this.settings.description.find(setting => setting.key === ':hackney_pools') + }, + hackneyPoolsData() { + return _.get(this.settings.settings, [':pleroma', ':hackney_pools']) || {} + }, isMobile() { return this.$store.state.app.device === 'mobile' }, @@ -66,6 +90,12 @@ export default { obanQueuesData() { return _.get(this.settings.settings, [':pleroma', 'Oban']) || {} }, + pools() { + return this.settings.description.find(setting => setting.key === ':pools') + }, + poolsData() { + return _.get(this.settings.settings, [':pleroma', ':pools']) || {} + }, workers() { return this.settings.description.find(setting => setting.key === ':workers') }, diff --git a/src/views/settings/components/MRF.vue b/src/views/settings/components/MRF.vue index ba2454a5..a7275d83 100644 --- a/src/views/settings/components/MRF.vue +++ b/src/views/settings/components/MRF.vue @@ -49,9 +49,6 @@ export default { loading() { return this.settings.loading }, - modules() { - return this.settings.description.find(setting => setting.key === ':modules') - }, mrfSettings() { return this.settings.description.filter(el => el.tab === 'mrf') } diff --git a/src/views/settings/components/Other.vue b/src/views/settings/components/Other.vue index 978b93e4..b1ee7369 100644 --- a/src/views/settings/components/Other.vue +++ b/src/views/settings/components/Other.vue @@ -7,6 +7,14 @@ + + + + + + + +
Submit
@@ -26,6 +34,12 @@ export default { ...mapGetters([ 'settings' ]), + castAndValidate() { + return this.settings.description.find(setting => setting.key === 'Pleroma.Web.ApiSpec.CastAndValidate') + }, + castAndValidateData() { + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Web.ApiSpec.CastAndValidate']) || {} + }, isMobile() { return this.$store.state.app.device === 'mobile' }, @@ -56,6 +70,12 @@ export default { mimeTypesData() { return _.get(this.settings.settings, [':mime']) || {} }, + modules() { + return this.settings.description.find(setting => setting.key === ':modules') + }, + modulesData() { + return _.get(this.settings.settings, [':pleroma', ':modules']) || {} + }, remoteIp() { return this.settings.description.find(setting => setting.key === 'Pleroma.Plugs.RemoteIp') }, diff --git a/src/views/settings/components/inputComponents/SenderInput.vue b/src/views/settings/components/inputComponents/SenderInput.vue new file mode 100644 index 00000000..3e303c8a --- /dev/null +++ b/src/views/settings/components/inputComponents/SenderInput.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/views/settings/components/inputComponents/SpecificMultipleSelect.vue b/src/views/settings/components/inputComponents/SpecificMultipleSelect.vue index 06f06436..a9fb60eb 100644 --- a/src/views/settings/components/inputComponents/SpecificMultipleSelect.vue +++ b/src/views/settings/components/inputComponents/SpecificMultipleSelect.vue @@ -20,9 +20,11 @@ allow-create class="input" @change="updateSetting($event, settingGroup.group, settingGroup.key, setting.key, setting.type)"> - - - + + + + + diff --git a/src/views/settings/components/inputComponents/index.js b/src/views/settings/components/inputComponents/index.js index 0ef58841..a5a9a7f1 100644 --- a/src/views/settings/components/inputComponents/index.js +++ b/src/views/settings/components/inputComponents/index.js @@ -8,4 +8,5 @@ export { default as PruneInput } from './PruneInput' export { default as RateLimitInput } from './RateLimitInput' export { default as RegInvitesInput } from './RegInvitesInput' export { default as SelectInputWithReducedLabels } from './SelectInputWithReducedLabels' +export { default as SenderInput } from './SenderInput' export { default as SpecificMultipleSelect } from './SpecificMultipleSelect' diff --git a/src/views/settings/components/tabs.js b/src/views/settings/components/tabs.js index 394f0670..011a16b8 100644 --- a/src/views/settings/components/tabs.js +++ b/src/views/settings/components/tabs.js @@ -6,11 +6,7 @@ export const tabs = description => { }, 'authentication': { label: 'settings.auth', - settings: [':auth', ':ldap', ':oauth2', 'Pleroma.Web.Auth.Authenticator'] - }, - 'auto-linker': { - label: 'settings.autoLinker', - settings: [':opts'] + settings: [':auth', ':ldap', ':oauth2', 'Pleroma.Web.Auth.Authenticator', ':restrict_unauthenticated'] }, 'esshd': { label: 'settings.esshd', @@ -22,7 +18,7 @@ export const tabs = description => { }, 'frontend': { label: 'settings.frontend', - settings: [':assets', ':chat', ':emoji', ':frontend_configurations', ':markup', ':static_fe'] + settings: [':assets', ':chat', ':frontends', ':emoji', ':frontend_configurations', ':markup', ':static_fe'] }, 'gopher': { label: 'settings.gopher', @@ -34,11 +30,15 @@ export const tabs = description => { }, 'instance': { label: 'settings.instance', - settings: [':admin_token', ':instance', ':manifest', 'Pleroma.User', 'Pleroma.ScheduledActivity', ':uri_schemes', ':feed', ':streamer'] + settings: [':admin_token', ':instance', ':instances_favicons', ':welcome', ':manifest', 'Pleroma.User', 'Pleroma.ScheduledActivity', ':uri_schemes', ':feed', ':streamer'] }, 'job-queue': { label: 'settings.jobQueue', - settings: ['Pleroma.ActivityExpiration', 'Oban', ':workers'] + settings: ['Pleroma.ActivityExpiration', ':connections_pool', ':hackney_pools', 'Oban', ':pools', ':workers'] + }, + 'link-formatter': { + label: 'settings.linkFormatter', + settings: ['Pleroma.Formatter'] }, 'logger': { label: 'settings.logger', @@ -78,7 +78,7 @@ export const tabs = description => { }, 'other': { label: 'settings.other', - settings: [':mime', 'Pleroma.Plugs.RemoteIp'] + settings: [':mime', 'Pleroma.Plugs.RemoteIp', ':modules', 'Pleroma.Web.ApiSpec.CastAndValidate'] } } } diff --git a/src/views/settings/styles/main.scss b/src/views/settings/styles/main.scss index 96e699e1..4cecb962 100644 --- a/src/views/settings/styles/main.scss +++ b/src/views/settings/styles/main.scss @@ -46,6 +46,10 @@ .el-tabs__header { z-index: 2002; } + .email-address-input { + width: 50%; + margin-right: 10px; + } .esshd-list { margin: 0; } @@ -175,6 +179,9 @@ width: 30%; margin-right: 8px } + .nickname-input { + width: 50%; + } .no-top-margin { margin-top: 0; p { @@ -254,6 +261,12 @@ margin-left: 8px; margin-right: 10px } + .sender-input { + display: flex; + align-items: center; + margin-bottom: 10px; + width: 100%; + } .scale-input { width: 47%; margin: 0 1% 5px 0 diff --git a/test/modules/normalizers/parseTuples.test.js b/test/modules/normalizers/parseTuples.test.js index 9cbe9d2f..ff039135 100644 --- a/test/modules/normalizers/parseTuples.test.js +++ b/test/modules/normalizers/parseTuples.test.js @@ -223,10 +223,10 @@ describe('Parse tuples', () => { }) it('parses match_actor setting in mrf_subchain group', () => { - const tuples = [{ tuple: [":match_actor", - { '~r/https:\/\/example.com/s': ["Elixir.Pleroma.Web.ActivityPub.MRF.DropPolicy"]}]}] - const expectedResult = { ":match_actor": - [{ '~r/https:\/\/example.com/s': { value: ["Elixir.Pleroma.Web.ActivityPub.MRF.DropPolicy"] }}]} + const tuples = [{ tuple: [':match_actor', + { '~r/https:\/\/example.com/s': ['Elixir.Pleroma.Web.ActivityPub.MRF.DropPolicy']}]}] + const expectedResult = { ':match_actor': + [{ '~r/https:\/\/example.com/s': { value: ['Elixir.Pleroma.Web.ActivityPub.MRF.DropPolicy'] }}]} const parsed = parseTuples(tuples, ':mrf_subchain') @@ -241,7 +241,7 @@ describe('Parse tuples', () => { }) it('parses options setting in MediaProxy.Invalidation.Http group', () => { - const tuples = [{ tuple: [":options", [{ tuple: [":params", { xxx: "zzz", aaa: "bbb" }]}]]}] + const tuples = [{ tuple: [':options', [{ tuple: [':params', { xxx: 'zzz', aaa: 'bbb' }]}]]}] const expectedResult = { ':options': { ':params': [ { xxx: { value: 'zzz' }}, { aaa: { value: 'bbb' }}] }} @@ -260,14 +260,28 @@ describe('Parse tuples', () => { expect(_.isEqual(expectedResult, parsed)).toBeTruthy() }) - it('parses proxy_url', () => { - const proxyUrlNull = [{ tuple: [":proxy_url", null] }] - const proxyUrlTuple = [{ tuple: [":proxy_url", { tuple: [":socks5", ":localhost", 3090] }]}] - const proxyUrlString = [{ tuple: [":proxy_url", 'localhost:9020'] }] + it('parses sender setting in :welcome', () => { + const senderEmpty = [{ tuple: [':sender', ''] }] + const senderTuple = [{ tuple: [':sender', { tuple: ['test', 'test@email.com'] }]}] + const senderString = [{ tuple: [':sender', 'test@email.com'] }] - const expectedProxyUrlNull = { ":proxy_url": { socks5: false, host: null, port: null }} - const expectedProxyUrlTuple = { ":proxy_url": { socks5: true, host: ":localhost", port: 3090 }} - const expectedProxyUrlString = { ":proxy_url": { socks5: false, host: 'localhost', port: '9020' }} + const expectedSenderEmpty = { ':sender': { email: '' }} + const expectedSenderTuple = { ':sender': { email: 'test@email.com', nickname: 'test' }} + const expectedSenderString = { ':sender': { email: 'test@email.com' }} + + expect(_.isEqual(expectedSenderEmpty, parseTuples(senderEmpty, ':welcome'))).toBeTruthy() + expect(_.isEqual(expectedSenderTuple, parseTuples(senderTuple, ':welcome'))).toBeTruthy() + expect(_.isEqual(expectedSenderString, parseTuples(senderString, ':welcome'))).toBeTruthy() + }) + + it('parses proxy_url', () => { + const proxyUrlNull = [{ tuple: [':proxy_url', null] }] + const proxyUrlTuple = [{ tuple: [':proxy_url', { tuple: [':socks5', ':localhost', 3090] }]}] + const proxyUrlString = [{ tuple: [':proxy_url', 'localhost:9020'] }] + + const expectedProxyUrlNull = { ':proxy_url': { socks5: false, host: null, port: null }} + const expectedProxyUrlTuple = { ':proxy_url': { socks5: true, host: ':localhost', port: 3090 }} + const expectedProxyUrlString = { ':proxy_url': { socks5: false, host: 'localhost', port: '9020' }} expect(_.isEqual(expectedProxyUrlNull, parseTuples(proxyUrlNull, ':http'))).toBeTruthy() expect(_.isEqual(expectedProxyUrlTuple, parseTuples(proxyUrlTuple, ':http'))).toBeTruthy() @@ -275,8 +289,8 @@ describe('Parse tuples', () => { }) it('parses args setting in Pleroma.Upload.Filter.Mogrify', () => { - const tuples = [{ tuple: [":args", ["strip", { tuple: ["implode", "1"] }]]}] - const expectedResult = { ":args": ["strip", "implode"] } + const tuples = [{ tuple: [':args', ['strip', '{ "implode", "1" }']]}] + const expectedResult = { ':args': ['strip', '{ "implode", "1" }'] } const result = parseTuples(tuples, 'Pleroma.Upload.Filter.Mogrify') expect(_.isEqual(expectedResult, result)).toBeTruthy() @@ -284,19 +298,19 @@ describe('Parse tuples', () => { it('parses nested tuples', () => { const tuples = [{ tuple: [':proxy_opts', [ - { tuple: [":redirect_on_failure", false] }, - { tuple: [":max_body_length", 26214400] }, - { tuple: [":http", [ - { tuple: [":follow_redirect", true] }, - { tuple: [":pool", ":media"] } + { tuple: [':redirect_on_failure', false] }, + { tuple: [':max_body_length', 26214400] }, + { tuple: [':http', [ + { tuple: [':follow_redirect', true] }, + { tuple: [':pool', ':media'] } ]]}, ]]}] const expectedResult = { ':proxy_opts': { - ":redirect_on_failure": false, - ":max_body_length": 26214400, - ":http": { - ":follow_redirect": true, - ":pool": ":media" + ':redirect_on_failure': false, + ':max_body_length': 26214400, + ':http': { + ':follow_redirect': true, + ':pool': ':media' } }} const result = parseTuples(tuples, ':media_proxy') @@ -304,8 +318,8 @@ describe('Parse tuples', () => { }) it('parses tuples with arrays', () => { - const tuples = [{ tuple: [":ignore_hosts", []]}, { tuple: [":ignore_tld", ["local", "localdomain", "lan"]]}] - const expectedResult = { ":ignore_hosts": [], ":ignore_tld": ["local", "localdomain", "lan"] } + const tuples = [{ tuple: [':ignore_hosts', []]}, { tuple: [':ignore_tld', ['local', 'localdomain', 'lan']]}] + const expectedResult = { ':ignore_hosts': [], ':ignore_tld': ['local', 'localdomain', 'lan'] } const result = parseTuples(tuples, ':rich_media') expect(_.isEqual(expectedResult, result)).toBeTruthy() diff --git a/test/modules/normalizers/wrapUpdatedSettings.test.js b/test/modules/normalizers/wrapUpdatedSettings.test.js index dedb5c34..78f8ee6e 100644 --- a/test/modules/normalizers/wrapUpdatedSettings.test.js +++ b/test/modules/normalizers/wrapUpdatedSettings.test.js @@ -357,14 +357,14 @@ describe('Wrap settings', () => { it('wraps args setting in Pleroma.Upload.Filter.Mogrify group', () => { const settings = { 'Pleroma.Upload.Filter.Mogrify': { ':args': [ ['string', ['list', 'string'], ['list', 'tuple']], - ['strip', 'implode'] + ['strip', '{ "implode", "1"]}'] ]}} 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']}]]}] + value: [{tuple: [':args', ['strip', '{ "implode", "1"]}']]}] }] expect(_.isEqual(result, expectedResult)).toBeTruthy()