diff --git a/.eslintrc.js b/.eslintrc.js index 6f55c5a1..65f9687c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -141,7 +141,8 @@ module.exports = { 'no-unsafe-finally': 2, 'no-unused-vars': [2, { 'vars': 'all', - 'args': 'none' + 'args': 'none', + 'ignoreRestSiblings': true }], 'no-useless-call': 2, 'no-useless-computed-key': 2, diff --git a/package.json b/package.json index 9adb8ba8..fac6ba8e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "axios": "0.18.0", "clipboard": "1.7.1", "codemirror": "5.39.2", + "default-passive-events": "^1.0.10", "driver.js": "0.8.1", "dropzone": "5.2.0", "echarts": "4.1.0", @@ -63,6 +64,7 @@ "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/initialDataForConfig.js b/src/api/initialDataForConfig.js new file mode 100644 index 00000000..bc425a35 --- /dev/null +++ b/src/api/initialDataForConfig.js @@ -0,0 +1,110 @@ +export const initialSettings = [ + { + group: 'pleroma', + key: ':instance', + value: [ + { 'tuple': [':name', 'Pleroma'] }, + { 'tuple': [':email', 'example@example.com'] }, + { 'tuple': [':notify_email', 'noreply@example.com'] }, + { 'tuple': [':description', 'A Pleroma instance, an alternative fediverse server'] }, + { 'tuple': [':limit', 5000] }, + { 'tuple': [':remote_limit', 100000] }, + { 'tuple': [':upload_limit', 16 * 1048576] }, + { 'tuple': [':avatar_upload_limit', 2 * 1048576] }, + { 'tuple': [':background_upload_limit', 4 * 1048576] }, + { 'tuple': [':banner_upload_limit', 4 * 1048576] }, + { 'tuple': [':poll_limits', [ + { 'tuple': [':max_options', 20] }, + { 'tuple': [':max_option_chars', 200] }, + { 'tuple': [':min_expiration', 0] }, + { 'tuple': [':max_expiration', 365 * 86400] } + ]] }, + { 'tuple': [':registrations_open', true] }, + { 'tuple': [':invites_enabled', false] }, + { 'tuple': [':account_activation_required', false] }, + { 'tuple': [':federating', true] }, + { 'tuple': [':federation_reachability_timeout_days', 7] }, + { 'tuple': + [':federation_publisher_modules', ['Pleroma.Web.ActivityPub.Publisher', 'Pleroma.Web.Websub', 'Pleroma.Web.Salmon']] }, + { 'tuple': [':allow_relay', true] }, + { 'tuple': [':rewrite_policy', 'Pleroma.Web.ActivityPub.MRF.NoOpPolicy'] }, + { 'tuple': [':public', true] }, + { 'tuple': [':managed_config', true] }, + { 'tuple': [':static_dir', 'instance/static/'] }, + { 'tuple': [':allowed_post_formats', ['text/plain', 'text/html', 'text/markdown', 'text/bbcode']] }, + { 'tuple': [':mrf_transparency', true] }, + { 'tuple': [':extended_nickname_format', false] }, + { 'tuple': [':max_pinned_statuses', 1] }, + { 'tuple': [':no_attachment_links', false] }, + { 'tuple': [':max_report_comment_size', 1000] }, + { 'tuple': [':safe_dm_mentions', false] }, + { 'tuple': [':healthcheck', false] }, + { 'tuple': [':remote_post_retention_days', 90] }, + { 'tuple': [':skip_thread_containment', true] }, + { 'tuple': [':limit_to_local_content', ':unauthenticated'] }, + { 'tuple': [':dynamic_configuration', true] } + ] + }, + { + group: 'mime', + key: ':types', + value: { + 'application/activity+json': ['activity+json'], + 'application/jrd+json': ['jrd+json'], + 'application/ld+json': ['activity+json'], + 'application/xml': ['xml'], + 'application/xrd+xml': ['xrd+xml'] + } + }, + { + group: 'cors_plug', + key: ':max_age', + value: 86400 + }, + { + group: 'cors_plug', + key: ':methods', + value: ['POST', 'PUT', 'DELETE', 'GET', 'PATCH', 'OPTIONS'] + }, + { + group: 'cors_plug', + key: ':expose', + value: [ + 'Link', + 'X-RateLimit-Reset', + 'X-RateLimit-Limit', + 'X-RateLimit-Remaining', + 'X-Request-Id', + 'Idempotency-Key' + ] + }, + { + group: 'cors_plug', + key: ':credentials', + value: true + }, + { + group: 'cors_plug', + key: ':headers', + value: ['Authorization', 'Content-Type', 'Idempotency-Key'] + }, + { + group: 'tesla', + key: ':adapter', + value: 'Tesla.Adapter.Hackney' + }, + { + group: 'pleroma', + key: ':markup', + value: [ + { 'tuple': [':allow_inline_images', true] }, + { 'tuple': [':allow_headings', false] }, + { 'tuple': [':allow_tables', false] }, + { 'tuple': [':allow_fonts', false] }, + { 'tuple': [':scrub_policy', [ + 'Pleroma.HTML.Transform.MediaProxy', + 'Pleroma.HTML.Scrubber.Default' + ]] } + ] + } +] diff --git a/src/api/settings.js b/src/api/settings.js new file mode 100644 index 00000000..a0da39cd --- /dev/null +++ b/src/api/settings.js @@ -0,0 +1,36 @@ +import request from '@/utils/request' +import { getToken } from '@/utils/auth' +import { baseName } from './utils' + +export async function fetchSettings(authHost, token) { + return await request({ + baseURL: baseName(authHost), + url: `/api/pleroma/admin/config`, + method: 'get', + headers: authHeaders(token) + }) +} + +export async function updateSettings(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) + 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/icons/svg/settings.svg b/src/icons/svg/settings.svg new file mode 100644 index 00000000..b41911d4 --- /dev/null +++ b/src/icons/svg/settings.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lang/en.js b/src/lang/en.js index b8cfa4c6..94e64662 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -65,7 +65,8 @@ export default { i18n: 'I18n', externalLink: 'External Link', users: 'Users', - reports: 'Reports' + reports: 'Reports', + settings: 'Settings' }, navbar: { logOut: 'Log Out', @@ -273,5 +274,30 @@ export default { open: 'Open', closed: 'Closed', resolved: 'Resolved' + }, + settings: { + settings: 'Settings', + instance: 'Instance', + upload: 'Upload', + mailer: 'Mailer', + logger: 'Logger', + activityPub: 'ActivityPub', + auth: 'Authentication', + autoLinker: 'Auto Linker', + captcha: 'Captcha', + frontend: 'Frontend', + http: 'HTTP', + mrf: 'MRF', + 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', + success: 'Settings changed successfully!' } } diff --git a/src/router/index.js b/src/router/index.js index 77bb9758..e215ca4b 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -76,6 +76,18 @@ export const asyncRouterMap = [ } ] }, + { + path: '/settings', + component: Layout, + children: [ + { + path: 'index', + component: () => import('@/views/settings/index'), + name: 'Settings', + meta: { title: 'settings', icon: 'settings', noCache: true } + } + ] + }, { path: '/users/:id', component: Layout, diff --git a/src/store/getters.js b/src/store/getters.js index cec3bce1..49d041f9 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -16,6 +16,82 @@ const getters = { addRouters: state => state.permission.addRouters, errorLogs: state => state.errorLog.logs, users: state => state.users.fetchedUsers, - authHost: state => state.user.authHost + authHost: state => state.user.authHost, + + activityPubConfig: state => state.settings.settings['activitypub'], + adminTokenConfig: state => state.settings.settings['admin_token'], + assetsConfig: state => state.settings.settings['assets'], + authConfig: state => state.settings.settings['auth'], + autoLinkerConfig: state => state.settings.settings['auto_linker'], + captchaConfig: state => state.settings.settings['Pleroma.Captcha'], + chatConfig: state => state.settings.settings['chat'], + consoleConfig: state => state.settings.settings['console'], + corsPlugCredentials: state => state.settings.settings['credentials'], + corsPlugExposeConfig: state => state.settings.settings['expose'], + corsPlugHeaders: state => state.settings.settings['headers'], + corsPlugMaxAge: state => state.settings.settings['max_age'], + corsPlugMethods: state => state.settings.settings['methods'], + databaseConfig: state => state.settings.settings['database'], + ectoReposConfig: state => state.settings.settings['ecto_repos'], + emojiConfig: state => state.settings.settings['emoji'], + enabledConfig: state => state.settings.settings['enabled'], + endpointConfig: state => state.settings.settings['Pleroma.Web.Endpoint'], + exsysloggerConfig: state => state.settings.settings['ex_syslogger'], + facebookConfig: state => state.settings.settings['Ueberauth.Strategy.Facebook.OAuth'], + fetchInitialPostsConfig: state => state.settings.settings['fetch_initial_posts'], + formatEncodersConfig: state => state.settings.settings['format_encoders'], + frontendConfig: state => state.settings.settings['frontend_configurations'], + googleConfig: state => state.settings.settings['Ueberauth.Strategy.Google.OAuth'], + gopherConfig: state => state.settings.settings['gopher'], + hackneyPoolsConfig: state => state.settings.settings['hackney_pools'], + handlerConfig: state => state.settings.settings['handler'], + httpConfig: state => state.settings.settings['http'], + httpSecurityConfig: state => state.settings.settings['http_security'], + instanceConfig: state => state.settings.settings['instance'], + kocaptchaConfig: state => state.settings.settings['Pleroma.Captcha.Kocaptcha'], + levelConfig: state => state.settings.settings['level'], + ldapConfig: state => state.settings.settings['ldap'], + loggerBackendsConfig: state => state.settings.settings['backends'], + mailerConfig: state => state.settings.settings['Pleroma.Emails.Mailer'], + markupConfig: state => state.settings.settings['markup'], + mediaProxyConfig: state => state.settings.settings['media_proxy'], + metaConfig: state => state.settings.settings['meta'], + metadataConfig: state => state.settings.settings['Pleroma.Web.Metadata'], + microsoftConfig: state => state.settings.settings['Ueberauth.Strategy.Microsoft.OAuth'], + mimeTypesConfig: state => state.settings.settings['types'], + mrfHellthreadConfig: state => state.settings.settings['mrf_hellthread'], + mrfKeywordConfig: state => state.settings.settings['mrf_keyword'], + mrfMentionConfig: state => state.settings.settings['mrf_mention'], + mrfNormalizeMarkupConfig: state => state.settings.settings['mrf_normalize_markup'], + mrfRejectnonpublicConfig: state => state.settings.settings['mrf_rejectnonpublic'], + mrfSimpleConfig: state => state.settings.settings['mrf_simple'], + mrfSubchainConfig: state => state.settings.settings['mrf_subchain'], + mrfUserAllowlistConfig: state => state.settings.settings['mrf_user_allowlist'], + oauth2Config: state => state.settings.settings['oauth2'], + passwordAuthenticatorConfig: state => state.settings.settings['password_authenticator'], + pleromaAuthenticatorConfig: state => state.settings.settings['Pleroma.Web.Auth.Authenticator'], + pleromaRepoConfig: state => state.settings.settings['Pleroma.Repo'], + pleromaUserConfig: state => state.settings.settings['Pleroma.User'], + portConfig: state => state.settings.settings['port'], + privDirConfig: state => state.settings.settings['priv_dir'], + queuesConfig: state => state.settings.settings['queues'], + rateLimitersConfig: state => state.settings.settings['rate_limit'], + retryQueueConfig: state => state.settings.settings['Pleroma.Web.Federator.RetryQueue'], + richMediaConfig: state => state.settings.settings['rich_media'], + suggestionsConfig: state => state.settings.settings['suggestions'], + scheduledActivityConfig: state => state.settings.settings['Pleroma.ScheduledActivity'], + teslaAdapterConfig: state => state.settings.settings['adapter'], + twitterConfig: state => state.settings.settings['Ueberauth.Strategy.Twitter.OAuth'], + ueberauthConfig: state => state.settings.settings['Ueberauth'], + uploadAnonymizeFilenameConfig: state => state.settings.settings['Pleroma.Upload.Filter.AnonymizeFilename'], + uploadConfig: state => state.settings.settings['Pleroma.Upload'], + uploadFilterMogrifyConfig: state => state.settings.settings['Pleroma.Upload.Filter.Mogrify'], + uploadersLocalConfig: state => state.settings.settings['Pleroma.Uploaders.Local'], + uploadMDIIConfig: state => state.settings.settings['Pleroma.Uploaders.MDII'], + uploadS3Config: state => state.settings.settings['Pleroma.Uploaders.S3'], + uriSchemesConfig: state => state.settings.settings['uri_schemes'], + userConfig: state => state.settings.settings['user'], + vapidDetailsConfig: state => state.settings.settings['vapid_details'], + webhookUrlConfig: state => state.settings.settings['webhook_url'] } export default getters diff --git a/src/store/index.js b/src/store/index.js index 84e93f94..161a87d6 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -4,6 +4,7 @@ import app from './modules/app' import errorLog from './modules/errorLog' import permission from './modules/permission' import reports from './modules/reports' +import settings from './modules/settings' import tagsView from './modules/tagsView' import user from './modules/user' import userProfile from './modules/userProfile' @@ -18,6 +19,7 @@ const store = new Vuex.Store({ errorLog, permission, reports, + settings, tagsView, user, userProfile, diff --git a/src/store/modules/normalizers.js b/src/store/modules/normalizers.js new file mode 100644 index 00000000..37d17b62 --- /dev/null +++ b/src/store/modules/normalizers.js @@ -0,0 +1,188 @@ +const nonAtomsTuples = ['replace', 'match_actor', ':replace', ':match_actor'] +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.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', + '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', + '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 filterIgnored = (settings, ignored) => { + if (settings.enabled.value === true) { + return settings + } + + return ignored.reduce((acc, name) => { + const { [name]: ignored, ...newAcc } = acc + + return newAcc + }, settings) +} + +// REFACTOR +export const parseTuples = (tuples, key) => { + return tuples.reduce((accum, item) => { + if (key === 'rate_limit') { + accum[item.tuple[0].substr(1)] = 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]) + } 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('.') + } else { + key === 'mrf_user_allowlist' + ? accum[item.tuple[0]] = item.tuple[1] + : accum[item.tuple[0].substr(1)] = item.tuple[1] + } + return accum + }, {}) +} + +const parseNonAtomTuples = (tuples) => { + return tuples.reduce((acc, item) => { + acc[item.tuple[0]] = item.tuple[1] + return acc + }, {}) +} + +export const valueHasTuples = (key, value) => { + const valueIsArrayOfNonObjects = Array.isArray(value) && value.length > 0 && typeof value[0] !== 'object' + return key === 'meta' || + key === 'types' || + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + 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 === '') { + return acc + } else if (key === ':rate_limit') { + return [...acc, { 'tuple': [`:${settingName}`, data] }] + } else if (settingName === 'ip') { + const ip = data.split('.') + return [...acc, { 'tuple': [`:${settingName}`, { 'tuple': ip }] }] + } else if (!Array.isArray(data) && typeof data === 'object') { + return nonAtomsTuples.includes(settingName) + ? [...acc, { 'tuple': [`:${settingName}`, wrapNonAtomsTuples(data)] }] + : [...acc, { 'tuple': [`:${settingName}`, wrapNestedTuples(data)] }] + } + return key === ':mrf_user_allowlist' + ? [...acc, { 'tuple': [`${settingName}`, settings[config][settingName]] }] + : [...acc, { 'tuple': [`:${settingName}`, settings[config][settingName]] }] + }, []) + return { group, key, value } + }) +} + +const wrapNestedTuples = setting => { + return Object.keys(setting).reduce((acc, settingName) => { + const data = setting[settingName] + if (data === '') { + return acc + } else if (settingName === 'ip') { + const ip = data.split('.') + return [...acc, { 'tuple': [`:${settingName}`, { 'tuple': ip }] }] + } else if (!Array.isArray(data) && typeof data === 'object') { + return [...acc, { 'tuple': [`:${settingName}`, wrapNestedTuples(data)] }] + } + return [...acc, { 'tuple': [`:${settingName}`, setting[settingName]] }] + }, []) +} + +const wrapNonAtomsTuples = setting => { + return Object.keys(setting).reduce((acc, settingName) => { + return [...acc, { 'tuple': [`${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 new file mode 100644 index 00000000..0bf95a36 --- /dev/null +++ b/src/store/modules/settings.js @@ -0,0 +1,145 @@ +import { fetchSettings, updateSettings, uploadMedia } from '@/api/settings' +import { initialSettings } from '@/api/initialDataForConfig' +import { filterIgnored, parseTuples, valueHasTuples, wrapConfig } from './normalizers' + +const settings = { + state: { + settings: { + 'activitypub': {}, + 'adapter': {}, + 'admin_token': {}, + 'assets': { mascots: {}}, + 'auth': {}, + 'auto_linker': { opts: {}}, + 'backends': {}, + 'chat': {}, + 'console': { colors: {}}, + 'credentials': {}, + 'database': {}, + 'ecto_repos': {}, + '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': {}, + 'oauth2': {}, + 'password_authenticator': {}, + 'Pleroma.Captcha': {}, + 'Pleroma.Captcha.Kocaptcha': {}, + 'Pleroma.Emails.Mailer': {}, + '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': {} + }, + ignoredIfNotEnabled: ['enabled', 'handler', 'password_authenticator', 'port', 'priv_dir'], + loading: true + }, + mutations: { + REWRITE_CONFIG: (state, { tab, data }) => { + state.settings[tab] = 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 } + 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 } + } + }) + } + }, + actions: { + async FetchSettings({ commit, dispatch, getters }) { + commit('SET_LOADING', true) + const response = await fetchSettings(getters.authHost, getters.token) + if (response.data.configs.length === 0) { + dispatch('SubmitChanges', initialSettings) + } else { + commit('SET_SETTINGS', response.data.configs) + } + commit('SET_LOADING', false) + }, + RewriteConfig({ commit }, { tab, data }) { + commit('REWRITE_CONFIG', { tab, data }) + }, + async SubmitChanges({ getters, commit, state }, data) { + const filteredSettings = filterIgnored(state.settings, state.ignoredIfNotEnabled) + const configs = data || wrapConfig(filteredSettings) + const response = await updateSettings(configs, getters.authHost, getters.token) + if (data) { + commit('SET_SETTINGS', response.data.configs) + } + }, + UpdateSettings({ commit }, { tab, data }) { + commit('UPDATE_SETTINGS', { tab, data }) + }, + async UploadMedia({ dispatch, getters, state }, { file, tab, inputName, childName }) { + const response = await uploadMedia(file, getters.authHost, getters.token) + const updatedValue = { ...state.settings[tab][inputName], ...{ [childName]: response.data.url }} + dispatch('UpdateSettings', { tab, data: { [inputName]: updatedValue }}) + } + } +} + +export default settings diff --git a/src/views/settings/components/ActivityPub.vue b/src/views/settings/components/ActivityPub.vue new file mode 100644 index 00000000..455b9f67 --- /dev/null +++ b/src/views/settings/components/ActivityPub.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/views/settings/components/Authentication.vue b/src/views/settings/components/Authentication.vue new file mode 100644 index 00000000..79104c0d --- /dev/null +++ b/src/views/settings/components/Authentication.vue @@ -0,0 +1,303 @@ + + + + + diff --git a/src/views/settings/components/AutoLinker.vue b/src/views/settings/components/AutoLinker.vue new file mode 100644 index 00000000..8d843e11 --- /dev/null +++ b/src/views/settings/components/AutoLinker.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/src/views/settings/components/Captcha.vue b/src/views/settings/components/Captcha.vue new file mode 100644 index 00000000..4dd269ef --- /dev/null +++ b/src/views/settings/components/Captcha.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/src/views/settings/components/Database.vue b/src/views/settings/components/Database.vue new file mode 100644 index 00000000..6f70d863 --- /dev/null +++ b/src/views/settings/components/Database.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/src/views/settings/components/Endpoint.vue b/src/views/settings/components/Endpoint.vue new file mode 100644 index 00000000..7a739b10 --- /dev/null +++ b/src/views/settings/components/Endpoint.vue @@ -0,0 +1,266 @@ + + + + + diff --git a/src/views/settings/components/Esshd.vue b/src/views/settings/components/Esshd.vue new file mode 100644 index 00000000..ac8fa290 --- /dev/null +++ b/src/views/settings/components/Esshd.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/src/views/settings/components/Frontend.vue b/src/views/settings/components/Frontend.vue new file mode 100644 index 00000000..421466de --- /dev/null +++ b/src/views/settings/components/Frontend.vue @@ -0,0 +1,451 @@ + + + + + diff --git a/src/views/settings/components/Gopher.vue b/src/views/settings/components/Gopher.vue new file mode 100644 index 00000000..f7db70fe --- /dev/null +++ b/src/views/settings/components/Gopher.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/views/settings/components/Http.vue b/src/views/settings/components/Http.vue new file mode 100644 index 00000000..328e516c --- /dev/null +++ b/src/views/settings/components/Http.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/src/views/settings/components/Instance.vue b/src/views/settings/components/Instance.vue new file mode 100644 index 00000000..af0800f1 --- /dev/null +++ b/src/views/settings/components/Instance.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/src/views/settings/components/JobQueue.vue b/src/views/settings/components/JobQueue.vue new file mode 100644 index 00000000..e5e18d14 --- /dev/null +++ b/src/views/settings/components/JobQueue.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/views/settings/components/Logger.vue b/src/views/settings/components/Logger.vue new file mode 100644 index 00000000..c04491d6 --- /dev/null +++ b/src/views/settings/components/Logger.vue @@ -0,0 +1,247 @@ + + + + + diff --git a/src/views/settings/components/MRF.vue b/src/views/settings/components/MRF.vue new file mode 100644 index 00000000..854b7a51 --- /dev/null +++ b/src/views/settings/components/MRF.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/src/views/settings/components/Mailer.vue b/src/views/settings/components/Mailer.vue new file mode 100644 index 00000000..77eeed69 --- /dev/null +++ b/src/views/settings/components/Mailer.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/views/settings/components/MediaProxy.vue b/src/views/settings/components/MediaProxy.vue new file mode 100644 index 00000000..e82e37bc --- /dev/null +++ b/src/views/settings/components/MediaProxy.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/views/settings/components/Metadata.vue b/src/views/settings/components/Metadata.vue new file mode 100644 index 00000000..60ec6da2 --- /dev/null +++ b/src/views/settings/components/Metadata.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/views/settings/components/Other.vue b/src/views/settings/components/Other.vue new file mode 100644 index 00000000..e36889d0 --- /dev/null +++ b/src/views/settings/components/Other.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/views/settings/components/RateLimiters.vue b/src/views/settings/components/RateLimiters.vue new file mode 100644 index 00000000..e88b0a95 --- /dev/null +++ b/src/views/settings/components/RateLimiters.vue @@ -0,0 +1,362 @@ + + + + + diff --git a/src/views/settings/components/Upload.vue b/src/views/settings/components/Upload.vue new file mode 100644 index 00000000..cbbe7711 --- /dev/null +++ b/src/views/settings/components/Upload.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/src/views/settings/components/WebPush.vue b/src/views/settings/components/WebPush.vue new file mode 100644 index 00000000..549ce29b --- /dev/null +++ b/src/views/settings/components/WebPush.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/views/settings/components/index.js b/src/views/settings/components/index.js new file mode 100644 index 00000000..4e5b8e8e --- /dev/null +++ b/src/views/settings/components/index.js @@ -0,0 +1,21 @@ +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' +export { default as Http } from './Http' +export { default as Instance } from './Instance' +export { default as JobQueue } from './JobQueue' +export { default as Logger } from './Logger' +export { default as Mailer } from './Mailer' +export { default as MediaProxy } from './MediaProxy' +export { default as Metadata } from './Metadata' +export { default as Mrf } from './MRF' +export { default as Other } from './Other' +export { default as RateLimiters } from './RateLimiters' +export { default as Upload } from './Upload' +export { default as WebPush } from './WebPush' diff --git a/src/views/settings/components/options.js b/src/views/settings/components/options.js new file mode 100644 index 00000000..0d348402 --- /dev/null +++ b/src/views/settings/components/options.js @@ -0,0 +1,116 @@ +export const options = { + federationPublisherModulesOptions: [ + { label: 'Pleroma.Web.ActivityPub.Publisher', value: 'Pleroma.Web.ActivityPub.Publisher' }, + { label: 'Pleroma.Web.Websub', value: 'Pleroma.Web.Websub' }, + { label: 'Pleroma.Web.Salmon', value: 'Pleroma.Web.Salmon' }], + rewritePolicyOptions: [ + { label: 'NoOpPolicy', value: 'Pleroma.Web.ActivityPub.MRF.NoOpPolicy', expl: 'NoOpPolicy: Doesn’t modify activities (default)' }, + { label: 'DropPolicy', value: 'Pleroma.Web.ActivityPub.MRF.DropPolicy', expl: 'DropPolicy: Drops all activities. It generally doesn’t makes sense to use in production' }, + { label: 'SimplePolicy', value: 'Pleroma.Web.ActivityPub.MRF.SimplePolicy', expl: 'SimplePolicy: Restrict the visibility of activities from certains instances (See :mrf_simple section)' }, + { label: 'TagPolicy', value: 'Pleroma.Web.ActivityPub.MRF.TagPolicy', expl: 'Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive)' }, + { label: 'SubchainPolicy', value: 'Pleroma.Web.ActivityPub.MRF.SubchainPolicy', expl: 'Selectively runs other MRF policies when messages match (see :mrf_subchain section)' }, + { label: 'RejectNonPublic', value: 'Pleroma.Web.ActivityPub.MRF.RejectNonPublic', expl: 'RejectNonPublic: Drops posts with non-public visibility settings (See :mrf_rejectnonpublic section)' }, + { label: 'EnsureRePrepended', value: 'Pleroma.Web.ActivityPub.MRF.EnsureRePrepended', expl: 'EnsureRePrepended: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:' }, + { label: 'AntiLinkSpamPolicy', value: 'Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy', expl: 'Rejects posts from likely spambots by rejecting posts from new users that contain links' }, + { label: 'MediaProxyWarmingPolicy', value: 'Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy', expl: 'Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed' }, + { label: 'MentionPolicy', value: 'Pleroma.Web.ActivityPub.MRF.MentionPolicy', expl: 'Drops posts mentioning configurable users. (see :mrf_mention section)' } + ], + quarantinedInstancesOptions: [], + autofollowedNicknamesOptions: [], + uriSchemesOptions: [ + { label: 'https', value: 'https' }, + { label: 'http', value: 'http' }, + { label: 'dat', value: 'dat' }, + { label: 'dweb', value: 'dweb' }, + { label: 'gopher', value: 'gopher' }, + { label: 'ipfs', value: 'ipfs' }, + { label: 'ipns', value: 'ipns' }, + { label: 'irc', value: 'irc' }, + { label: 'ircs', value: 'ircs' }, + { label: 'magnet', value: 'magnet' }, + { label: 'mailto', value: 'mailto' }, + { label: 'mumble', value: 'mumble' }, + { label: 'ssb', value: 'ssb' }, + { label: 'xmpp', value: 'xmpp' }], + themeOptions: [ + { label: 'pleroma-dark', value: 'pleroma-dark' }, + { label: 'pleroma-light', value: 'pleroma-light' }, + { label: 'classic-dark', value: 'classic-dark' }, + { label: 'bird', value: 'bird' }, + { label: 'ir-black', value: 'ir-black' }, + { label: 'monokai', value: 'monokai' }, + { label: 'mammal', value: 'mammal' }, + { label: 'redmond-xx', value: 'redmond-xx' }, + { label: 'redmond-xx-se', value: 'redmond-xx-se' }, + { label: 'redmond-xxi', value: 'redmond-xxi' }, + { label: 'breezy-dark', value: 'breezy-dark' }, + { label: 'breezy-light', value: 'breezy-light' }], + instrumentersOptions: [{ label: 'Pleroma.Web.Endpoint.Instrumenter', value: 'Pleroma.Web.Endpoint.Instrumenter' }], + extraCookieAttrsOptions: [{ label: 'SameSite=Lax', value: 'SameSite=Lax' }], + hackneyPoolsOptions: [{ label: 'Federation', value: ':federation', max_connections: 50, timeout: 150000 }, + { label: 'Media', value: ':media', max_connections: 50, timeout: 150000 }, + { label: 'Upload', value: ':upload', max_connections: 25, timeout: 300000 }], + whitelistedContentTypesOptions: [{ label: 'image/gif', value: 'image/gif' }, + { label: 'image/jpeg', value: 'image/jpeg' }, + { label: 'image/jpg', value: 'image/jpg' }, + { label: 'image/png', value: 'image/png' }, + { label: 'image/svg+xml', value: 'image/svg+xml' }, + { label: 'audio/mpeg', value: 'audio/mpeg' }, + { label: 'audio/mp3', value: 'audio/mp3' }, + { label: 'video/webm', value: 'video/webm' }, + { label: 'video/mp4', value: 'video/mp4' }, + { label: 'video/quicktime', value: 'video/quicktime' }], + mogrifyActionsOptions: [{ label: 'strip', value: 'strip' }, { label: 'auto-orient', value: 'auto-orient' }], + adapterOptions: [ + { label: 'Swoosh.Adapters.Sendmail', value: 'Swoosh.Adapters.Sendmail' }, + { label: 'Swoosh.Adapters.SMTP', value: 'Swoosh.Adapters.SMTP' }, + { label: 'Swoosh.Adapters.Sendgrid', value: 'Swoosh.Adapters.Sendgrid' }, + { label: 'Swoosh.Adapters.Mandrill', value: 'Swoosh.Adapters.Mandrill' }, + { label: 'Swoosh.Adapters.Mailgun', value: 'Swoosh.Adapters.Mailgun' }, + { label: 'Swoosh.Adapters.Mailjet', value: 'Swoosh.Adapters.Mailjet' }, + { label: 'Swoosh.Adapters.Postmark', value: 'Swoosh.Adapters.Postmark' }, + { label: 'Swoosh.Adapters.SparkPost', value: 'Swoosh.Adapters.SparkPost' }, + { label: 'Swoosh.Adapters.AmazonSES', value: 'Swoosh.Adapters.AmazonSES' }, + { label: 'Swoosh.Adapters.Dyn', value: 'Swoosh.Adapters.Dyn' }, + { label: 'Swoosh.Adapters.SocketLabs', value: 'Swoosh.Adapters.SocketLabs' }, + { label: 'Swoosh.Adapters.Gmail', value: 'Swoosh.Adapters.Gmail' }, + { label: 'Swoosh.Adapters.Local', value: 'Swoosh.Adapters.Local' } + ], + loggerBackendsOptions: [ + { label: 'Console // log to stdout', value: JSON.stringify(':console') }, + { label: 'Ex_syslogger // log to syslog', value: JSON.stringify({ 'tuple': ['ExSyslogger', ':ex_syslogger'] }) }, + { label: 'Quack.Logger // log to Slack', value: JSON.stringify('Quack.Logger') } + ], + restrictedNicknamesOptions: [ + { value: '.well-known' }, + { value: '~' }, + { value: 'about' }, + { value: 'activities' }, + { value: 'api' }, + { value: 'auth' }, + { value: 'check_password' }, + { value: 'dev' }, + { value: 'friend-requests' }, + { value: 'inbox' }, + { value: 'internal' }, + { value: 'main' }, + { value: 'media' }, + { value: 'nodeinfo' }, + { value: 'notice' }, + { value: 'oauth' }, + { value: 'objects' }, + { value: 'ostatus_subscribe' }, + { value: 'pleroma' }, + { value: 'proxy' }, + { value: 'push' }, + { value: 'registration' }, + { value: 'relay' }, + { value: 'settings' }, + { value: 'status' }, + { value: 'tag' }, + { value: 'user-search' }, + { value: 'user_exists' }, + { value: 'users' }, + { value: 'web' } + ] +} diff --git a/src/views/settings/index.vue b/src/views/settings/index.vue new file mode 100644 index 00000000..79ae7727 --- /dev/null +++ b/src/views/settings/index.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/views/settings/styles/main.scss b/src/views/settings/styles/main.scss new file mode 100644 index 00000000..2648f480 --- /dev/null +++ b/src/views/settings/styles/main.scss @@ -0,0 +1,156 @@ +@mixin settings { + a { + text-decoration: underline; + } + .code { + background-color: #adbed67a; + border-radius: 3px; + font-family: monospace; + padding: 0 3px 0 3px; + } + .el-form-item { + margin-right: 30px; + } + .el-select { + width: 100%; + } + .esshd-list { + margin: 0; + } + .expl { + color: #666666; + font-size: 13px; + line-height: 22px; + margin: 5px 0 0 0; + overflow-wrap: break-word; + } + .highlight { + background-color: #e6e6e6; + } + .limit-button-container { + display: flex; + align-items: baseline; + } + .limit-expl { + margin-left: 10px; + } + .limit-input { + width: 48%; + margin: 0 0 5px 8px + } + .line { + width: 100%; + height: 0; + border: 1px solid #eee; + margin-bottom: 22px; + } + .mascot-container { + margin-bottom: 15px; + } + .mascot-input { + margin-bottom: 7px; + } + .mascot-name-container { + display: flex; + margin-bottom: 7px; + } + .mascot-name-input { + margin-right: 10px + } + .name-input { + width: 30%; + margin-right: 8px + } + .options-paragraph { + font-size: 14px; + color: #606266; + font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei"; + font-weight: 700; + line-height: 20px; + margin: 0 0 14px 0; + } + .options-paragraph-container { + overflow-wrap: break-word; + margin-bottom: 0; + } + .pattern-input { + width: 20%; + margin-right: 8px + } + .setting-input { + display: flex; + margin-bottom: 10px; + } + .single-input { + margin-right: 10px + } + .scale-input { + width: 48%; + margin: 0 8px 5px 0 + } + .replacement-input { + width: 80%; + margin-left: 8px; + margin-right: 10px + } + .text { + line-height: 20px; + margin-right: 15px + } + .upload-container { + display: flex; + align-items: baseline; + } + .value-input { + width: 70%; + margin-left: 8px; + margin-right: 10px + } + + @media only screen and (max-width: 760px), + (min-device-width: 768px) and (max-device-width: 1024px) { + .el-form-item { + margin-right: 15px; + } + .el-input__inner { + padding: 0 5px 0 5px + } + .el-form-item__label:not(.no-top-margin) { + padding-left: 3px; + padding-right: 10px; + line-height: 22px; + margin-top: 7px; + } + .el-message { + min-width: 80%; + } + .el-select__tags { + overflow: hidden; + } + .name-input { + width: 40%; + margin-right: 5px + } + p.expl { + line-height: 20px; + } + .pattern-input { + width: 40%; + margin-right: 4px + } + .replacement-input { + width: 60%; + margin-left: 4px; + margin-right: 5px + } + .top-margin { + position: absolute; + top: 25%; + } + .value-input { + width: 60%; + margin-left: 5px; + margin-right: 8px + } + } +} diff --git a/yarn.lock b/yarn.lock index c687a794..d6060dd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1868,6 +1868,11 @@ 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" @@ -3144,6 +3149,11 @@ default-gateway@^2.6.0: execa "^0.10.0" ip-regex "^2.1.0" +default-passive-events@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/default-passive-events/-/default-passive-events-1.0.10.tgz#28ad3269648a76a0158f413d66e37af24dad053a" + integrity sha1-KK0yaWSKdqAVj0E9ZuN68k2tBTo= + default-require-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7" @@ -10190,6 +10200,13 @@ 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"