Merge branch 'feature/update-server-configuration' into 'develop'

Update server configuration

See merge request pleroma/admin-fe!65
This commit is contained in:
feld 2020-01-09 15:57:41 +00:00
commit 9206538f61
44 changed files with 2846 additions and 3702 deletions

View file

@ -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)

View file

@ -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

View file

@ -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))
}

View file

@ -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)

View file

@ -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

View file

@ -1,33 +1,11 @@
<template>
<div>
<el-form ref="activityPub" :model="activityPub" :label-width="labelWidth">
<el-form-item label="Unfollow blocked">
<el-switch :value="activityPub.unfollow_blocked" @change="updateSetting($event, 'activitypub', 'unfollow_blocked')"/>
<p class="expl">Whether blocks result in people getting unfollowed</p>
</el-form-item>
<el-form-item label="Outgoing blocks">
<el-switch :value="activityPub.outgoing_blocks" @change="updateSetting($event, 'activitypub', 'outgoing_blocks')"/>
<p class="expl">Whether to federate blocks to other instances</p>
</el-form-item>
<el-form-item label="Follow handshake timeout">
<el-input-number
:value="activityPub.follow_handshake_timeout"
:step="100"
:min="0"
size="large"
class="top-margin"
@change="updateSetting($event, 'activitypub', 'follow_handshake_timeout')"/>
</el-form-item>
<el-form-item label="Sign object fetches">
<el-switch :value="activityPub.sign_object_fetches" @change="updateSetting($event, 'activitypub', 'sign_object_fetches')"/>
<p class="expl">Sign object fetches with HTTP signatures</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="activitypubData" :model="activitypubData" :label-width="labelWidth">
<setting :setting-group="activitypub" :data="activitypubData"/>
</el-form>
<el-form ref="user" :model="user" :label-width="labelWidth">
<el-form-item label="Deny follow blocked">
<el-switch :value="user.deny_follow_blocked" @change="updateSetting($event, 'user', 'deny_follow_blocked')"/>
<p class="expl">Whether to disallow following an account that has blocked the user in question</p>
</el-form-item>
<div class="line"/>
<el-form ref="userData" :model="userData" :label-width="labelWidth">
<setting :setting-group="user" :data="userData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -37,27 +15,49 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'ActivityPub',
components: { Setting },
computed: {
...mapGetters([
'activityPub',
'user'
'settings'
]),
activitypub() {
return this.settings.description.find(setting => setting.key === ':activitypub')
},
activitypubData() {
return this.settings.settings[':pleroma'][':activitypub']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.$store.state.settings.loading
},
user() {
return this.settings.description.find(setting => setting.key === ':user')
},
userData() {
return this.settings.settings[':pleroma'][':user']
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,228 +1,19 @@
<template>
<div>
<el-form ref="pleromaAuthenticator" :model="pleromaAuthenticator" :label-width="labelWidth">
<el-form-item label="Authentication type">
<el-select :value="pleromaAuthenticator.value" clearable @change="updateSetting($event, 'Pleroma.Web.Auth.Authenticator', 'value')">
<el-option label="None" value=""/>
<el-option label="Pleroma.Web.Auth.PleromaAuthenticator // Default database authenticator" value="Pleroma.Web.Auth.PleromaAuthenticator"/>
<el-option label="Pleroma.Web.Auth.LDAPAuthenticator // LDAP authenticator" value="Pleroma.Web.Auth.LDAPAuthenticator"/>
</el-select>
</el-form-item>
<div v-if="!loading">
<el-form ref="pleromaAuthenticatorData" :model="pleromaAuthenticatorData" :label-width="labelWidth">
<setting :setting-group="pleromaAuthenticator" :data="pleromaAuthenticatorData"/>
</el-form>
<div class="line"/>
<el-form ref="auth" :model="auth" :label-width="labelWidth">
<el-form-item label="Authentication settings:"/>
<el-form-item label="Auth template">
<el-input :value="auth.auth_template" @input="updateSetting($event, 'auth', 'auth_template')"/>
<p class="expl">Authentication form template. By default it's
<span class="code">show.html</span> which corresponds to
<span class="code">lib/pleroma/web/templates/o_auth/o_auth/show.html.eex.</span>
</p>
</el-form-item>
<el-form-item label="OAuth consumer template">
<el-input :value="auth.oauth_consumer_template" @input="updateSetting($event, 'auth', 'oauth_consumer_template')"/>
<p class="expl">OAuth consumer mode authentication form template. By default it's
<span class="code">consumer.html</span> which corresponds to
<span class="code">lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex.</span>
</p>
</el-form-item>
<el-form-item label="OAuth consumer strategies">
<el-select :value="auth.oauth_consumer_strategies || []" multiple filterable allow-create @change="updateSetting($event, 'auth', 'oauth_consumer_strategies')"/>
<p class="expl">The list of enabled OAuth consumer strategies; by default it's set by
<span class="code">OAUTH_CONSUMER_STRATEGIES</span>
environment variable. You can enter values in the following format: <span class="code">'a:foo b:baz'</span>
</p>
</el-form-item>
<el-form ref="authData" :model="authData" :label-width="labelWidth">
<setting :setting-group="auth" :data="authData"/>
</el-form>
<div class="line"/>
<el-form ref="ldap" :model="ldap" :label-width="labelWidth">
<el-form-item class="options-paragraph-container">
<p class="options-paragraph">Use LDAP for user authentication. When a user logs in to the Pleroma
instance, the name and password will be verified by trying to authenticate
(bind) to an LDAP server. If a user exists in the LDAP directory but there
is no account with the same name yet on the Pleroma instance then a new
Pleroma account will be created with the same name as the LDAP user name.</p>
</el-form-item>
<el-form-item label="LDAP Authenticator:"/>
<el-form-item label="Enabled">
<el-switch :value="ldap.enabled" @change="updateSetting($event, 'ldap', 'enabled')"/>
<p class="expl">Enables LDAP authentication</p>
</el-form-item>
<el-form-item label="Host">
<el-input :value="ldap.host" @input="updateSetting($event, 'ldap', 'host')"/>
<p class="expl">LDAP server hostname</p>
</el-form-item>
<el-form-item label="Port">
<el-input :value="ldap.port" @input="updateSetting($event, 'ldap', 'port')"/>
<p class="expl">LDAP port, e.g. 389 or 636</p>
</el-form-item>
<el-form-item label="SSL">
<el-switch :value="ldap.ssl" @change="updateSetting($event, 'ldap', 'ssl')"/>
<p class="expl">True to use SSL, usually implies the port 636</p>
</el-form-item>
<el-form-item label="TLS">
<el-switch :value="ldap.tls" @change="updateSetting($event, 'ldap', 'tls')"/>
<p class="expl">True to start TLS, usually implies the port 389</p>
</el-form-item>
<el-form-item label="Base">
<el-input :value="ldap.base" @input="updateSetting($event, 'ldap', 'base')"/>
<p class="expl">LDAP base, e.g. <span class="code">'dc=example,dc=com'</span></p>
</el-form-item>
<el-form-item label="UID">
<el-input :value="ldap.uid" @input="updateSetting($event, 'ldap', 'uid')"/>
<p class="expl">LDAP attribute name to authenticate the user, e.g. when
<span class="code">'cn'</span>, the filter will be <span class="code">'cn=username,base'</span>
</p>
</el-form-item>
<el-form ref="ldapData" :model="ldapData" :label-width="labelWidth">
<setting :setting-group="ldap" :data="ldapData"/>
</el-form>
<div class="line"/>
<el-form ref="ueberauth" :model="ueberauth" :label-width="labelWidth">
<el-form-item label="OAuth consumer mode" class="options-paragraph-container">
<p class="options-paragraph">
OAuth consumer mode allows sign in / sign up via external OAuth providers
(e.g. Twitter, Facebook, Google, Microsoft, etc.). Implementation is based on Ueberauth; see the list of
<a
href="https://github.com/ueberauth/ueberauth/wiki/List-of-Strategies"
rel="nofollow noreferrer noopener"
target="_blank">
available strategies.
</a>
</p>
<p class="options-paragraph">
Note: each strategy is shipped as a separate dependency; in order to get the strategies, run
<span class="code">OAUTH_CONSUMER_STRATEGIES="..." mix deps.get</span>,
e.g. <span class="code">OAUTH_CONSUMER_STRATEGIES="twitter facebook google microsoft" mix deps.get</span>.
The server should also be started with <span class="code">OAUTH_CONSUMER_STRATEGIES="..." mix phx.server</span>
in case you enable any strategies.
</p>
<p class="options-paragraph">
Note: each strategy requires separate setup (on external provider side and Pleroma side).
Below are the guidelines on setting up most popular strategies.
</p>
<p class="options-paragraph">
Note: make sure that <span class="code">'SameSite=Lax'</span> is set in
<span class="code">extra_cookie_attrs</span> when you have this feature enabled.
OAuth consumer mode will not work with <span class="code">'SameSite=Strict'</span>
</p>
<p class="options-paragraph">For Twitter,
<a
href="https://developer.twitter.com/en/apps"
rel="nofollow noreferrer noopener"
target="_blank">
register an app,
</a>
configure callback URL to <span class="code">https://&lt;your_host&gt;/oauth/twitter/callback</span>
</p>
<p class="options-paragraph">For Facebook,
<a
href="https://developers.facebook.com/apps"
rel="nofollow noreferrer noopener"
target="_blank">
register an app,
</a>
configure callback URL to <span class="code">https://&lt;your_host&gt;/oauth/facebook/callback</span>,
enable Facebook Login service at
<span class="code">https://developers.facebook.com/apps/&lt;app_id&gt;/fb-login/settings/</span>
</p>
<p class="options-paragraph">For Google,
<a
href="https://console.developers.google.com/"
rel="nofollow noreferrer noopener"
target="_blank">
register an app,
</a>
configure callback URL to <span class="code">https://&lt;your_host&gt;/oauth/google/callback</span>
</p>
<p class="options-paragraph">For Microsoft,
<a
href="https://portal.azure.com"
rel="nofollow noreferrer noopener"
target="_blank">
register an app,
</a>
configure callback URL to <span class="code">https://&lt;your_host&gt;/oauth/microsoft/callback</span>
</p>
<p class="options-paragraph">
Once the app is configured on external OAuth provider side, add app's credentials and strategy-specific settings
per strategy's documentation (e.g.
<a
href="https://github.com/ueberauth/ueberauth_twitter"
rel="nofollow noreferrer noopener"
target="_blank">
ueberauth_twitter
</a>).
</p>
</el-form-item>
<el-form-item label="Ueberauth:"/>
<el-form-item label="Base path">
<el-input :value="ueberauth.base_path" @input="updateSetting($event, 'ueberauth', 'base_path')"/>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="facebook" :model="facebook" :label-width="labelWidth">
<el-form-item label="Facebook:"/>
<el-form-item label="Client ID">
<el-input :value="facebook.client_id" @input="updateSetting($event, 'Ueberauth.Strategy.Facebook.OAuth', 'client_id')"/>
</el-form-item>
<el-form-item label="Client secret">
<el-input :value="facebook.client_secret" @input="updateSetting($event, 'Ueberauth.Strategy.Facebook.OAuth', 'client_secret')"/>
</el-form-item>
<el-form-item label="Redirect URI">
<el-input :value="facebook.redirect_uri" @input="updateSetting($event, 'Ueberauth.Strategy.Facebook.OAuth', 'redirect_uri')"/>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="twitter" :model="twitter" :label-width="labelWidth">
<el-form-item label="Twitter:"/>
<el-form-item label="Consumer key">
<el-input :value="twitter.consumer_key" @input="updateSetting($event, 'Ueberauth.Strategy.Twitter.OAuth', 'consumer_key')"/>
</el-form-item>
<el-form-item label="Consumer secret">
<el-input :value="twitter.consumer_secret" @input="updateSetting($event, 'Ueberauth.Strategy.Twitter.OAuth', 'consumer_secret')"/>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="google" :model="google" :label-width="labelWidth">
<el-form-item label="Google:"/>
<el-form-item label="Client ID">
<el-input :value="google.client_id" @input="updateSetting($event, 'Ueberauth.Strategy.Google.OAuth', 'client_id')"/>
</el-form-item>
<el-form-item label="Client secret">
<el-input :value="google.client_secret" @input="updateSetting($event, 'Ueberauth.Strategy.Google.OAuth', 'client_secret')"/>
</el-form-item>
<el-form-item label="Redirect URI">
<el-input :value="google.redirect_uri" @input="updateSetting($event, 'Ueberauth.Strategy.Google.OAuth', 'redirect_uri')"/>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="microsoft" :model="microsoft" :label-width="labelWidth">
<el-form-item label="Microsoft:"/>
<el-form-item label="Client ID">
<el-input :value="microsoft.client_id" @input="updateSetting($event, 'Ueberauth.Strategy.Microsoft.OAuth', 'client_id')"/>
</el-form-item>
<el-form-item label="Client secret">
<el-input :value="microsoft.client_secret" @input="updateSetting($event, 'Ueberauth.Strategy.Microsoft.OAuth', 'client_secret')"/>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="oauth2" :model="oauth2" :label-width="labelWidth">
<el-form-item label="OAuth 2.0 Provider:"/>
<el-form-item label="Token expires in (s)">
<el-input-number :value="oauth2.token_expires_in" :step="10" :min="0" size="large" @change="updateSetting($event, 'oauth2', 'token_expires_in')"/>
<p class="expl">The lifetime in seconds of the access token</p>
</el-form-item>
<el-form-item label="Issue new refresh token">
<el-switch :value="oauth2.issue_new_refresh_token" @change="updateSetting($event, 'oauth2', 'issue_new_refresh_token')"/>
<p class="expl">Keeps old refresh token or generate new refresh token when to obtain an access token</p>
</el-form-item>
<el-form-item label="Clean expired token">
<el-switch :value="oauth2.clean_expired_tokens" @change="updateSetting($event, 'oauth2', 'clean_expired_tokens')"/>
<p class="expl">Enable a background job to clean expired oauth tokens. Defaults to false.</p>
</el-form-item>
<el-form-item label="Clean expired token interval">
<el-input-number :value="oauth2.clean_expired_tokens_interval / 3600000" :step="1" :min="0" size="large" @change="updateSetting($event * 3600000, 'oauth2', 'clean_expired_tokens_interval')"/>
<p class="expl">Interval to run the job to clean expired tokens. Defaults to 24 hours.</p>
</el-form-item>
<el-form ref="oauth2" :model="oauth2Data" :label-width="labelWidth">
<setting :setting-group="oauth2" :data="oauth2Data"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -232,34 +23,61 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Authentication',
components: { Setting },
computed: {
...mapGetters([
'pleromaAuthenticator',
'ldap',
'auth',
'ueberauth',
'oauth2',
'facebook',
'google',
'twitter',
'microsoft'
'settings'
]),
auth() {
return this.settings.description.find(setting => setting.key === ':auth')
},
authData() {
return this.settings.settings[':pleroma'][':auth']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
ldap() {
return this.settings.description.find(setting => setting.key === ':ldap')
},
ldapData() {
return this.settings.settings[':pleroma'][':ldap']
},
loading() {
return this.settings.loading
},
oauth2() {
return this.settings.description.find(setting => setting.key === ':oauth2')
},
oauth2Data() {
return this.settings.settings[':pleroma'][':oauth2']
},
pleromaAuthenticator() {
return this.settings.description.find(setting => setting.description === 'Authenticator')
},
pleromaAuthenticatorData() {
return this.settings.settings[':pleroma']['Pleroma.Web.Auth.Authenticator']
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,49 +1,6 @@
<template>
<el-form v-if="!loading" ref="autoLinker" :model="autoLinker" :label-width="labelWidth">
<el-form-item label="Class">
<el-switch :value="booleanClass" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'class')"/>
<p v-if="!booleanClass" class="expl">Specify the class to be added to the generated link. False to clear.</p>
</el-form-item>
<el-form-item v-if="booleanClass">
<el-input :value="getStringValue('class')" @input="processTwoTypeValue($event, 'auto_linker', 'opts', 'class')"/>
<p class="expl">Specify the class to be added to the generated link. False to clear.</p>
</el-form-item>
<el-form-item label="Rel">
<el-switch :value="booleanRel" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'rel')"/>
<p v-if="!booleanRel" class="expl">Override the rel attribute. False to clear</p>
</el-form-item>
<el-form-item v-if="booleanRel">
<el-input :value="getStringValue('rel')" @input="processTwoTypeValue($event, 'auto_linker', 'opts', 'rel')"/>
<p class="expl">Override the rel attribute. False to clear</p>
</el-form-item>
<el-form-item label="New window">
<el-switch :value="autoLinker.opts.new_window" @change="processNestedData($event, 'auto_linker', 'opts', 'new_window')"/>
<p class="expl">Set to false to remove <span class="code">target='_blank'</span> attribute</p>
</el-form-item>
<el-form-item label="Scheme">
<el-switch :value="autoLinker.opts.scheme" @change="processNestedData($event, 'auto_linker', 'opts', 'scheme')"/>
<p class="expl">Set to true to link urls with schema <span class="code">http://google.com</span></p>
</el-form-item>
<el-form-item label="Truncate">
<el-switch :value="booleanTruncate" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'truncate')"/>
<p v-if="!booleanTruncate" class="expl">Set to a number to truncate urls longer then the number.
Truncated urls will end in <span class="code">..</span></p>
</el-form-item>
<el-form-item v-if="booleanTruncate">
<el-input-number :value="getStringValue('truncate')" :step="1" :min="0" size="large" @change="processTwoTypeValue($event, 'auto_linker', 'opts', 'truncate')"/>
<p class="expl">Specify the class to be added to the generated link. False to clear.</p>
</el-form-item>
<el-form-item label="Strip prefix">
<el-switch :value="autoLinker.opts.strip_prefix" @change="processNestedData($event, 'auto_linker', 'opts', 'strip_prefix')"/>
<p class="expl">Strip the scheme prefix</p>
</el-form-item>
<el-form-item label="Extra">
<el-switch :value="autoLinker.opts.extra" @change="processNestedData($event, 'auto_linker', 'opts', 'extra')"/>
<p class="expl">Link urls with rarely used schemes (magnet, ipfs, irc, etc.)</p>
</el-form-item>
<el-form-item label="Validate TLD">
<el-switch :value="autoLinker.opts.validate_tld" @change="processNestedData($event, 'auto_linker', 'opts', 'validate_tld')"/>
</el-form-item>
<el-form v-if="!loading" ref="autoLinker" :model="autoLinkerData" :label-width="labelWidth">
<setting :setting-group="autoLinker" :data="autoLinkerData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -52,62 +9,43 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'AutoLinker',
components: { Setting },
computed: {
...mapGetters([
'autoLinker'
'settings'
]),
autoLinker() {
return this.settings.description.find(setting => setting.key === ':opts')
},
autoLinkerData() {
return this.settings.settings[':auto_linker'][':opts']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.$store.state.settings.loading
},
booleanClass() {
return this.getBooleanValue('class')
},
booleanRel() {
return this.getBooleanValue('rel')
},
booleanTruncate() {
return this.getBooleanValue('truncate')
return this.settings.loading
}
},
methods: {
getBooleanValue(name) {
const value = this.autoLinker.opts[name]
return typeof value === 'string' || typeof value === 'number'
},
getNumValue(name) {
const value = this.autoLinker.opts[name]
return value || 0
},
getStringValue(name) {
const value = this.autoLinker.opts[name]
return value || ''
},
processTwoTypeValue(value, tab, inputName, childName) {
if (value === true) {
const data = childName === 'truncate' ? 0 : ''
this.processNestedData(data, tab, inputName, childName)
} else {
this.processNestedData(value, tab, inputName, childName)
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
},
processNestedData(value, tab, inputName, childName) {
const updatedValue = { ...this.$store.state.settings.settings[tab][inputName], ...{ [childName]: value }}
this.updateSetting(updatedValue, tab, inputName)
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,29 +1,11 @@
<template>
<div>
<el-form ref="captcha" :model="captcha" :label-width="labelWidth">
<el-form-item label="Enabled">
<el-switch :value="captcha.enabled" @change="updateSetting($event, 'Pleroma.Captcha', 'enabled')"/>
<p class="expl">Whether the captcha should be shown on registration</p>
</el-form-item>
<el-form-item label="Valid for (s)">
<el-input-number :value="captcha.seconds_valid" :step="1" :min="0" size="large" @change="updateSetting($event, 'Pleroma.Captcha', 'seconds_valid')"/>
<p class="expl">The time in seconds for which the captcha is valid</p>
</el-form-item>
<el-form-item label="Method">
<el-select :value="captcha.method" clearable @change="updateSetting($event, 'Pleroma.Captcha', 'method')">
<el-option label="Pleroma.Captcha.Kocaptcha" value="Pleroma.Captcha.Kocaptcha"/>
</el-select>
<p class="expl">The method/service to use for captcha</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="captchaData" :model="captchaData" :label-width="labelWidth">
<setting :setting-group="captcha" :data="captchaData"/>
</el-form>
<el-form ref="kocaptcha" :model="kocaptcha" :label-width="labelWidth">
<el-form-item label="Kocaptcha Endpoint">
<el-input :value="kocaptcha.endpoint" @input="updateSetting($event, 'Pleroma.Captcha.Kocaptcha', 'endpoint')"/>
<p class="expl">Kocaptcha is a captcha service with a single API endpoint, the source code is
<a href="https://github.com/koto-bank/kocaptcha" rel="nofollow noreferrer noopener" target="_blank">here</a>.
The default endpoint <span class="code">'https://captcha.kotobank.ch'</span> is hosted by the developer.
</p>
</el-form-item>
<div class="line"/>
<el-form ref="kocaptchaData" :model="kocaptchaData" :label-width="labelWidth">
<setting :setting-group="kocaptcha" :data="kocaptchaData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -33,27 +15,49 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Captcha',
components: { Setting },
computed: {
...mapGetters([
'captcha',
'kocaptcha'
'settings'
]),
captcha() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha')
},
captchaData() {
return this.settings.settings[':pleroma']['Pleroma.Captcha']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
kocaptcha() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Captcha.Kocaptcha')
},
kocaptchaData() {
return this.settings.settings[':pleroma']['Pleroma.Captcha.Kocaptcha']
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,132 +1,7 @@
<template>
<div>
<el-form ref="database" :model="database" :label-width="labelWidth">
<el-form-item label="Database settings:"/>
<el-form-item label="RUM enabled">
<el-switch :value="database.rum_enabled" @change="updateSetting($event, 'database', 'rum_enabled')"/>
<p class="expl">RUM indexes are an alternative indexing scheme that is not included in PostgreSQL by default.
While they may eventually be mainlined, for now they have to be installed as a PostgreSQL extension from
<a
href="https://github.com/postgrespro/rum"
rel="nofollow noreferrer noopener"
target="_blank">
https://github.com/postgrespro/rum.
</a>
</p>
<p class="expl">Their advantage over the standard GIN indexes is that they allow efficient ordering of search results by timestamp,
which makes search queries a lot faster on larger servers, by one or two orders of magnitude.
They take up around 3 times as much space as GIN indexes.</p>
<p class="expl">To enable them, both the <span class="code">rum_enabled</span> flag has to be set and the following special
migration has to be run: <span class="code">mix ecto.migrate --migrations-path priv/repo/optional_migrations/rum_indexing/</span></p>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="ectoRepos" :model="ectoRepos" :label-width="labelWidth">
<el-form-item label="Ecto repos">
<el-select :value="ectoRepos.value || []" multiple filterable allow-create @change="updateSetting($event, 'ecto_repos', 'value')">
<el-option label="Pleroma.Repo" value="Pleroma.Repo"/>
</el-select>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="pleromaRepo" :model="pleromaRepo" :label-width="labelWidth">
<el-form-item label="Pleroma Repo configuration:"/>
<el-form-item label="Name">
<el-input :value="pleromaRepo.name" @input="updateSetting($event, 'Pleroma.Repo', 'name')"/>
<p class="expl">The name of the Repo supervisor process</p>
</el-form-item>
<el-form-item label="Priv">
<el-input :value="pleromaRepo.priv" @input="updateSetting($event, 'Pleroma.Repo', 'priv')"/>
<p class="expl">The directory where to keep repository data, like migrations, schema and more. Defaults to <span class="code">
priv/YOUR_REPO</span>. It must always point to a subdirectory inside the priv directory</p>
</el-form-item>
<el-form-item label="URL">
<el-input :value="pleromaRepo.url" @input="updateSetting($event, 'Pleroma.Repo', 'url')"/>
<p class="expl">An URL that specifies storage information</p>
</el-form-item>
<el-form-item label="Log level">
<el-select :value="pleromaRepo.log" clearable @change="updateSetting($event, 'Pleroma.Repo', 'log')">
<el-option :value="false" label="False - disables logging for that repository."/>
<el-option value=":debug" label=":debug - for debug-related messages"/>
<el-option value=":info" label=":info - for information of any kind"/>
<el-option value=":warn" label=":warn - for warnings"/>
<el-option value=":error" label=":error - for errors"/>
</el-select>
<p class="expl">The log level used when logging the query with Elixir's Logger</p>
</el-form-item>
<el-form-item label="Pool size">
<el-input-number :value="pleromaRepo.pool_size" :step="1" :min="0" size="large" @change="updateSetting($event, 'Pleroma.Repo', 'pool_size')"/>
<p class="expl">The size of the pool used by the connection module. Defaults to <span class="code">10</span></p>
</el-form-item>
<el-form-item label="Telemetry prefix">
<el-select :value="pleromaRepo.telemetry_prefix || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.Repo', 'telemetry_prefix')">
<el-option label=":my_app" value=":my_app"/>
<el-option label=":repo" value=":repo"/>
<el-option label=":query" value=":query"/>
</el-select>
</el-form-item>
<el-form-item label="Types">
<el-input :value="pleromaRepo.types" @input="updateSetting($event, 'Pleroma.Repo', 'types')"/>
</el-form-item>
<el-form-item label="Telemetry event">
<el-select :value="pleromaRepo.telemetry_event || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.Repo', 'telemetry_event')">
<el-option label="Pleroma.Repo.Instrumenter" value="Pleroma.Repo.Instrumenter"/>
</el-select>
</el-form-item>
<el-form-item label="Connection options:"/>
<el-form-item label="Hostname">
<el-input :value="pleromaRepo.hostname" @input="updateSetting($event, 'Pleroma.Repo', 'hostname')"/>
<p class="expl">Server hostname</p>
</el-form-item>
<el-form-item label="Socket dir">
<el-input :value="pleromaRepo.socket_dir" @input="updateSetting($event, 'Pleroma.Repo', 'socket_dir')"/>
<p class="expl">Connect to Postgres via UNIX sockets in the given directory. The socket name is derived based on the port.
This is the preferred method for configuring sockets and it takes precedence over the hostname.
If you are connecting to a socket outside of the Postgres convention, use <span class="code">:socket</span> instead.</p>
</el-form-item>
<el-form-item label="Socket">
<el-input :value="pleromaRepo.socket" @input="updateSetting($event, 'Pleroma.Repo', 'socket')"/>
<p class="expl">Connect to Postgres via UNIX sockets in the given path. This option takes precedence over the
<span class="code">:hostname</span> and <span class="code">:socket_dir</span></p>
</el-form-item>
<el-form-item label="Username">
<el-input :value="pleromaRepo.username" @input="updateSetting($event, 'Pleroma.Repo', 'username')"/>
</el-form-item>
<el-form-item label="Password">
<el-input :value="pleromaRepo.password" @input="updateSetting($event, 'Pleroma.Repo', 'password')"/>
</el-form-item>
<el-form-item label="Port">
<el-input :value="pleromaRepo.port" @input="updateSetting($event, 'Pleroma.Repo', 'port')"/>
<p class="expl">Server port (default: 5432)</p>
</el-form-item>
<el-form-item label="Database">
<el-input :value="pleromaRepo.database" @input="updateSetting($event, 'Pleroma.Repo', 'database')"/>
<p class="expl">The database to connect to</p>
</el-form-item>
<el-form-item label="Maintenance database">
<el-input :value="pleromaRepo.maintenance_database" @input="updateSetting($event, 'Pleroma.Repo', 'maintenance_database')"/>
<p class="expl">Specifies the name of the database to connect to when creating or dropping the database. Defaults to "postgres"</p>
</el-form-item>
<el-form-item label="Pool">
<el-input :value="pleromaRepo.pool" @input="updateSetting($event, 'Pleroma.Repo', 'pool')"/>
<p class="expl">The connection pool module, defaults to <span class="code">DBConnection.ConnectionPool</span></p>
</el-form-item>
<el-form-item label="SSL">
<el-switch :value="pleromaRepo.ssl" @change="updateSetting($event, 'Pleroma.Repo', 'ssl')"/>
<p class="expl">Set to true if ssl should be used</p>
</el-form-item>
<el-form-item label="Connect timeout">
<el-input-number :value="pleromaRepo.connect_timeout" :step="1000" :min="0" size="large" @change="updateSetting($event, 'Pleroma.Repo', 'connect_timeout')"/>
<p class="expl">The timeout for establishing new connections. Defaults to 5000</p>
</el-form-item>
<el-form-item label="Prepare">
<el-select :value="pleromaRepo.prepare" clearable @change="updateSetting($event, 'Pleroma.Repo', 'prepare')">
<el-option label="named" value=":named"/>
<el-option label="unnamed" value=":unnamed"/>
</el-select>
<p class="expl">How to prepare queries, either <span class="code">:named</span> to use named queries or
<span class="code">:unnamed</span> to force unnamed queries (default: :named)</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="databaseData" :model="databaseData" :label-width="labelWidth">
<setting :setting-group="database" :data="databaseData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -136,28 +11,43 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Instance',
name: 'Database',
components: { Setting },
computed: {
...mapGetters([
'database',
'ectoRepos',
'pleromaRepo'
'settings'
]),
database() {
return this.settings.description.find(setting => setting.key === ':database')
},
databaseData() {
return this.settings.settings[':pleroma'][':database']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,252 +1,75 @@
<template>
<el-form v-if="!loading" ref="endpoint" :model="endpoint" :label-width="labelWidth">
<el-form-item label="Instrumenters">
<el-select :value="endpoint.instrumenters || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.Web.Endpoint', 'instrumenters')">
<el-option
v-for="item in instrumentersOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
</el-form-item>
<div v-if="!loading">
<el-form ref="endpointData" :model="endpointData" :label-width="labelWidth">
<setting :setting-group="endpoint" :data="endpointData"/>
</el-form>
<div class="line"/>
<el-form-item label="Compile-time configuration:"/>
<el-form-item label="Code reloader">
<el-switch :value="endpoint.code_reloader" @change="updateSetting($event, 'Pleroma.Web.Endpoint', 'code_reloader')"/>
<p class="expl">Enables code reloading functionality</p>
</el-form-item>
<el-form-item label="Debug errors">
<el-switch :value="endpoint.debug_errors" @change="updateSetting($event, 'Pleroma.Web.Endpoint', 'debug_errors')"/>
<p class="expl">Enables using <span class="code">Plug.Debugger</span> functionality for debugging failures in the application.
Recommended to be set to true only in development as it allows listing of the application source code during debugging. Defaults to false.</p>
</el-form-item>
<el-form-item label="Render errors:"/>
<el-form-item label="View">
<el-input :value="endpoint.render_errors.view" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'render_errors', 'view')"/>
</el-form-item>
<el-form-item label="Accepts">
<el-select :value="endpoint.render_errors.accepts || []" multiple filterable allow-create @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'render_errors', 'accepts')"/>
</el-form-item>
<el-form-item label="Layout">
<el-switch :value="endpoint.render_errors.layout" @change="processNestedData($event, 'Pleroma.Web.Endpoint', 'render_errors', 'layout')"/>
</el-form-item>
<el-form ref="endpointMetricsExporter" :model="endpointMetricsExporterData" :label-width="labelWidth">
<setting :setting-group="endpointMetricsExporter" :data="endpointMetricsExporterData"/>
</el-form>
<div class="line"/>
<el-form-item label="Runtime configuration:"/>
<el-form-item label="Cache static manifest">
<el-input :value="endpoint.cache_static_manifest" @input="updateSetting($event, 'Pleroma.Web.Endpoint', 'cache_static_manifest')"/>
<p class="expl">A path to a json manifest file that contains static files and their digested version.
This is typically set to <span class="code">'priv/static/cache_manifest.json'</span>
which is the file automatically generated by <span class="code">mix phx.digest</span></p>
</el-form-item>
<div class="line"/>
<el-form-item label="HTTP:"/>
<el-form-item label="Configure HTTP server">
<el-switch :value="configureHttp" @change="showServerConfig($event, 'http')"/>
</el-form-item>
<div v-if="configureHttp">
<el-form-item label="Dispatch">
<editor v-model="editorContentHttp" height="150" width="100%" lang="elixir" theme="chrome"/>
<p class="expl">You can type in Elixir code here</p>
<el-form ref="remoteIp" :model="remoteIpData" :label-width="labelWidth">
<setting :setting-group="remoteIp" :data="remoteIpData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
<el-form-item label="Port">
<el-input :value="endpointHttp.port" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'http', 'port')"/>
<p class="expl">The port to run the server. Defaults to 4000 (http) and 4040 (https).</p>
</el-form-item>
<el-form-item label="IP">
<el-input :value="endpointHttp.ip" placeholder="xxx.xxx.xxx.xx" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'http', 'ip')"/>
<p class="expl">The ip to bind the server to</p>
</el-form-item>
<el-form-item label="Reference name">
<el-input :value="endpointHttp.ref" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'http', 'ref')"/>
<p class="expl">The reference name to be used. Defaults to <span class="code">plug.HTTP</span> (http) and
<span class="code">plug.HTTPS</span> (https). This is the value that needs to be given on shutdown.</p>
</el-form-item>
<el-form-item label="Compress">
<el-switch :value="endpointHttp.compress" @change="processNestedData($event, 'Pleroma.Web.Endpoint', 'http', 'compress')"/>
<p class="expl">Cowboy will attempt to compress the response body. Defaults to false.</p>
</el-form-item>
<el-form-item label="Timeout in s">
<el-input-number :value="endpointHttp.timeout / 1000" :step="1" :min="0" size="large" @input="processNestedData($event * 1000, 'Pleroma.Web.Endpoint', 'http', 'timeout')"/>
<p class="expl">Time in s with no requests before Cowboy closes the connection. Defaults to 5 s.</p>
</el-form-item>
<div class="line"/>
</div>
<el-form-item label="HTTPS:"/>
<el-form-item label="Configure HTTPS server">
<el-switch :value="configureHttps" @change="showServerConfig($event, 'https')"/>
</el-form-item>
<div v-if="configureHttps">
<el-form-item label="Dispatch">
<editor v-model="editorContentHttps" height="150" width="100%" lang="elixir" theme="chrome"/>
<p class="expl">You can type in Elixir code here</p>
</el-form-item>
<el-form-item label="Port">
<el-input :value="endpointHttps.port" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'https', 'port')"/>
<p class="expl">The port to run the server. Defaults to 4000 (http) and 4040 (https).</p>
</el-form-item>
<el-form-item label="IP">
<el-input :value="endpointHttps.ip" placeholder="xxx.xxx.xxx.xx" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'https', 'ip')"/>
<p class="expl">The ip to bind the server to</p>
</el-form-item>
<el-form-item label="Reference name">
<el-input :value="endpointHttps.ref" @input="updateSetting($event, 'Pleroma.Web.Endpoint', 'https', 'ref')"/>
<p class="expl">The reference name to be used. Defaults to <span class="code">plug.HTTP</span> (http) and
<span class="code">plug.HTTPS</span> (https). This is the value that needs to be given on shutdown.</p>
</el-form-item>
<el-form-item label="Compress">
<el-switch :value="endpointHttps.compress" @change="processNestedData($event, 'Pleroma.Web.Endpoint', 'https', 'compress')"/>
<p class="expl">Cowboy will attempt to compress the response body. Defaults to false.</p>
</el-form-item>
<el-form-item label="Timeout in s">
<el-input-number :value="endpointHttps.timeout / 1000" :step="1" :min="0" size="large" @input="processNestedData($event * 1000, 'Pleroma.Web.Endpoint', 'https', 'timeout')"/>
<p class="expl">Time in s with no requests before Cowboy closes the connection. Defaults to 5 s.</p>
</el-form-item>
<div class="line"/>
</div>
<el-form-item label="Secret key base">
<el-input :value="endpoint.secret_key_base" @input="updateSetting($event, 'Pleroma.Web.Endpoint', 'secret_key_base')"/>
<p class="expl">A secret key used as a base to generate secrets for encrypting and signing data. For example, cookies and tokens are signed by default, but they may also be encrypted if desired. Defaults to nil as it must be set per application</p>
</el-form-item>
<el-form-item label="Server">
<el-switch :value="endpoint.server" @change="updateSetting($event, 'Pleroma.Web.Endpoint', 'server')"/>
<p class="expl">When true, starts the web server when the endpoint supervision tree starts. Defaults to false. The <span class="code">mix phx.server</span> task automatically sets this to true.</p>
</el-form-item>
<div class="line"/>
<el-form-item label="URL:"/>
<el-form-item label="Host">
<el-input :value="endpoint.url.host" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'url', 'host')"/>
<p class="expl">The host without the scheme and a post (e.g <span class="code">example.com</span>, not <span class="code">https://example.com:2020</span>)</p>
</el-form-item>
<el-form-item label="Scheme">
<el-input :value="endpoint.url.scheme" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'url', 'scheme')"/>
<p class="expl">e.g http, https</p>
</el-form-item>
<el-form-item label="Port">
<el-input :value="endpoint.url.port" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'url', 'port')"/>
</el-form-item>
<el-form-item label="Path">
<el-input :value="endpoint.url.path" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'url', 'path')"/>
</el-form-item>
<div class="line"/>
<el-form-item label="Protocol">
<el-input :value="endpoint.protocol" @input="updateSetting($event, 'Pleroma.Web.Endpoint', 'protocol')"/>
</el-form-item>
<el-form-item label="Signing salt">
<el-input :value="endpoint.signing_salt" @input="updateSetting($event, 'Pleroma.Web.Endpoint', 'signing_salt')"/>
</el-form-item>
<div class="line"/>
<el-form-item label="PubSub:"/>
<el-form-item label="Name">
<el-input :value="endpoint.pubsub.name" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'pubsub', 'name')"/>
</el-form-item>
<el-form-item label="Adapter">
<el-input :value="endpoint.pubsub.adapter" @input="processNestedData($event, 'Pleroma.Web.Endpoint', 'pubsub', 'adapter')"/>
</el-form-item>
<div class="line"/>
<el-form-item label="Secure cookie flag">
<el-switch :value="endpoint.secure_cookie_flag" @change="updateSetting($event, 'Pleroma.Web.Endpoint', 'secure_cookie_flag')"/>
</el-form-item>
<el-form-item label="Extra cookie attrs">
<el-select :value="endpoint.extra_cookie_attrs || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.Web.Endpoint', 'extra_cookie_attrs')">
<el-option
v-for="item in extraCookieAttrsOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
</el-form-item>
<div class="line"/>
<el-form-item class="options-paragraph-container">
<p class="options-paragraph">Only common options are listed here. You can add more (all configuration options can be viewed
<a
href="https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#module-dynamic-configuration"
rel="nofollow noreferrer noopener"
target="_blank">here</a>)
</p>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import AceEditor from 'vue2-ace-editor'
import 'brace/mode/elixir'
import 'default-passive-events'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Endpoint',
components: {
editor: AceEditor
Setting
},
computed: {
...mapGetters([
'endpoint'
'settings'
]),
editorContentHttp: {
get: function() {
return this.endpoint.http.dispatch ? this.endpoint.http.dispatch[0] : ''
},
set: function(value) {
this.processNestedData([value], 'Pleroma.Web.Endpoint', 'http', 'dispatch')
}
endpoint() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Endpoint')
},
editorContentHttps: {
get: function() {
return this.endpoint.https.dispatch ? this.endpoint.https.dispatch[0] : ''
},
set: function(value) {
this.processNestedData([value], 'Pleroma.Web.Endpoint', 'https', 'dispatch')
}
endpointData() {
return this.settings.settings[':pleroma']['Pleroma.Web.Endpoint']
},
configureHttp() {
return !this.endpoint.http === false
endpointMetricsExporter() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Endpoint.MetricsExporter')
},
configureHttps() {
return !this.endpoint.https === false
},
endpointHttp() {
return this.endpoint.http || {}
},
endpointHttps() {
return this.endpoint.https || {}
},
extraCookieAttrsOptions() {
return options.extraCookieAttrsOptions
},
instrumentersOptions() {
return options.instrumentersOptions
endpointMetricsExporterData() {
return this.settings.settings[':prometheus']['Pleroma.Web.Endpoint.MetricsExporter']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.$store.state.settings.loading
return this.settings.loading
},
remoteIp() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Plugs.RemoteIp')
},
remoteIpData() {
return this.settings.settings[':pleroma']['Pleroma.Plugs.RemoteIp']
}
},
methods: {
processNestedData(value, tab, inputName, childName) {
const updatedValue = { ...this.$store.state.settings.settings[tab][inputName], ...{ [childName]: value }}
this.updateSetting(updatedValue, tab, inputName)
},
showServerConfig(value, protocol) {
if (value) {
this.updateSetting({}, 'Pleroma.Web.Endpoint', protocol)
} else {
this.updateSetting(value, 'Pleroma.Web.Endpoint', protocol)
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,45 +1,7 @@
<template>
<div>
<el-form :label-width="labelWidth">
<el-form-item>
<p class="expl">Before enabling this you must:
<ol class="esshd-list">
<li>Add <span class="code">:esshd</span> to <span class="code">mix.exs</span> as one of the
<span class="code">extra_applications</span>
</li>
<li>Generate host keys in your
<span class="code">priv</span> dir with
<span class="code">ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key</span>
</li>
</ol>
</p>
</el-form-item>
</el-form>
<el-form ref="enabled" :model="enabled" :label-width="labelWidth">
<el-form-item label="Enabled">
<el-switch :value="enabled.value" @change="updateSetting($event, 'enabled', 'value')"/>
</el-form-item>
</el-form>
<el-form ref="privDir" :model="privDir" :label-width="labelWidth">
<el-form-item label="Priv dir">
<el-input :value="privDir.value" @input="updateSetting($event, 'priv_dir', 'value')"/>
<p class="expl">You can input relative path here</p>
</el-form-item>
</el-form>
<el-form ref="handler" :model="handler" :label-width="labelWidth">
<el-form-item label="Handler">
<el-input :value="handler.value" @input="updateSetting($event, 'handler', 'value')"/>
</el-form-item>
</el-form>
<el-form ref="port" :model="port" :label-width="labelWidth">
<el-form-item label="Port">
<el-input :value="port.value" @input="updateSetting($event, 'port', 'value')"/>
</el-form-item>
</el-form>
<el-form ref="passwordAuthenticator" :model="passwordAuthenticator" :label-width="labelWidth">
<el-form-item label="Password authenticator">
<el-input :value="passwordAuthenticator.value" @input="updateSetting($event, 'password_authenticator', 'value')"/>
</el-form-item>
<div v-if="!loading">
<el-form ref="esshdData" :model="esshdData" :label-width="labelWidth">
<setting :setting-group="esshd" :data="esshdData"/>
<el-form-item>
<p class="expl">Feel free to adjust the priv_dir and port number.
Then you will have to create the key for the keys (in the example <span class="code">priv/ssh_keys</span>) and create the host keys with
@ -55,23 +17,31 @@
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
import Setting from './Setting'
export default {
name: 'Instance',
name: 'Esshd',
components: { Setting },
computed: {
...mapGetters([
'enabled',
'handler',
'passwordAuthenticator',
'port',
'privDir'
'settings'
]),
esshd() {
return this.settings.description.find(setting => setting.group === ':esshd')
},
esshdData() {
return this.settings.settings[':esshd']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
}
},
methods: {
@ -81,8 +51,16 @@ export default {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,314 +1,30 @@
<template>
<div>
<el-form ref="frontend" :model="frontend" :label-width="labelWidth">
<div v-if="!loading">
<el-form ref="frontendData" :model="frontendData" :label-width="labelWidth">
<el-form-item>
<p class="expl">This form can be used to configure a keyword list that keeps the configuration data for any kind of frontend.
By default, settings for <span class="code">pleroma_fe</span> and <span class="code">masto_fe</span> are configured.
If you want to add your own configuration your settings need to be complete as they will override the defaults.</p>
</el-form-item>
<el-form-item label="Pleroma FE:"/>
<el-form-item label="Theme">
<el-select :value="frontend.pleroma_fe.theme" clearable @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'theme')">
<el-option
v-for="item in themeOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<p class="expl">Which theme to use</p>
</el-form-item>
<el-form-item label="Background">
<el-input :value="frontend.pleroma_fe.background" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'background')"/>
<div class="upload-container">
<p class="text">or</p>
<el-upload
:http-request="sendBackgroundPleroma"
:multiple="false"
:show-file-list="false"
action="/api/v1/media">
<el-button size="small" type="primary">Click to upload</el-button>
</el-upload>
</div>
<p class="expl">URL of the background, unless viewing a user profile with a background that is set</p>
</el-form-item>
<el-form-item label="Logo">
<el-input :value="frontend.pleroma_fe.logo" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'logo')"/>
<div class="upload-container">
<p class="text">or</p>
<el-upload
:http-request="sendLogoPleroma"
:multiple="false"
:show-file-list="false"
action="/api/v1/media">
<el-button size="small" type="primary">Click to upload</el-button>
</el-upload>
</div>
<p class="expl">URL of the logo</p>
</el-form-item>
<el-form-item label="Logo mask">
<el-switch :value="frontend.pleroma_fe.logoMask" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'logoMask')"/>
<p class="expl">Whether to use only the logo's shape as a mask (true) or as a regular image (false)</p>
</el-form-item>
<el-form-item label="Logo margin (em)">
<el-input-number :value="frontend.pleroma_fe.logoMargin" :step="0.1" :min="0" size="large" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'logoMargin')"/>
<p class="expl">What margin to use around the logo</p>
</el-form-item>
<el-form-item label="Redirect URL">
<el-input :value="frontend.pleroma_fe.redirectRootNoLogin" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'redirectRootNoLogin')"/>
<p class="expl">Relative URL which indicates where to redirect when a user is logged in</p>
</el-form-item>
<el-form-item label="Redirect for anonymous user">
<el-input :value="frontend.pleroma_fe.redirectRootLogin" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'redirectRootLogin')"/>
<p class="expl">Relative URL which indicates where to redirect when a user isnt logged in</p>
</el-form-item>
<el-form-item label="Show instance panel">
<el-switch :value="frontend.pleroma_fe.showInstanceSpecificPanel" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'showInstanceSpecificPanel')"/>
<p class="expl">Whenether to show the instances specific panel</p>
</el-form-item>
<el-form-item label="Scope options enabled">
<el-switch :value="frontend.pleroma_fe.scopeOptionsEnabled" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'scopeOptionsEnabled')"/>
</el-form-item>
<el-form-item label="Formatting options enabled">
<el-switch :value="frontend.pleroma_fe.formattingOptionsEnabled" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'formattingOptionsEnabled')"/>
</el-form-item>
<el-form-item label="Collapse msg with subject">
<el-switch :value="frontend.pleroma_fe.collapseMessageWithSubject" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'collapseMessageWithSubject')"/>
<p class="expl">When a message has a subject (aka Content Warning), collapse it by default</p>
</el-form-item>
<el-form-item label="Scope copy">
<el-switch :value="frontend.pleroma_fe.scopeCopy" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'scopeCopy')"/>
<p class="expl">Copy the scope <span class="code">(private/unlisted/public)</span> in replies to posts by default</p>
</el-form-item>
<el-form-item label="Subject line behavior">
<el-select :value="frontend.pleroma_fe.subjectLineBehavior" clearable @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'subjectLineBehavior')">
<el-option label="Email" value="email">Email / Copy and preprend re:, as in email</el-option>
<el-option label="Masto" value="masto">Masto / Copy verbatim, as in Mastodon</el-option>
<el-option label="Noop" value="noop">Noop / Don't copy the subject</el-option>
</el-select>
<p class="expl">Allows changing the default behaviour of subject lines in replies</p>
</el-form-item>
<el-form-item label="Post content type">
<el-input :value="frontend.pleroma_fe.postContentType" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'postContentType')"/>
</el-form-item>
<el-form-item label="Always show subject input">
<el-switch :value="frontend.pleroma_fe.alwaysShowSubjectInput" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'alwaysShowSubjectInput')"/>
<p class="expl">When set to false, auto-hide the subject field when it's empty</p>
</el-form-item>
<el-form-item label="Hide post statistics">
<el-switch :value="frontend.pleroma_fe.hidePostStats" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'hidePostStats')"/>
<p class="expl">Hide notices statistics(repeats, favorites, )</p>
</el-form-item>
<el-form-item label="Hide user statistics">
<el-switch :value="frontend.pleroma_fe.hideUserStats" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'hideUserStats')"/>
<p class="expl">Hide profile statistics(posts, posts per day, followers, followings, )</p>
</el-form-item>
<el-form-item label="Login method">
<el-input :value="frontend.pleroma_fe.loginMethod" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'loginMethod')"/>
</el-form-item>
<el-form-item label="Web push notifications">
<el-switch :value="frontend.pleroma_fe.webPushNotifications" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'webPushNotifications')"/>
</el-form-item>
<el-form-item label="No attachment links">
<el-switch :value="frontend.pleroma_fe.noAttachmentLinks" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'noAttachmentLinks')"/>
</el-form-item>
<el-form-item label="NSFW Censor image">
<el-input :value="frontend.pleroma_fe.nsfwCensorImage" @input="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'nsfwCensorImage')"/>
</el-form-item>
<el-form-item label="Show features panel">
<el-switch :value="frontend.pleroma_fe.showFeaturesPanel" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'showFeaturesPanel')"/>
</el-form-item>
<el-form-item label="Minimal scopes mode">
<el-switch :value="frontend.pleroma_fe.minimalScopesMode" @change="processNestedData($event, 'frontend_configurations', 'pleroma_fe', 'minimalScopesMode')"/>
</el-form-item>
<div class="line"/>
<el-form-item label="Masto FE:"/>
<el-form-item label="Theme">
<el-select :value="frontend.masto_fe.theme" clearable @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'theme')">
<el-option
v-for="item in themeOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<p class="expl">Which theme to use</p>
</el-form-item>
<el-form-item label="Background">
<el-input :value="frontend.masto_fe.background" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'background')"/>
<div class="upload-container">
<p class="text">or</p>
<el-upload
:http-request="sendBackgroundMasto"
:multiple="false"
:show-file-list="false"
action="/api/v1/media">
<el-button size="small" type="primary">Click to upload</el-button>
</el-upload>
</div>
<p class="expl">URL of the background, unless viewing a user profile with a background that is set</p>
</el-form-item>
<el-form-item label="Logo">
<el-input :value="frontend.masto_fe.logo" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'logo')"/>
<div class="upload-container">
<p class="text">or</p>
<el-upload
:http-request="sendLogoMasto"
:multiple="false"
:show-file-list="false"
action="/api/v1/media">
<el-button size="small" type="primary">Click to upload</el-button>
</el-upload>
</div>
<p class="expl">URL of the logo</p>
</el-form-item>
<el-form-item label="Logo mask">
<el-switch :value="frontend.masto_fe.logoMask" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'logoMask')"/>
<p class="expl">Whether to use only the logo's shape as a mask (true) or as a regular image (false)</p>
</el-form-item>
<el-form-item label="Logo margin (em)">
<el-input-number :value="frontend.masto_fe.logoMargin" :step="0.1" :min="0" size="large" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'logoMargin')"/>
<p class="expl">What margin to use around the logo</p>
</el-form-item>
<el-form-item label="Redirect URL">
<el-input :value="frontend.masto_fe.redirectRootNoLogin" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'redirectRootNoLogin')"/>
<p class="expl">Relative URL which indicates where to redirect when a user is logged in</p>
</el-form-item>
<el-form-item label="Redirect for anonymous user">
<el-input :value="frontend.masto_fe.redirectRootLogin" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'redirectRootLogin')"/>
<p class="expl">Relative URL which indicates where to redirect when a user isnt logged in</p>
</el-form-item>
<el-form-item label="Show instance panel">
<el-switch :value="frontend.masto_fe.showInstanceSpecificPanel" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'showInstanceSpecificPanel')"/>
<p class="expl">Whenether to show the instances specific panel</p>
</el-form-item>
<el-form-item label="Scope options enabled">
<el-switch :value="frontend.masto_fe.scopeOptionsEnabled" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'scopeOptionsEnabled')"/>
</el-form-item>
<el-form-item label="Formatting options enabled">
<el-switch :value="frontend.masto_fe.formattingOptionsEnabled" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'formattingOptionsEnabled')"/>
</el-form-item>
<el-form-item label="Collapse msg with subjects">
<el-switch :value="frontend.masto_fe.collapseMessageWithSubject" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'collapseMessageWithSubject')"/>
<p class="expl">When a message has a subject (aka Content Warning), collapse it by default</p>
</el-form-item>
<el-form-item label="Scope copy">
<el-switch :value="frontend.masto_fe.scopeCopy" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'scopeCopy')"/>
<p class="expl">Copy the scope <span class="code">(private/unlisted/public)</span> in replies to posts by default</p>
</el-form-item>
<el-form-item label="Subject line behavior">
<el-select :value="frontend.masto_fe.subjectLineBehavior" clearable @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'subjectLineBehavior')">
<el-option label="Email" value="email">Email / Copy and preprend re:, as in email</el-option>
<el-option label="Masto" value="masto">Masto / Copy verbatim, as in Mastodon</el-option>
<el-option label="Noop" value="noop">Noop / Don't copy the subject</el-option>
</el-select>
<p class="expl">Allows changing the default behaviour of subject lines in replies</p>
</el-form-item>
<el-form-item label="Post content type">
<el-input :value="frontend.masto_fe.postContentType" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'postContentType')"/>
</el-form-item>
<el-form-item label="Always show subject input">
<el-switch :value="frontend.masto_fe.alwaysShowSubjectInput" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'alwaysShowSubjectInput')"/>
<p class="expl">When set to false, auto-hide the subject field when it's empty</p>
</el-form-item>
<el-form-item label="Hide post statistics">
<el-switch :value="frontend.masto_fe.hidePostStats" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'hidePostStats')"/>
<p class="expl">Hide notices statistics(repeats, favorites, )</p>
</el-form-item>
<el-form-item label="Hide user statistics">
<el-switch :value="frontend.masto_fe.hideUserStats" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'hideUserStats')"/>
<p class="expl">Hide profile statistics(posts, posts per day, followers, followings, )</p>
</el-form-item>
<el-form-item label="Login method">
<el-input :value="frontend.masto_fe.loginMethod" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'loginMethod')"/>
</el-form-item>
<el-form-item label="Web push notifications">
<el-switch :value="frontend.masto_fe.webPushNotifications" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'webPushNotifications')"/>
</el-form-item>
<el-form-item label="No attachment links">
<el-switch :value="frontend.masto_fe.noAttachmentLinks" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'noAttachmentLinks')"/>
</el-form-item>
<el-form-item label="NSFW Censor image">
<el-input :value="frontend.masto_fe.nsfwCensorImage" @input="processNestedData($event, 'frontend_configurations', 'masto_fe', 'nsfwCensorImage')"/>
</el-form-item>
<el-form-item label="Show features panel">
<el-switch :value="frontend.masto_fe.showFeaturesPanel" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'showFeaturesPanel')"/>
</el-form-item>
<el-form-item label="Minimal scopes mode">
<el-switch :value="frontend.masto_fe.minimalScopesMode" @change="processNestedData($event, 'frontend_configurations', 'masto_fe', 'minimalScopesMode')"/>
</el-form-item>
<setting :setting-group="frontend" :data="frontendData"/>
</el-form>
<div class="line"/>
<el-form ref="assets" :model="assets" :label-width="labelWidth">
<el-form ref="assetsData" :model="assetsData" :label-width="labelWidth">
<el-form-item label="Assets:"/>
<el-form-item label="Default mascot">
<el-select :value="assets.default_mascot" clearable @change="updateSetting($event, 'assets', 'default_mascot')"/>
<p class="expl">An element from mascots - This will be used as the default mascot on MastoFE
(default: <span class="code">:pleroma_fox_tan</span>)</p>
</el-form-item>
<el-form-item label="Mascots">
<div v-for="([name, url, mimeType], index) in mascots" :key="index" class="mascot-container">
<div class="mascot-name-container">
<el-input :value="name" placeholder="Name" class="mascot-name-input" @input="parseMascots($event, 'name', index)"/>
<el-button icon="el-icon-minus" circle @click="deleteMascotsRow(index, 'emoji', 'groups')"/>
</div>
<el-input :value="url" placeholder="URL" class="mascot-input" @input="parseMascots($event, 'url', index)"/>
<el-input :value="mimeType" placeholder="Mime type" class="mascot-input" @input="parseMascots($event, 'mimeType', index)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addRowToMascots"/>
</el-form-item>
<setting :setting-group="assets" :data="assetsData"/>
</el-form>
<div class="line"/>
<el-form ref="emoji" :model="emoji" :label-width="labelWidth">
<el-form ref="emojiData" :model="emojiData" :label-width="labelWidth">
<el-form-item label="Emoji:"/>
<el-form-item label="Location of emoji files">
<el-select :value="emoji.shortcode_globs || []" multiple filterable allow-create @change="updateSetting($event, 'emoji', 'shortcode_globs')">
<el-option label="/emoji/custom/**/*.png" value="/emoji/custom/**/*.png"/>
</el-select>
<p class="expl">Location of custom emoji files. <span class="code">*</span> can be used as a wildcard.</p>
</el-form-item>
<el-form-item label="Pack extensions">
<el-select :value="emoji.pack_extensions || []" multiple filterable allow-create @change="updateSetting($event, 'emoji', 'pack_extensions')"/>
<p class="expl">A list of file extensions for emojis, when no <span class="code">emoji.txt</span> for a pack is present. </p>
</el-form-item>
<el-form-item label="Group">
<div v-for="([key, value], index) in groups" :key="index" class="setting-input">
<el-input :value="key" placeholder="key" class="name-input" @input="parseGroups($event, 'key', index)"/> :
<el-select :value="value" multiple filterable allow-create class="value-input" @change="parseGroups($event, 'value', index)"/>
<el-button icon="el-icon-minus" circle @click="deleteGroupsRow(index)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addRowToGroups"/>
</el-form-item>
<el-form-item label="Location of JSON-manifest">
<el-input :value="emoji.default_manifest" @input="updateSetting($event, 'emoji', 'default_manifest')"/>
<p class="expl">Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays).</p>
</el-form-item>
<setting :setting-group="emoji" :data="emojiData"/>
</el-form>
<div class="line"/>
<el-form ref="chat" :model="chat" :label-width="labelWidth">
<el-form-item label="Chat enabled">
<el-switch :value="chat.enabled" @input="updateSetting($event, 'chat', 'enabled')"/>
</el-form-item>
<el-form ref="chatData" :model="chatData" :label-width="labelWidth">
<setting :setting-group="chat" :data="chatData"/>
</el-form>
<div class="line"/>
<el-form ref="markup" :model="markup" :label-width="labelWidth">
<el-form ref="markupData" :model="markupData" :label-width="labelWidth">
<el-form-item label="Markup settings:"/>
<el-form-item label="Allow inline images">
<el-switch :value="markup.allow_inline_images" @input="updateSetting($event, 'markup', 'allow_inline_images')"/>
</el-form-item>
<el-form-item label="Allow headings">
<el-switch :value="markup.allow_headings" @input="updateSetting($event, 'markup', 'allow_headings')"/>
</el-form-item>
<el-form-item label="Allow tables">
<el-switch :value="markup.allow_tables" @input="updateSetting($event, 'markup', 'allow_tables')"/>
</el-form-item>
<el-form-item label="Allow fonts">
<el-switch :value="markup.allow_fonts" @input="updateSetting($event, 'markup', 'allow_fonts')"/>
</el-form-item>
<el-form-item label="Scrub policy">
<el-select :value="markup.scrub_policy || []" multiple filterable allow-create @change="updateSetting($event, 'markup', 'scrub_policy')">
<el-option label="Pleroma.HTML.Transform.MediaProxy" value="Pleroma.HTML.Transform.MediaProxy"/>
<el-option label="Pleroma.HTML.Scrubber.Default" value="Pleroma.HTML.Scrubber.Default"/>
</el-select>
</el-form-item>
<setting :setting-group="markup" :data="markupData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -318,108 +34,67 @@
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Frontend',
components: { Setting },
computed: {
...mapGetters([
'assets',
'frontend',
'emoji',
'chat',
'markup'
'settings'
]),
groups() {
return Object.keys(this.emoji.groups).map(key => [key, this.emoji.groups[key]])
assets() {
return this.settings.description.find(setting => setting.key === ':assets')
},
mascots() {
return Object.keys(this.assets.mascots)
.map(mascotName =>
[mascotName, this.assets.mascots[mascotName].url, this.assets.mascots[mascotName].mime_type])
assetsData() {
return this.settings.settings[':pleroma'][':assets']
},
themeOptions() {
return options.themeOptions
chat() {
return this.settings.description.find(setting => setting.key === ':chat')
},
chatData() {
return this.settings.settings[':pleroma'][':chat']
},
emoji() {
return this.settings.description.find(setting => setting.key === ':emoji')
},
emojiData() {
return this.settings.settings[':pleroma'][':emoji']
},
frontend() {
return this.settings.description.find(setting => setting.key === ':frontend_configurations')
},
frontendData() {
return this.settings.settings[':pleroma'][':frontend_configurations']
},
markup() {
return this.settings.description.find(setting => setting.key === ':markup')
},
markupData() {
return this.settings.settings[':pleroma'][':markup']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
}
},
methods: {
addRowToGroups() {
const updatedValue = this.groups.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting({ ...updatedValue, '': [] }, 'emoji', 'groups')
},
addRowToMascots() {
const updatedValue = this.mascots.reduce((acc, el, i) => {
return { ...acc, [el[0]]: { url: el[1], mime_type: el[2] }}
}, {})
this.updateSetting({ ...updatedValue, '': { url: '', mime_type: '' }}, 'assets', 'mascots')
},
deleteGroupsRow(index) {
const filteredValues = this.groups.filter((el, i) => index !== i)
const updatedValue = filteredValues.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'emoji', 'groups')
},
deleteMascotsRow(index) {
const filteredValues = this.mascots.filter((el, i) => index !== i)
const updatedValue = filteredValues.reduce((acc, el, i) => {
return { ...acc, [el[0]]: { url: el[1], mime_type: el[2] }}
}, {})
this.updateSetting(updatedValue, 'assets', 'mascots')
},
parseGroups(value, inputType, index) {
const updatedValue = this.groups.reduce((acc, el, i) => {
if (index === i) {
return inputType === 'key' ? { ...acc, [value]: el[1] } : { ...acc, [el[0]]: value }
}
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'emoji', 'groups')
},
parseMascots(value, inputType, index) {
const updatedValue = this.mascots.reduce((acc, el, i) => {
if (index === i) {
if (inputType === 'name') {
return { ...acc, [value]: { url: el[1], mime_type: el[2] }}
} else if (inputType === 'url') {
return { ...acc, [el[0]]: { url: value, mime_type: el[2] }}
} else {
return { ...acc, [el[0]]: { url: el[1], mime_type: value }}
}
}
return { ...acc, [el[0]]: { url: el[1], mime_type: el[2] }}
}, {})
this.updateSetting(updatedValue, 'assets', 'mascots')
},
processNestedData(value, tab, inputName, childName) {
const updatedValue = { ...this.$store.state.settings.settings[tab][inputName], ...{ [childName]: value }}
this.updateSetting(updatedValue, tab, inputName)
},
sendBackgroundMasto({ file }) {
this.$store.dispatch('UploadMedia', { file, tab: 'frontend_configurations', inputName: 'masto_fe', childName: 'background' })
},
sendBackgroundPleroma({ file }) {
this.$store.dispatch('UploadMedia', { file, tab: 'frontend_configurations', inputName: 'pleroma_fe', childName: 'background' })
},
sendLogoMasto({ file }) {
this.$store.dispatch('UploadMedia', { file, tab: 'frontend_configurations', inputName: 'masto_fe', childName: 'logo' })
},
sendLogoPleroma({ file }) {
this.$store.dispatch('UploadMedia', { file, tab: 'frontend_configurations', inputName: 'pleroma_fe', childName: 'logo' })
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,21 +1,6 @@
<template>
<el-form ref="gopher" :model="gopher" :label-width="labelWidth">
<el-form-item label="Enabled">
<el-switch :value="gopher.enabled" @change="updateSetting($event, 'gopher', 'enabled')"/>
<p class="expl">Enables the gopher interface</p>
</el-form-item>
<el-form-item label="IP address">
<el-input :value="gopher.ip" placeholder="xxx.xxx.xxx.xx" @input="updateSetting($event, 'gopher', 'ip')"/>
<p class="expl">Enables the gopher interface</p>
</el-form-item>
<el-form-item label="Port">
<el-input :value="gopher.port" @input="updateSetting($event, 'gopher', 'port')"/>
<p class="expl">Port to bind to</p>
</el-form-item>
<el-form-item label="Dst port">
<el-input :value="gopher.dstport" @input="updateSetting($event, 'gopher', 'dstport')"/>
<p class="expl">Port advertised in urls (optional, defaults to port)</p>
</el-form-item>
<el-form v-if="!loading" ref="gopher" :model="gopherData" :label-width="labelWidth">
<setting :setting-group="gopher" :data="gopherData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -24,26 +9,43 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Gopher',
components: { Setting },
computed: {
...mapGetters([
'gopher'
'settings'
]),
gopher() {
return this.settings.description.find(setting => setting.key === ':gopher')
},
gopherData() {
return this.settings.settings[':pleroma'][':gopher']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,126 +1,23 @@
<template>
<div>
<el-form ref="http" :model="http" :label-width="labelWidth">
<el-form-item label="HTTP settings:"/>
<el-form-item label="Proxy url">
<el-input :value="http.proxy_url" @input="updateSetting($event, 'http', 'proxy_url')"/>
</el-form-item>
<el-form-item label="Send user agent">
<el-switch :value="http.send_user_agent" @change="updateSetting($event, 'http', 'send_user_agent')"/>
</el-form-item>
<el-form-item label="Adapter:"/>
<el-form-item label="Versions">
<el-select :value="http.adapter.versions || []" multiple filterable allow-create @change="processNestedData($event, 'http', 'adapter', 'versions')">
<el-option value=":tlsv1"/>
<el-option value=":'tlsv1.1'"/>
<el-option value=":'tlsv1.2'"/>
</el-select>
</el-form-item>
<div v-if="!loading">
<el-form ref="httpData" :model="httpData" :label-width="labelWidth">
<setting :setting-group="http" :data="httpData"/>
</el-form>
<div class="line"/>
<el-form ref="corsPlugMaxAge" :model="corsPlugMaxAge" :label-width="labelWidth">
<el-form ref="corsPlugData" :model="corsPlugData" :label-width="labelWidth">
<el-form-item label="Cors plug config:"/>
<el-form-item label="Max age (days)">
<el-input-number :value="corsPlugMaxAge.value / 86400" :step="1" :min="0" size="large" @change="updateSetting($event * 86400, 'max_age', 'value')"/>
</el-form-item>
</el-form>
<el-form ref="corsPlugMethods" :model="corsPlugMethods" :label-width="labelWidth">
<el-form-item label="Methods">
<el-select :value="corsPlugMethods.value || []" multiple filterable allow-create @change="updateSetting($event, 'methods', 'value')">
<el-option value="POST"/>
<el-option value="PUT"/>
<el-option value="DELETE"/>
<el-option value="GET"/>
<el-option value="PATCH"/>
<el-option value="OPTIONS"/>
</el-select>
</el-form-item>
</el-form>
<el-form ref="corsPlugExpose" :model="corsPlugExpose" :label-width="labelWidth">
<el-form-item label="Expose">
<el-select :value="corsPlugExpose.value || []" multiple filterable allow-create @change="updateSetting($event, 'expose', 'value')">
<el-option value="Link"/>
<el-option value="X-RateLimit-Reset"/>
<el-option value="X-RateLimit-Limit"/>
<el-option value="X-RateLimit-Remaining"/>
<el-option value="X-Request-Id"/>
<el-option value="Idempotency-Key"/>
</el-select>
</el-form-item>
</el-form>
<el-form ref="corsPlugCredentials" :model="corsPlugCredentials" :label-width="labelWidth">
<el-form-item label="Credentials">
<el-switch :value="corsPlugCredentials.value" @change="updateSetting($event, 'credentials', 'value')"/>
</el-form-item>
</el-form>
<el-form ref="corsPlugHeaders" :model="corsPlugHeaders" :label-width="labelWidth">
<el-form-item label="Headers">
<el-select :value="corsPlugHeaders.value || []" multiple filterable allow-create @change="updateSetting($event, 'headers', 'value')">
<el-option value="Authorization"/>
<el-option value="Content-Type"/>
<el-option value="Idempotency-Key"/>
</el-select>
</el-form-item>
<setting :setting-group="corsPlug" :data="corsPlugData"/>
</el-form>
<div class="line"/>
<el-form ref="httpSecurity" :model="httpSecurity" :label-width="labelWidth">
<el-form-item label="HTTP security:"/>
<el-form-item label="Security policy">
<el-switch :value="httpSecurity.enabled" @change="updateSetting($event, 'http_security', 'enabled')"/>
<p class="expl">Whether the managed content security policy is enabled</p>
</el-form-item>
<el-form-item label="STS">
<el-switch :value="httpSecurity.sts" @change="updateSetting($event, 'http_security', 'sts')"/>
<p class="expl">Whether to additionally send a <span class="code">Strict-Transport-Security header</span></p>
</el-form-item>
<el-form-item label="STS max age (days)">
<el-input-number :value="httpSecurity.sts_max_age / 86400" :step="1" :min="0" size="large" @change="updateSetting($event * 86400, 'http_security', 'sts_max_age')"/>
<p class="expl">The maximum age for the <span class="code">Strict-Transport-Security</span> header if sent</p>
</el-form-item>
<el-form-item label="CT max age (days)">
<el-input-number :value="httpSecurity.ct_max_age / 86400" :step="1" :min="0" size="large" @change="updateSetting($event * 86400, 'http_security', 'ct_max_age')"/>
<p class="expl">The maximum age for the <span class="code">Expect-CT</span> header if sent</p>
</el-form-item>
<el-form-item label="Referrer policy">
<el-select :value="httpSecurity.referrer_policy" clearable @change="updateSetting($event, 'http_security', 'referrer_policy')">
<el-option label="same-origin" value="same-origin"/>
<el-option label="no-referrer" value="no-referrer"/>
</el-select>
<p class="expl">The referrer policy to use</p>
</el-form-item>
<el-form-item label="Report URI">
<el-input :value="httpSecurity.report_uri" @input="updateSetting($event, 'http_security', 'report_uri')"/>
<p class="expl">Adds the specified url to <span class="code">report-uri</span> and <span class="code">report-to</span> group in CSP header</p>
</el-form-item>
<el-form ref="httpSignatures" :model="httpSignaturesData" :label-width="labelWidth">
<setting :setting-group="httpSignatures" :data="httpSignaturesData"/>
</el-form>
<el-form ref="hackneyPools" :model="hackneyPools" :label-width="labelWidth">
<div class="line"/>
<el-form-item label="Hackney pools:"/>
<el-form-item label="Federation:"/>
<el-form-item label="Max connections">
<el-input-number :value="hackneyPools.federation.max_connections" :step="1" :min="0" size="large" @change="processNestedData($event, 'hackney_pools', 'federation', 'max_connections')"/>
<p class="expl">You may want this pool <span class="code">max_connections</span> to be at least equal to the number of federator jobs + retry queue jobs.</p>
</el-form-item>
<el-form-item label="Timeout (s)">
<el-input-number :value="hackneyPools.federation.timeout / 1000" :step="10" :min="0" size="large" @change="processNestedData($event * 1000, 'hackney_pools', 'federation', 'timeout')"/>
<p class="expl">For the federation jobs</p>
</el-form-item>
<el-form-item label="Media:"/>
<el-form-item label="Max connections">
<el-input-number :value="hackneyPools.media.max_connections" :step="1" :min="0" size="large" @change="processNestedData($event, 'hackney_pools', 'media', 'max_connections')"/>
</el-form-item>
<el-form-item label="Timeout (s)">
<el-input-number :value="hackneyPools.media.timeout / 1000" :step="10" :min="0" size="large" @change="processNestedData($event * 1000, 'hackney_pools', 'media', 'timeout')"/>
<p class="expl">For rich media, media proxy</p>
</el-form-item>
<el-form-item label="Upload:"/>
<el-form-item label="Max connections">
<el-input-number :value="hackneyPools.upload.max_connections" :step="1" :min="0" size="large" @change="processNestedData($event, 'hackney_pools', 'upload', 'max_connections')"/>
</el-form-item>
<el-form-item label="Timeout (s)">
<el-input-number :value="hackneyPools.upload.timeout / 1000" :step="10" :min="0" size="large" @change="processNestedData($event * 1000, 'hackney_pools', 'upload', 'timeout')"/>
<p class="expl">For uploaded media (if using a remote uploader and <span class="code">proxy_remote: true</span>)</p>
</el-form-item>
<div class="line"/>
<el-form ref="httpSecurityData" :model="httpSecurityData" :label-width="labelWidth">
<setting :setting-group="httpSecurity" :data="httpSecurityData"/>
</el-form>
<div class="line"/>
<el-form ref="webCacheTtl" :model="webCacheTtlData" :label-width="labelWidth">
<setting :setting-group="webCacheTtl" :data="webCacheTtlData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -130,38 +27,67 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'HTTP',
components: { Setting },
computed: {
...mapGetters([
'corsPlugCredentials',
'corsPlugExpose',
'corsPlugHeaders',
'corsPlugMaxAge',
'corsPlugMethods',
'hackneyPools',
'http',
'httpSecurity',
'metricsExporter'
'settings'
]),
corsPlug() {
return this.settings.description.find(setting => setting.group === ':cors_plug')
},
corsPlugData() {
return this.settings.settings[':cors_plug']
},
http() {
return this.settings.description.find(setting => setting.key === ':http')
},
httpData() {
return this.settings.settings[':pleroma'][':http']
},
httpSecurity() {
return this.settings.description.find(setting => setting.key === ':http_security')
},
httpSecurityData() {
return this.settings.settings[':pleroma'][':http_security']
},
httpSignatures() {
return this.settings.description.find(setting => setting.group === ':http_signatures')
},
httpSignaturesData() {
return this.settings.settings[':http_signatures']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
webCacheTtl() {
return this.settings.description.find(setting => setting.key === ':web_cache_ttl')
},
webCacheTtlData() {
return this.settings.settings[':pleroma'][':web_cache_ttl']
}
},
methods: {
processNestedData(value, tab, inputName, childName) {
const updatedValue = { ...this.$store.state.settings.settings[tab][inputName], ...{ [childName]: value }}
this.updateSetting(updatedValue, tab, inputName)
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -0,0 +1,243 @@
<template>
<el-form-item :label="setting.label" :label-width="customLabelWidth" :class="labelClass">
<el-input
v-if="setting.type === 'string'"
:value="inputValue"
:placeholder="setting.suggestions ? setting.suggestions[0] : null"
@input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/>
<el-switch
v-if="setting.type === 'boolean'"
:value="inputValue"
@change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/>
<el-input-number
v-if="setting.type === 'integer'"
:value="inputValue === null ? 0 : inputValue"
:placeholder="setting.suggestions ? setting.suggestions[0].toString() : null"
:min="0"
size="large"
class="top-margin"
@change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/>
<el-select
v-if="setting.type === 'module' || (setting.type.includes('atom') && setting.type.includes(false))"
:value="inputValue"
clearable
@change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)">
<el-option
v-for="(option, index) in setting.suggestions"
:value="option"
:key="index"/>
</el-select>
<el-select
v-if="renderMultipleSelect(setting.type)"
:value="setting.key === ':rewrite_policy' ? rewritePolicyValue : inputValue"
multiple
filterable
allow-create
@change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)">
<el-option v-for="(option, index) in setting.suggestions" :key="index" :value="option"/>
</el-select>
<editor
v-if="setting.key === ':dispatch'"
v-model="editorContent"
height="150"
width="100%"
lang="elixir"
theme="chrome"/>
<el-input
v-if="setting.key === ':ip'"
:value="inputValue"
placeholder="xxx.xxx.xxx.xx"
@input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/>
<el-input
v-if="setting.type === 'atom'"
:value="inputValue"
:placeholder="setting.suggestions[0] ? setting.suggestions[0].substr(1) : ''"
@input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)">
<template slot="prepend">:</template>
</el-input>
<div v-if="setting.type === 'keyword'">
<div v-for="subSetting in setting.children" :key="subSetting.key">
<inputs
:setting-group="settingGroup"
:setting-parent="[...settingParent, subSetting]"
:setting="subSetting"
:data="data[setting.key]"
:custom-label-width="'100px'"
:label-class="'center-label'"
:input-class="'keyword-inner-input'"
:nested="true"/>
</div>
</div>
<!-- special inputs -->
<auto-linker-input v-if="settingGroup.group === ':auto_linker'" :data="data" :setting-group="settingGroup" :setting="setting"/>
<mascots-input v-if="setting.key === ':mascots'" :data="data" :setting-group="settingGroup" :setting="setting"/>
<editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="data" :setting-group="settingGroup" :setting="setting"/>
<icons-input v-if="setting.key === ':icons'" :data="data[':icons']" :setting-group="settingGroup" :setting="setting"/>
<proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
<!-- <ssl-options-input v-if="setting.key === ':ssl_options'" :setting-group="settingGroup" :setting-parent="settingParent" :setting="setting" :data="data" :nested="true" :custom-label-width="'100px'"/> -->
<multiple-select v-if="setting.key === ':backends' || setting.key === ':args'" :data="data" :setting-group="settingGroup" :setting="setting"/>
<prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
<rate-limit-input v-if="settingGroup.key === ':rate_limit'" :data="data" :setting-group="settingGroup" :setting="setting"/>
<!-------------------->
<p v-if="setting.type !== 'keyword'" :class="inputClass" class="expl">{{ setting.description }}</p>
</el-form-item>
</template>
<script>
import AceEditor from 'vue2-ace-editor'
import 'brace/mode/elixir'
import 'default-passive-events'
import { AutoLinkerInput, EditableKeywordInput, IconsInput, MascotsInput, MultipleSelect, ProxyUrlInput, PruneInput, RateLimitInput, SslOptionsInput } from './inputComponents'
import { processNested } from '@/store/modules/normalizers'
export default {
name: 'Inputs',
components: {
editor: AceEditor,
AutoLinkerInput,
EditableKeywordInput,
IconsInput,
MascotsInput,
MultipleSelect,
ProxyUrlInput,
PruneInput,
RateLimitInput,
SslOptionsInput
},
props: {
customLabelWidth: {
type: String,
default: function() {
return this.labelWidth
},
required: false
},
data: {
type: [Object, Array],
default: function() {
return {}
}
},
inputClass: {
type: String,
default: function() {
return 'input-class'
},
required: false
},
labelClass: {
type: String,
default: function() {
return 'label'
},
required: false
},
nested: {
type: Boolean,
default: function() {
return false
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
},
settingParent: {
type: Array,
default: function() {
return []
},
required: false
}
},
computed: {
editorContent: {
get: function() {
return this.data[this.setting.key] ? this.data[this.setting.key][0] : ''
},
set: function(value) {
this.processNestedData([value], this.settingGroup.group, this.settingGroup.key, this.settingParent)
}
},
inputValue() {
if ([':esshd', ':cors_plug', ':quack', ':http_signatures', ':tesla'].includes(this.settingGroup.group) &&
this.data[this.setting.key]) {
return this.setting.type === 'atom' && this.data[this.setting.key].value[0] === ':'
? this.data[this.setting.key].value.substr(1)
: this.data[this.setting.key].value
} else if ((this.settingGroup.group === ':logger' && this.setting.key === ':backends') ||
this.setting.key === 'Pleroma.Web.Auth.Authenticator' ||
this.setting.key === ':admin_token') {
return this.data.value
} else if (this.settingGroup.group === ':mime' && this.settingParent[0].key === ':types') {
return this.data.value[this.setting.key]
} else if (this.setting.type === 'atom') {
return this.data[this.setting.key] && this.data[this.setting.key][0] === ':' ? this.data[this.setting.key].substr(1) : this.data[this.setting.key]
} else {
return this.data[this.setting.key]
}
},
labelWidth() {
return this.isMobile ? '100px' : '240px'
},
rewritePolicyValue() {
return typeof this.data[this.setting.key] === 'string' ? [this.data[this.setting.key]] : this.data[this.setting.key]
},
settings() {
return this.$store.state.settings.settings
},
updatedSettings() {
return this.$store.state.settings.updatedSettings
}
},
methods: {
editableKeyword(key, type) {
return key === ':replace' ||
(Array.isArray(type) && type.includes('keyword') && type.includes('integer')) ||
type === 'map' ||
(Array.isArray(type) && type.includes('keyword') && type.findIndex(el => el.includes('list') && el.includes('string')) !== -1)
},
processNestedData(value, group, parentKey, parents) {
const { valueForState,
valueForUpdatedSettings,
setting } = processNested(value, value, group, parentKey, parents.reverse(), this.settings, this.updatedSettings)
this.$store.dispatch('UpdateSettings',
{ group, key: parentKey, input: setting.key, value: valueForUpdatedSettings, type: setting.type })
this.$store.dispatch('UpdateState',
{ group, key: parentKey, input: setting.key, value: valueForState })
},
renderMultipleSelect(type) {
return Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && (
type.includes('module') ||
(type.includes('list') && type.includes('string')) ||
(type.includes('list') && type.includes('atom')) ||
(type.includes('regex') && type.includes('string')) ||
this.setting.key === ':args'
)
},
update(value, group, key, parents, input, type, nested) {
nested
? this.processNestedData(value, group, key, parents)
: this.updateSetting(value, group, key, input, type)
},
updateSetting(value, group, key, input, type) {
this.$store.dispatch('UpdateSettings', { group, key, input, value, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View file

@ -1,334 +1,36 @@
<template>
<div>
<el-form ref="instance" :model="instance" :label-width="labelWidth">
<el-form-item label="Name">
<el-input :value="instance.name" @input="updateSetting($event, 'instance', 'name')"/>
<p class="expl">The instances name</p>
</el-form-item>
<el-form-item label="Email">
<el-input :value="instance.email" @input="updateSetting($event, 'instance', 'email')"/>
<p class="expl">Email used to reach an Administrator/Moderator of the instance</p>
</el-form-item>
<el-form-item label="Notify email">
<el-input :value="instance.notify_email" @input="updateSetting($event, 'instance', 'notify_email')"/>
<p class="expl">Email used for notifications</p>
</el-form-item>
<el-form-item label="Description">
<el-input :value="instance.description" @input="updateSetting($event, 'instance', 'description')"/>
<p class="expl">The instances description, can be seen in nodeinfo and <span class="code">/api/v1/instance</span></p>
</el-form-item>
<el-form-item label="Limit">
<el-input-number :value="instance.limit" :step="1000" :min="0" size="large" @change="updateSetting($event, 'instance', 'limit')"/>
<p class="expl">Posts character limit (CW/Subject included in the counter)</p>
</el-form-item>
<el-form-item label="Remote limit">
<el-input-number :value="instance.remote_limit" :step="1000" :min="0" size="large" @change="updateSetting($event, 'instance', 'remote_limit')"/>
<p class="expl">Hard character limit beyond which remote posts will be dropped</p>
</el-form-item>
<el-form-item label="Upload limit (MB)">
<el-input-number :value="instance.upload_limit / 1048576" :step="1" :min="0" size="large" @change="updateSetting($event * 1048576, 'instance', 'upload_limit')"/>
<p class="expl">File size limit of uploads (except for avatar, background, banner)</p>
</el-form-item>
<el-form-item label="Avatar upload limit (MB)">
<el-input-number :value="instance.avatar_upload_limit / 1048576" :step="1" :min="0" size="large" @change="updateSetting($event * 1048576, 'instance', 'avatar_upload_limit')"/>
<p class="expl">File size limit of users profile avatars</p>
</el-form-item>
<el-form-item label="Background upload limit (MB)">
<el-input-number :value="instance.background_upload_limit / 1048576" :step="1" :min="0" size="large" @change="updateSetting($event * 1048576, 'instance', 'background_upload_limit')"/>
<p class="expl">File size limit of users profile backgrounds</p>
</el-form-item>
<el-form-item label="Banner upload limit (MB)">
<el-input-number :value="instance.banner_upload_limit / 1048576" :step="1" :min="0" size="large" @change="updateSetting($event * 1048576, 'instance', 'banner_upload_limit')"/>
<p class="expl">File size limit of users profile banners</p>
</el-form-item>
<el-form-item label="User bio length">
<el-input-number :value="instance.user_bio_length" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'user_bio_length')"/>
<p class="expl">A user bio maximum length (default: 5000)</p>
</el-form-item>
<el-form-item label="User name length">
<el-input-number :value="instance.user_name_length" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'user_name_length')"/>
<p class="expl">A user name maximum length (default: 100)</p>
</el-form-item>
<el-form-item label="Poll limits:"/>
<el-form-item label="Max options">
<el-input-number :value="instance.poll_limits.max_options" :step="1" :min="0" size="large" @change="processNestedData($event, 'instance', 'poll_limits', 'max_options')"/>
<p class="expl">Maximum number of options</p>
</el-form-item>
<el-form-item label="Max characters per option">
<el-input-number :value="instance.poll_limits.max_option_chars" :step="1" :min="0" size="large" @change="processNestedData($event, 'instance', 'poll_limits', 'max_option_chars')"/>
<p class="expl">Maximum number of characters per option</p>
</el-form-item>
<el-form-item label="Minimum expiration (days)">
<el-input-number :value="instance.poll_limits.min_expiration" :step="1" :min="0" size="large" @change="processNestedData($event, 'instance', 'poll_limits', 'min_expiration')"/>
<p class="expl">Minimum expiration time</p>
</el-form-item>
<el-form-item label="Max expiration (days)">
<el-input-number :value="instance.poll_limits.max_expiration / 86400" :step="1" :min="0" size="large" @change="processNestedData($event * 86400, 'instance', 'poll_limits', 'max_expiration')"/>
<p class="expl">Maximum expiration time</p>
</el-form-item>
<el-form-item label="Registrations open">
<el-switch :value="instance.registrations_open" @change="updateSetting($event, 'instance', 'registrations_open')"/>
<p class="expl">Enable registrations for anyone, invitations can be enabled when false</p>
</el-form-item>
<el-form-item label="Invites enabled">
<el-switch :value="instance.invites_enabled" @change="updateSetting($event, 'instance', 'invites_enabled')"/>
<p class="expl">Enable user invitations for admins (depends on <span class="code">registrations_open: false)</span>.</p>
</el-form-item>
<el-form-item label="Account activation required">
<el-switch :value="instance.account_activation_required" @change="updateSetting($event, 'instance', 'account_activation_required')"/>
<p class="expl">Require users to confirm their emails before signing in</p>
</el-form-item>
<div class="line"/>
<el-form-item label="Federating">
<el-switch :value="instance.federating" @change="updateSetting($event, 'instance', 'federating')"/>
<p class="expl">Enable federation with other instances</p>
</el-form-item>
<el-form-item label="Fed. replies max depth">
<el-input-number :value="instance.federation_incoming_replies_max_depth" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'federation_incoming_replies_max_depth')"/>
<p class="expl">Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. Lower this value if you experience out-of-memory crashes.</p>
</el-form-item>
<el-form-item label="Fed. reachability timeout">
<el-input-number :value="instance.federation_reachability_timeout_days" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'federation_reachability_timeout_days')"/>
<p class="expl">Timeout (in days) of each external federation target being unreachable prior to pausing federating to it</p>
</el-form-item>
<el-form-item label="Federation publisher modules">
<el-select :value="instance.federation_publisher_modules || []" multiple filterable allow-create @change="updateSetting($event, 'instance', 'federation_publisher_modules')">
<el-option
v-for="item in federationPublisherModulesOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form-item label="Allow relay">
<el-switch :value="instance.allow_relay" @change="updateSetting($event, 'instance', 'allow_relay')"/>
<p class="expl">Enable Pleromas Relay, which makes it possible to follow a whole instance</p>
</el-form-item>
<el-form-item label="Rewrite policy">
<el-select :value="rewritePolicy || []" multiple filterable allow-create @change="updateSetting($event, 'instance', 'rewrite_policy')">
<el-option
v-for="item in rewritePolicyOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<p
v-for="item in rewritePolicyExplanations"
:key="item"
class="expl">{{ getRewritePolicyExpl(item) }}</p>
</el-form-item>
<el-form-item label="Public">
<el-switch :value="instance.public" @change="updateSetting($event, 'instance', 'public')"/>
<p class="expl">Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network</p>
</el-form-item>
<el-form-item label="Quarantined instances">
<el-select :value="instance.quarantined_instances || []" multiple filterable allow-create @change="updateSetting($event, 'instance', 'quarantined_instances')">
<el-option
v-for="item in quarantinedInstancesOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<p class="expl">List of ActivityPub instances where private (DMs, followers-only) activities will not be send</p>
</el-form-item>
<div class="line"/>
<el-form-item label="Managed config">
<el-switch :value="instance.managed_config" @change="updateSetting($event, 'instance', 'managed_config')"/>
<p class="expl">Whenether the config for pleroma-fe is configured in this config or in <span class="code">static/config.json</span></p>
</el-form-item>
<el-form-item label="Static directory">
<el-input :value="instance.static_dir" @input="updateSetting($event, 'instance', 'static_dir')"/>
</el-form-item>
<el-form-item label="Allowed post formats">
<el-select :value="instance.allowed_post_formats || []" multiple filterable allow-create @change="updateSetting($event, 'instance', 'allowed_post_formats')">
<el-option label="text/plain" value="text/plain"/>
<el-option label="text/html" value="text/html"/>
<el-option label="text/markdown" value="text/markdown"/>
<el-option label="text/bbcode" value="text/bbcode"/>
</el-select>
<p class="expl">MIME-type list of formats allowed to be posted (transformed into HTML)</p>
</el-form-item>
<el-form-item label="MRF transparency">
<el-switch :value="instance.mrf_transparency" @change="updateSetting($event, 'instance', 'mrf_transparency')"/>
<p class="expl">Make the content of your Message Rewrite Facility settings public (via nodeinfo)</p>
</el-form-item>
<el-form-item label="MRF transparency exclusions">
<el-select :value="instance.mrf_transparency_exclusions || []" multiple filterable allow-create @change="updateSetting($event, 'instance', 'mrf_transparency_exclusions')"/>
</el-form-item>
<el-form-item label="Scope copy">
<el-switch :value="instance.scope_copy" @change="updateSetting($event, 'instance', 'scope_copy')"/>
<p class="expl">Copy the scope <span class="code">(private/unlisted/public)</span> in replies to posts by default</p>
</el-form-item>
<el-form-item label="Subject line behavior">
<el-select :value="instance.subject_line_behavior" clearable @change="updateSetting($event, 'instance', 'subject_line_behavior')">
<el-option label="Email" value="email">Email / Copy and preprend re:, as in email</el-option>
<el-option label="Masto" value="masto">Masto / Copy verbatim, as in Mastodon</el-option>
<el-option label="Noop" value="noop">Noop / Don't copy the subject</el-option>
</el-select>
<p class="expl">Allows changing the default behaviour of subject lines in replies</p>
</el-form-item>
<el-form-item label="Always show subject input">
<el-switch :value="instance.always_show_subject_input" @change="updateSetting($event, 'instance', 'always_show_subject_input')"/>
<p class="expl">When set to false, auto-hide the subject field when it's empty</p>
</el-form-item>
<el-form-item label="Extended nickname format">
<el-switch :value="instance.extended_nickname_format" @change="updateSetting($event, 'instance', 'extended_nickname_format')"/>
<p class="expl">Set to <span class="code">true</span> to use extended local nicknames format (allows underscores/dashes). This will break federation with older software for theses nicknames</p>
</el-form-item>
<el-form-item label="Max pinned statuses">
<el-input-number :value="instance.max_pinned_statuses" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'max_pinned_statuses')"/>
<p class="expl">The maximum number of pinned statuses. '0' will disable the feature</p>
</el-form-item>
<el-form-item label="Autofollowed nicknames">
<el-select :value="instance.autofollowed_nicknames || []" multiple filterable allow-create @change="updateSetting($event, 'instance', 'autofollowed_nicknames')">
<el-option
v-for="item in autofollowedNicknamesOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<p class="expl">Set to nicknames of (local) users that every new user should automatically follow</p>
</el-form-item>
<el-form-item label="No attachment links">
<el-switch :value="instance.no_attachment_links" @change="updateSetting($event, 'instance', 'no_attachment_links')"/>
<p class="expl">Set to true to disable automatically adding attachment link text to statuses</p>
</el-form-item>
<el-form-item label="Welcome message">
<el-input :value="instance.welcome_message" @input="updateSetting($event, 'instance', 'welcome_message')"/>
<p class="expl">A message that will be send to a newly registered users as a direct message</p>
</el-form-item>
<el-form-item label="Welcome user nickname">
<el-input :value="instance.welcome_user_nickname" @input="updateSetting($event, 'instance', 'welcome_user_nickname')"/>
<p class="expl">The nickname of the local user that sends the welcome message</p>
</el-form-item>
<el-form-item label="Max report comment size">
<el-input-number :value="instance.max_report_comment_size" :step="100" :min="0" size="large" @change="updateSetting($event, 'instance', 'max_report_comment_size')"/>
<p class="expl">The maximum size of the report comment</p>
</el-form-item>
<el-form-item label="Safe DM mentions">
<el-switch :value="instance.safe_dm_mentions" @change="updateSetting($event, 'instance', 'safe_dm_mentions')"/>
<p class="expl">If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them</p>
</el-form-item>
<el-form-item label="Healthcheck">
<el-switch :value="instance.healthcheck" @change="updateSetting($event, 'instance', 'healthcheck')"/>
<p class="expl">If set to true, system data will be shown on <span class="code">/api/pleroma/healthcheck</span></p>
</el-form-item>
<el-form-item label="Remote post retention days">
<el-input-number :value="instance.remote_post_retention_days" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'remote_post_retention_days')"/>
<p class="expl">The default amount of days to retain remote posts when pruning the database.</p>
</el-form-item>
<el-form-item label="Skip thread containment">
<el-switch :value="instance.skip_thread_containment" @change="updateSetting($event, 'instance', 'skip_thread_containment')"/>
<p class="expl">Skip filter out broken threads.</p>
</el-form-item>
<el-form-item label="Limit to local content">
<el-select :value="instance.limit_to_local_content" clearable @change="updateSetting($event, 'instance', 'limit_to_local_content')">
<el-option label="Unauthenticated" value=":unauthenticated"/>
<el-option label="All" value=":all"/>
<el-option label="False" value="false"/>
</el-select>
</el-form-item>
<el-form-item label="Dynamic configuration">
<el-switch :value="instance.dynamic_configuration" @change="updateSetting($event, 'instance', 'dynamic_configuration')"/>
<p class="expl">Allow transferring configuration to DB with the subsequent customization from Admin API</p>
</el-form-item>
<el-form-item label="Max account fields">
<el-input-number :value="instance.max_account_fields" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'max_account_fields')"/>
<p class="expl">The maximum number of custom fields in the user profile (Default: 4)</p>
</el-form-item>
<el-form-item label="Max remote account fields">
<el-input-number :value="instance.max_remote_account_fields" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'max_remote_account_fields')"/>
<p class="expl">The maximum number of custom fields in the remote user profile (Default: 10)</p>
</el-form-item>
<el-form-item label="Account field name length">
<el-input-number :value="instance.account_field_name_length" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'account_field_name_length')"/>
<p class="expl">An account field name maximum length (Default: 255)</p>
</el-form-item>
<el-form-item label="Account field value length">
<el-input-number :value="instance.account_field_value_length" :step="1" :min="0" size="large" @change="updateSetting($event, 'instance', 'account_field_value_length')"/>
<p class="expl">An account field value maximum length (Default: 255)</p>
</el-form-item>
<el-form-item label="External user synchronization">
<el-switch :value="instance.external_user_synchronization" @change="updateSetting($event, 'instance', 'external_user_synchronization')"/>
<p class="expl">Enabling following/followers counters synchronization for external users.</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="instanceData" :model="instanceData" :label-width="labelWidth">
<setting :setting-group="instance" :data="instanceData"/>
</el-form>
<div class="line"/>
<el-form ref="uriSchemes" :model="uriSchemes" :label-width="labelWidth">
<el-form-item label="URI schemes">
<el-select :value="uriSchemes.valid_schemes || []" multiple filterable allow-create @change="updateSetting($event, 'uri_schemes', 'valid_schemes')">
<el-option
v-for="item in uriSchemesOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
<p class="expl">List of the scheme part that is considered valid to be an URL</p>
</el-form-item>
<el-form ref="uriSchemes" :model="uriSchemesData" :label-width="labelWidth">
<setting :setting-group="uriSchemes" :data="uriSchemesData"/>
</el-form>
<div class="line"/>
<el-form ref="adminToken" :model="adminToken" :label-width="labelWidth">
<el-form-item label="Admin token">
<el-input :value="adminToken.value" @input="updateSetting($event, 'admin_token', 'value')"/>
<p class="expl">Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the <span class="code">admin_token</span> parameter.</p>
</el-form-item>
<el-form ref="adminToken" :model="adminTokenData" :label-width="labelWidth">
<setting :setting-group="adminToken" :data="adminTokenData"/>
</el-form>
<div class="line"/>
<el-form ref="scheduledActivity" :model="scheduledActivity" :label-width="labelWidth">
<el-form-item label="Scheduled activity:"/>
<el-form-item label="Daily user limit">
<el-input-number :value="scheduledActivity.daily_user_limit" :step="5" :min="0" size="large" @change="updateSetting($event, 'Pleroma.ScheduledActivity', 'daily_user_limit')"/>
<p class="expl">The number of scheduled activities a user is allowed to create in a single day (Default: 25)</p>
</el-form-item>
<el-form-item label="Total user limit">
<el-input-number :value="scheduledActivity.total_user_limit" :step="10" :min="0" size="large" @change="updateSetting($event, 'Pleroma.ScheduledActivity', 'total_user_limit')"/>
<p class="expl">The number of scheduled activities a user is allowed to create in total (Default: 300)</p>
</el-form-item>
<el-form-item label="Enabled">
<el-switch :value="scheduledActivity.enabled" @change="updateSetting($event, 'Pleroma.ScheduledActivity', 'enabled')"/>
<p class="expl">Whether scheduled activities are sent to the job queue to be executed</p>
</el-form-item>
<el-form ref="scheduledActivity" :model="scheduledActivityData" :label-width="labelWidth">
<setting :setting-group="scheduledActivity" :data="scheduledActivityData"/>
</el-form>
<div class="line"/>
<el-form ref="fetchInitialPosts" :model="fetchInitialPosts" :label-width="labelWidth">
<el-form-item label="Fetch initial posts">
<el-switch :value="fetchInitialPosts.enabled" @change="updateSetting($event, 'fetch_initial_posts', 'enabled')"/>
<p class="expl">If enabled, when a new user is federated with, fetch some of their latest posts</p>
</el-form-item>
<el-form-item label="Pages">
<el-input-number :value="fetchInitialPosts.pages" :step="1" :min="0" size="large" @change="updateSetting($event, 'fetch_initial_posts', 'pages')"/>
<p class="expl">The amount of pages to fetch</p>
</el-form-item>
<el-form ref="fetchInitialPosts" :model="fetchInitialPostsData" :label-width="labelWidth">
<setting :setting-group="fetchInitialPosts" :data="fetchInitialPostsData"/>
</el-form>
<div class="line"/>
<el-form ref="suggestions" :model="suggestions" :label-width="labelWidth">
<el-form ref="manifest" :model="manifestData" :label-width="labelWidth">
<setting :setting-group="manifest" :data="manifestData"/>
</el-form>
<div class="line"/>
<el-form ref="suggestions" :model="suggestionsData" :label-width="labelWidth">
<el-form-item label="Suggestions:"/>
<el-form-item label="Enabled">
<el-switch :value="suggestions.enabled" @change="updateSetting($event, 'suggestions', 'enabled')"/>
</el-form-item>
<el-form-item label="Third party engine">
<el-input :value="suggestions.third_party_engine" @input="updateSetting($event, 'suggestions', 'third_party_engine')"/>
</el-form-item>
<el-form-item label="Timeout">
<el-input-number :value="suggestions.timeout" :step="1000" :min="0" size="large" @change="updateSetting($event, 'suggestions', 'timeout')"/>
</el-form-item>
<el-form-item label="Limit">
<el-input-number :value="suggestions.limit" :step="1" :min="0" size="large" @change="updateSetting($event, 'suggestions', 'limit')"/>
</el-form-item>
<el-form-item label="Web">
<el-input :value="suggestions.web" @input="updateSetting($event, 'suggestions', 'web')"/>
</el-form-item>
<setting :setting-group="suggestions" :data="suggestionsData"/>
</el-form>
<div class="line"/>
<el-form ref="pleromaUser" :model="pleromaUser" :label-width="labelWidth">
<el-form-item label="Restricted nicknames">
<el-select :value="pleromaUser.restricted_nicknames || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.User', 'restricted_nicknames')">
<el-option
v-for="item in restrictedNicknamesOptions"
:key="item.value"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form ref="pleromaUser" :model="pleromaUserData" :label-width="labelWidth">
<setting :setting-group="pleromaUser" :data="pleromaUserData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -338,65 +40,87 @@
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Instance',
components: {
Setting
},
computed: {
...mapGetters([
'adminToken',
'fetchInitialPosts',
'instance',
'pleromaUser',
'scheduledActivity',
'suggestions',
'uriSchemes'
'settings'
]),
autofollowedNicknamesOptions() {
return options.autofollowedNicknamesOptions
adminToken() {
return this.settings.description.find(setting => setting.description === `Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter`)
},
federationPublisherModulesOptions() {
return options.federationPublisherModulesOptions
adminTokenData() {
return this.settings.settings[':pleroma'][':admin_token']
},
fetchInitialPosts() {
return this.settings.description.find(setting => setting.key === ':fetch_initial_posts')
},
fetchInitialPostsData() {
return this.settings.settings[':pleroma'][':fetch_initial_posts']
},
instance() {
return this.settings.description.find(setting => setting.key === ':instance')
},
instanceData() {
return this.settings.settings[':pleroma'][':instance']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
quarantinedInstancesOptions() {
return options.quarantinedInstancesOptions
loading() {
return this.settings.loading
},
restrictedNicknamesOptions() {
return options.restrictedNicknamesOptions
manifest() {
return this.settings.description.find(setting => setting.key === ':manifest')
},
rewritePolicy() {
return typeof this.instance.rewrite_policy === 'string' ? [this.instance.rewrite_policy] : this.instance.rewrite_policy
manifestData() {
return this.settings.settings[':pleroma'][':manifest']
},
rewritePolicyExplanations() {
return this.rewritePolicy ? this.rewritePolicy.filter(policy => options.rewritePolicyOptions.find(el => el.value === policy)) : []
pleromaUser() {
return this.settings.description.find(setting => setting.key === 'Pleroma.User')
},
rewritePolicyOptions() {
return options.rewritePolicyOptions
pleromaUserData() {
return this.settings.settings[':pleroma']['Pleroma.User']
},
uriSchemesOptions() {
return options.uriSchemesOptions
scheduledActivity() {
return this.$store.state.settings.description.find(setting => setting.key === 'Pleroma.ScheduledActivity')
},
scheduledActivityData() {
return this.settings.settings[':pleroma']['Pleroma.ScheduledActivity']
},
suggestions() {
return this.$store.state.settings.description.find(setting => setting.key === ':suggestions')
},
suggestionsData() {
return this.settings.settings[':pleroma'][':suggestions']
},
uriSchemes() {
return this.$store.state.settings.description.find(setting => setting.key === ':uri_schemes')
},
uriSchemesData() {
return this.settings.settings[':pleroma'][':uri_schemes']
}
},
methods: {
getRewritePolicyExpl(value) {
const policy = options.rewritePolicyOptions.find(el => el.value === value)
return policy.expl
},
processNestedData(value, tab, inputName, childName) {
const updatedValue = { ...this.$store.state.settings.settings[tab][inputName], ...{ [childName]: value }}
this.updateSetting(updatedValue, tab, inputName)
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,48 +1,13 @@
<template>
<div>
<el-form ref="queues" :model="queues" :label-width="labelWidth">
<el-form-item label="Job queues:"/>
<el-form-item label="Outgoing federation">
<el-input-number :value="queues.federator_outgoing" :step="1" :min="0" size="large" @change="updateSetting($event, 'queues', 'federator_outgoing')"/>
</el-form-item>
<el-form-item label="Incoming federation">
<el-input-number :value="queues.federator_incoming" :step="1" :min="0" size="large" @change="updateSetting($event, 'queues', 'federator_incoming')"/>
</el-form-item>
<el-form-item label="Email sender">
<el-input-number :value="queues.mailer" :step="1" :min="0" size="large" @change="updateSetting($event, 'queues', 'mailer')"/>
</el-form-item>
<el-form-item label="Transmogrifier">
<el-input-number :value="queues.transmogrifier" :step="1" :min="0" size="large" @change="updateSetting($event, 'queues', 'transmogrifier')"/>
</el-form-item>
<el-form-item label="Web push notifications">
<el-input-number :value="queues.web_push" :step="1" :min="0" size="large" @change="updateSetting($event, 'queues', 'web_push')"/>
</el-form-item>
<el-form-item label="Scheduled activities">
<el-input-number :value="queues.scheduled_activities" :step="1" :min="0" size="large" @change="updateSetting($event, 'queues', 'scheduled_activities')"/>
</el-form-item>
<el-form-item label="Background">
<el-input-number :value="queues.background" :step="1" :min="0" size="large" @change="updateSetting($event, 'queues', 'background')"/>
</el-form-item>
<div v-if="!loading">
<el-form ref="obanQueuesData" :model="obanQueuesData" :label-width="labelWidth">
<setting :setting-group="obanQueues" :data="obanQueuesData"/>
</el-form>
<div class="line"/>
<el-form ref="retryQueue" :model="retryQueue" :label-width="labelWidth">
<el-form-item label="Retry queue:"/>
<el-form-item label="Enabled">
<el-switch :value="retryQueue.enabled" @change="updateSetting($event, 'Pleroma.Web.Federator.RetryQueue', 'enabled')"/>
<p class="expl">If set to true, failed federation jobs will be retried</p>
</el-form-item>
<el-form-item label="Max jobs">
<el-input-number :value="retryQueue.max_jobs" :step="1" :min="0" size="large" @change="updateSetting($event, 'Pleroma.Web.Federator.RetryQueue', 'max_jobs')"/>
<p class="expl">The maximum amount of parallel federation jobs running at the same time.</p>
</el-form-item>
<el-form-item label="Initial timeout (s)">
<el-input-number :value="retryQueue.initial_timeout" :step="1" :min="0" size="large" @change="updateSetting($event, 'Pleroma.Web.Federator.RetryQueue', 'initial_timeout')"/>
<p class="expl">The initial timeout in seconds</p>
</el-form-item>
<el-form-item label="Max retries">
<el-input-number :value="retryQueue.max_retries" :step="1" :min="0" size="large" @change="updateSetting($event, 'Pleroma.Web.Federator.RetryQueue', 'max_retries')"/>
<p class="expl">The maximum number of times a federation job is retried</p>
</el-form-item>
<el-form ref="workersData" :model="workersData" :label-width="labelWidth">
<setting :setting-group="workers" :data="workersData"/>
</el-form>
<el-form ref="activityExpiration" :model="activityExpirationData" :label-width="labelWidth">
<setting :setting-group="activityExpiration" :data="activityExpirationData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -52,27 +17,55 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'JobQueue',
components: { Setting },
computed: {
...mapGetters([
'queues',
'retryQueue'
'settings'
]),
activityExpiration() {
return this.settings.description.find(setting => setting.key === 'Pleroma.ActivityExpiration')
},
activityExpirationData() {
return this.settings.settings[':pleroma']['Pleroma.ActivityExpiration']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
obanQueues() {
return this.settings.description.find(setting => setting.key === 'Oban')
},
obanQueuesData() {
return this.settings.settings[':pleroma']['Oban']
},
workers() {
return this.settings.description.find(setting => setting.key === ':workers')
},
workersData() {
return this.settings.settings[':pleroma'][':workers']
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,169 +1,20 @@
<template>
<div>
<el-form ref="loggerBackends" :model="loggerBackends" :label-width="labelWidth">
<el-form-item label="Backends">
<el-select :value="loggerBackendsValue" multiple filterable allow-create @change="updateloggerBackends($event, 'backends', 'value')">
<el-option
v-for="(item, index) in loggerBackendsOptions"
:key="index"
:label="item.label"
:value="item.value"/>
</el-select>
<p class="expl"><span class="code">:console</span> is used to send logs to stdout, <span class="code">{ExSyslogger, :ex_syslogger}</span> to log to syslog, and <span class="code">Quack.Logger</span> to log to Slack</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="loggerData" :model="loggerData" :label-width="labelWidth">
<setting :setting-group="logger" :data="loggerData"/>
</el-form>
<div class="line"/>
<el-form ref="consoleLogger" :model="consoleLogger" :label-width="labelWidth">
<el-form-item label="Console logger:"/>
<el-form-item label="Level">
<el-select :value="consoleLogger.level" clearable @change="updateSetting($event, 'console', 'level')">
<el-option value=":debug" label=":debug - for debug-related messages"/>
<el-option value=":info" label=":info - for information of any kind"/>
<el-option value=":warn" label=":warn - for warnings"/>
<el-option value=":error" label=":error - for errors"/>
</el-select>
<p class="expl">The level to be logged by this backend. Note that messages are filtered by the general
<span class="code">:level</span> configuration for the <span class="code">:logger</span> application first.</p>
</el-form-item>
<el-form-item label="Format">
<el-input :value="consoleLogger.format" @input="updateSetting($event, 'console', 'format')"/>
<p class="expl">The format message used to print logs. </p>
</el-form-item>
<el-form-item label="Metadata">
<el-select :value="consoleLogger.metadata || []" multiple filterable allow-create @change="updateSetting($event, 'console', 'metadata')">
<el-option value=":all"/>
<el-option value=":request_id"/>
<el-option value=":line"/>
<el-option value=":user_id"/>
<el-option value=":application"/>
<el-option value=":function"/>
<el-option value=":file"/>
<el-option value=":pid"/>
<el-option value=":crash_reason"/>
<el-option value=":initial_call"/>
<el-option value=":registered_name"/>
<el-option value=":none"/>
</el-select>
</el-form-item>
<el-form-item label="Device">
<el-input :value="consoleLogger.device" @input="updateSetting($event, 'console', 'device')"/>
<p class="expl">The device to log error messages to. Defaults to <span class="code">:user</span>
but can be changed to something else such as <span class="code">:standard_error</span></p>
</el-form-item>
<el-form-item label="Max buffer">
<el-input-number :value="consoleLogger.max_buffer" :step="1" :min="0" size="large" @change="updateSetting($event, 'console', 'max_buffer')"/>
<p class="expl">Maximum events to buffer while waiting for a confirmation from the IO device (default: 32). Once the buffer is full, the backend will block until a confirmation is received.</p>
</el-form-item>
<el-form-item label="Colors:"/>
<el-form-item label="Enabled">
<el-switch :value="consoleLogger.colors.enabled" @change="processNestedData($event, 'console', 'colors', 'enabled')"/>
</el-form-item>
<el-form-item label="Debug message">
<el-input :value="consoleLogger.colors.debug" @input="processNestedData($event, 'console', 'colors', 'debug')"/>
<p class="expl">Defaults to: <span class="code">:cyan</span></p>
</el-form-item>
<el-form-item label="Info message">
<el-input :value="consoleLogger.colors.info" @input="processNestedData($event, 'console', 'colors', 'info')"/>
<p class="expl">Defaults to: <span class="code">:normal</span></p>
</el-form-item>
<el-form-item label="Warn message">
<el-input :value="consoleLogger.colors.warn" @input="processNestedData($event, 'console', 'colors', 'warn')"/>
<p class="expl">Defaults to: <span class="code">:yellow</span></p>
</el-form-item>
<el-form-item label="Error message">
<el-input :value="consoleLogger.colors.error" @input="processNestedData($event, 'console', 'colors', 'error')"/>
<p class="expl">Defaults to: <span class="code">:red</span></p>
</el-form-item>
<el-form ref="consoleData" :model="consoleData" :label-width="labelWidth">
<setting :setting-group="console" :data="consoleData"/>
</el-form>
<div class="line"/>
<el-form ref="exsyslogger" :model="exsyslogger" :label-width="labelWidth">
<el-form-item label="ExSyslogger:"/>
<el-form-item label="Level">
<el-select :value="exsyslogger.level" clearable @change="updateSetting($event, 'ex_syslogger', 'level')">
<el-option value=":debug" label=":debug - for debug-related messages"/>
<el-option value=":info" label=":info - for information of any kind"/>
<el-option value=":warn" label=":warn - for warnings"/>
<el-option value=":error" label=":error - for errors"/>
</el-select>
<p class="expl">Logging level. It defaults to <span class="code">:info.</span></p>
</el-form-item>
<el-form-item label="Format">
<el-input :value="exsyslogger.format" @input="updateSetting($event, 'ex_syslogger', 'format')"/>
<p class="expl">The format message used to print logs.</p>
</el-form-item>
<el-form-item label="Formatter">
<el-input :value="exsyslogger.formatter" @input="updateSetting($event, 'ex_syslogger', 'formatter')"/>
<p class="expl">Formatter that will be used to format the log. It default to <span class="code">Logger.Formatter</span></p>
</el-form-item>
<el-form-item label="Metadata">
<el-select :value="exsyslogger.metadata || []" multiple filterable allow-create @change="updateSetting($event, 'ex_syslogger', 'metadata')">
<el-option value=":all"/>
<el-option value=":request_id"/>
<el-option value=":line"/>
<el-option value=":user_id"/>
<el-option value=":application"/>
<el-option value=":function"/>
<el-option value=":file"/>
<el-option value=":pid"/>
<el-option value=":crash_reason"/>
<el-option value=":initial_call"/>
<el-option value=":registered_name"/>
<el-option value=":none"/>
</el-select>
</el-form-item>
<el-form-item label="Ident">
<el-input :value="exsyslogger.ident" @input="updateSetting($event, 'ex_syslogger', 'ident')"/>
<p class="expl">A string thats prepended to every message, and is typically set to the app name. It defaults to <span class="code">Elixir</span></p>
</el-form-item>
<el-form-item label="Facility">
<el-input :value="exsyslogger.facility" @input="updateSetting($event, 'ex_syslogger', 'facility')"/>
<p class="expl">Syslog facility to be used. It defaults to <span class="code">:local0</span></p>
</el-form-item>
<el-form-item label="Options">
<el-select :value="exsyslogger.option || []" multiple filterable allow-create @change="updateSetting($event, 'ex_syslogger', 'option')">
<el-option value=":pid"/>
<el-option value=":cons"/>
<el-option value=":odelay"/>
<el-option value=":ndelay"/>
<el-option value=":perror"/>
</el-select>
<p class="expl">Syslog option to be used. It defaults to <span class="code">:ndelay.</span></p>
</el-form-item>
<el-form ref="exsysloggerData" :model="exsysloggerData" :label-width="labelWidth">
<setting :setting-group="exsyslogger" :data="exsysloggerData"/>
</el-form>
<div class="line"/>
<el-form ref="webhookUrl" :model="webhookUrl" :label-width="labelWidth">
<el-form ref="quackData" :model="quackData" :label-width="labelWidth">
<el-form-item label="Quack logger:"/>
<el-form-item label="Webhook URL">
<el-input :value="webhookUrl.value" @input="updateSetting($event, 'webhook_url', 'value')"/>
</el-form-item>
</el-form>
<el-form ref="level" :model="level" :label-width="labelWidth">
<el-form-item label="Level">
<el-select :value="level.value" clearable @change="updateSetting($event, 'level', 'value')">
<el-option value=":debug" label=":debug - for debug-related messages"/>
<el-option value=":info" label=":info - for information of any kind"/>
<el-option value=":warn" label=":warn - for warnings"/>
<el-option value=":error" label=":error - for errors"/>
</el-select>
<p class="expl">Logging level. It defaults to <span class="code">:info.</span></p>
</el-form-item>
</el-form>
<el-form ref="meta" :model="meta" :label-width="labelWidth">
<el-form-item label="Metadata">
<el-select :value="meta.value || []" multiple filterable allow-create @change="updateSetting($event, 'meta', 'value')">
<el-option value=":all"/>
<el-option value=":module"/>
<el-option value=":function"/>
<el-option value=":file"/>
<el-option value=":application"/>
<el-option value=":line"/>
<el-option value=":pid"/>
<el-option value=":crash_reason"/>
<el-option value=":initial_call"/>
<el-option value=":registered_name"/>
<el-option value=":none"/>
</el-select>
</el-form-item>
<setting :setting-group="quack" :data="quackData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -173,46 +24,61 @@
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Logger',
components: { Setting },
computed: {
...mapGetters([
'consoleLogger',
'exsyslogger',
'level',
'loggerBackends',
'meta',
'webhookUrl'
'settings'
]),
loggerBackendsValue() {
return this.loggerBackends.value ? this.loggerBackends.value.map(el => JSON.stringify(el)) : []
console() {
return this.settings.description.find(setting => setting.key === ':console')
},
loggerBackendsOptions() {
return options.loggerBackendsOptions
consoleData() {
return this.settings.settings[':logger'][':console']
},
exsyslogger() {
return this.settings.description.find(setting => setting.key === ':ex_syslogger')
},
exsysloggerData() {
return this.settings.settings[':logger'][':ex_syslogger']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
logger() {
return this.settings.description.find(setting => setting.group === ':logger')
},
loggerData() {
return this.settings.settings[':logger'][':backends']
},
quack() {
return this.settings.description.find(setting => setting.group === ':quack')
},
quackData() {
return this.settings.settings[':quack']
}
},
methods: {
processNestedData(value, tab, section, input) {
const updatedValue = { ...this.$store.state.settings.settings[tab][section], ...{ [input]: value }}
this.updateSetting(updatedValue, tab, section)
},
updateloggerBackends(value, tab, input) {
const parseValue = value.map(el => JSON.parse(el))
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: parseValue }})
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,141 +1,33 @@
<template>
<div>
<el-form ref="mrfSimple" :model="mrfSimple" :label-width="labelWidth">
<el-form-item label="MRF Simple:"/>
<el-form-item label="Media removal">
<el-select :value="mrfSimple.media_removal || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'media_removal')"/>
<p class="expl">List of instances to remove medias from</p>
</el-form-item>
<el-form-item label="Media NSFW">
<el-select :value="mrfSimple.media_nsfw || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'media_nsfw')"/>
<p class="expl">List of instances to put medias as NSFW (sensitive)</p>
</el-form-item>
<el-form-item label="Federated timeline removal">
<el-select :value="mrfSimple.federated_timeline_removal || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'federated_timeline_removal')"/>
<p class="expl">List of instances to remove from Federated (aka The Whole Known Network) Timeline</p>
</el-form-item>
<el-form-item label="Reject">
<el-select :value="mrfSimple.reject || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'reject')"/>
<p class="expl">List of instances to reject any activities from</p>
</el-form-item>
<el-form-item label="Accept">
<el-select :value="mrfSimple.accept || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'accept')"/>
<p class="expl">List of instances to accept any activities from</p>
</el-form-item>
<el-form-item label="Report removal">
<el-select :value="mrfSimple.report_removal || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'report_removal')"/>
<p class="expl">List of instances to reject reports from</p>
</el-form-item>
<el-form-item label="Avatar removal">
<el-select :value="mrfSimple.avatar_removal || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'avatar_removal')"/>
<p class="expl">List of instances to strip avatars from</p>
</el-form-item>
<el-form-item label="Banner removal">
<el-select :value="mrfSimple.banner_removal || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_simple', 'banner_removal')"/>
<p class="expl">List of instances to strip banners from</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="mrfSimple" :model="mrfSimpleData" :label-width="labelWidth">
<setting :setting-group="mrfSimple" :data="mrfSimpleData"/>
</el-form>
<div class="line"/>
<el-form ref="mrfRejectnonpublic" :model="mrfRejectnonpublic" :label-width="labelWidth">
<el-form-item label="MRF Reject non public:"/>
<el-form-item label="Allow followers-only posts">
<el-switch :value="mrfRejectnonpublic.allow_followersonly" @change="updateSetting($event, 'mrf_rejectnonpublic', 'allow_followersonly')"/>
</el-form-item>
<el-form-item label="Allow direct messages">
<el-switch :value="mrfRejectnonpublic.allow_direct" @change="updateSetting($event, 'mrf_rejectnonpublic', 'allow_direct')"/>
</el-form-item>
<el-form ref="mrfRejectnonpublic" :model="mrfRejectnonpublicData" :label-width="labelWidth">
<setting :setting-group="mrfRejectnonpublic" :data="mrfRejectnonpublicData"/>
</el-form>
<div class="line"/>
<el-form ref="mrfHellthread" :model="mrfHellthread" :label-width="labelWidth">
<el-form-item label="MRF Hellthread:"/>
<el-form-item label="Delist threshold">
<el-input-number :value="mrfHellthread.delist_threshold" :step="1" :min="0" size="large" @change="updateSetting($event, 'mrf_hellthread', 'delist_threshold')"/>
<p class="expl">Number of mentioned users after which the message gets delisted
(the message can still be seen, but it will not show up in public timelines and mentioned users won't get notifications about it).
Set to 0 to disable.</p>
</el-form-item>
<el-form-item label="Reject threshold">
<el-input-number :value="mrfHellthread.reject_threshold" :step="1" :min="0" size="large" @change="updateSetting($event, 'mrf_hellthread', 'reject_threshold')"/>
<p class="expl">Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.</p>
</el-form-item>
</el-form>
<el-form ref="mrfKeyword" :model="mrfKeyword" :label-width="labelWidth">
<div class="line"/>
<el-form-item label="MRF Keyword:"/>
<el-form-item label="Reject">
<el-select :value="mrfKeyword.reject || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_keyword', 'reject')"/>
<p class="expl">A list of patterns which result in message being rejected</p>
</el-form-item>
<el-form-item label="Federated timeline removal">
<el-select :value="mrfKeyword.federated_timeline_removal" multiple allow-create filterable @change="updateSetting($event, 'mrf_keyword', 'federated_timeline_removal')"/>
<p class="expl">A list of patterns which result in message being removed from federated timelines (a.k.a unlisted)</p>
</el-form-item>
<el-form-item label="Replace">
<div v-for="([key, value], index) in replacePatterns" :key="index" class="setting-input">
<el-input :value="key" placeholder="pattern" class="name-input" @input="parseReplace($event, 'key', index)"/> :
<el-input :value="value" placeholder="replacement" class="value-input" @input="parseReplace($event, 'value', index)"/>
<el-button icon="el-icon-minus" circle @click="deleteReplaceRow(index)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addReplaceRow"/>
</el-form-item>
<el-form ref="mrfHellthread" :model="mrfHellthreadData" :label-width="labelWidth">
<setting :setting-group="mrfHellthread" :data="mrfHellthreadData"/>
</el-form>
<div class="line"/>
<el-form ref="mrfSubchain" :model="mrfSubchain" :label-width="labelWidth">
<el-form-item label="MRF Subchain:"/>
<el-form-item label="Match actor:">
<div v-for="([regExp, policies], index) in matchActor" :key="index" class="setting-input">
<el-input :value="regExp" placeholder="Regular expression" class="name-input" @input="parseMrfSubchain($event, 'regExp', index)"/> :
<el-select :value="policies" placeholder="Policy modules" multiple filterable allow-create class="value-input" @change="parseMrfSubchain($event, 'policies', index)">
<el-option
v-for="item in policiesOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
<el-button icon="el-icon-minus" circle @click="deleteMrfSubchainRow(index)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addMrfSubchainRow"/>
<p class="expl">Matches a series of regular expressions against the actor field.</p>
</el-form-item>
<el-form ref="mrfKeyword" :model="mrfKeywordData" :label-width="labelWidth">
<setting :setting-group="mrfKeyword" :data="mrfKeywordData"/>
</el-form>
<el-form ref="mrfSubchain" :model="mrfSubchainData" :label-width="labelWidth">
<setting :setting-group="mrfSubchain" :data="mrfSubchainData"/>
</el-form>
<el-form ref="mrfMention" :model="mrfMentionData" :label-width="labelWidth">
<setting :setting-group="mrfMention" :data="mrfMentionData"/>
</el-form>
<div class="line"/>
<el-form ref="mrfMention" :model="mrfMention" :label-width="labelWidth">
<el-form-item label="MRF Mention:"/>
<el-form-item label="Actors">
<el-select :value="mrfMention.actors || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_mention', 'actors')"/>
<p class="expl">A list of actors, for which to drop any posts mentioning.</p>
</el-form-item>
<el-form ref="mrfNormalizeMarkup" :model="mrfNormalizeMarkupData" :label-width="labelWidth">
<setting :setting-group="mrfNormalizeMarkup" :data="mrfNormalizeMarkupData"/>
</el-form>
<div class="line"/>
<el-form ref="mrfUserAllowlist" :model="mrfUserAllowlist" :label-width="labelWidth">
<el-form-item label="MRF User allowlist">
<div v-for="([domain, users], index) in userAllowlist" :key="index" class="setting-input">
<el-input :value="domain" placeholder="domain" class="name-input" @input="parseMrfUserAllowlist($event, 'domain', index)"/> :
<el-select :value="users" placeholder="list of users" multiple filterable allow-create class="value-input" @change="parseMrfUserAllowlist($event, 'users', index)"/>
<el-button icon="el-icon-minus" circle @click="deleteMrfUserAllowlistRow(index)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addMrfUserAllowlistRow"/>
<p class="expl">The keys in this section are the domain names that the policy should apply to. Each key should be assigned a list of users that should be allowed through by their ActivityPub ID.</p>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="mrfNormalizeMarkup" :model="mrfNormalizeMarkup" :label-width="labelWidth">
<el-form-item label="MRF normalize markup:"/>
<el-form-item label="Scrub policy">
<el-input :value="mrfNormalizeMarkup.scrub_policy" @input="updateSetting($event, 'mrf_normalize_markup', 'scrub_policy')"/>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="mrfVocabulary" :model="mrfVocabulary" :label-width="labelWidth">
<el-form-item label="MRF Vocabulary:"/>
<el-form-item label="Accept">
<el-select :value="mrfVocabulary.accept || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_vocabulary', 'accept')"/>
<p class="expl">A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.</p>
</el-form-item>
<el-form-item label="Reject">
<el-select :value="mrfVocabulary.reject || []" multiple allow-create filterable @change="updateSetting($event, 'mrf_vocabulary', 'reject')"/>
<p class="expl">A list of ActivityStreams terms to reject. If empty, no messages are rejected.</p>
</el-form-item>
<el-form ref="mrfVocabulary" :model="mrfVocabularyData" :label-width="labelWidth">
<setting :setting-group="mrfVocabulary" :data="mrfVocabularyData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -145,119 +37,85 @@
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'MRF',
data: function() {
return {
removableDoubleOptions: ['replace', 'mrfUserAllowlist'],
removableSingleOptions: ['keywordReject', 'federatedTimelineRemoval']
}
},
components: { Setting },
computed: {
...mapGetters([
'mrfHellthread',
'mrfKeyword',
'mrfMention',
'mrfNormalizeMarkup',
'mrfSimple',
'mrfSubchain',
'mrfRejectnonpublic',
'mrfUserAllowlist',
'mrfVocabulary'
'settings'
]),
matchActor() {
return Object.keys(this.mrfSubchain.match_actor).map(key => [key, this.mrfSubchain.match_actor[key]])
},
policiesOptions() {
return options.rewritePolicyOptions
},
replacePatterns() {
return Object.keys(this.mrfKeyword.replace).map(key => [key, this.mrfKeyword.replace[key]])
},
userAllowlist() {
return Object.keys(this.mrfUserAllowlist).map(key => [key, this.mrfUserAllowlist[key]])
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
mrfSimple() {
return this.settings.description.find(setting => setting.key === ':mrf_simple')
},
mrfSimpleData() {
return this.settings.settings[':pleroma'][':mrf_simple']
},
mrfRejectnonpublic() {
return this.settings.description.find(setting => setting.key === ':mrf_rejectnonpublic')
},
mrfRejectnonpublicData() {
return this.settings.settings[':pleroma'][':mrf_rejectnonpublic']
},
mrfHellthread() {
return this.settings.description.find(setting => setting.key === ':mrf_hellthread')
},
mrfHellthreadData() {
return this.settings.settings[':pleroma'][':mrf_hellthread']
},
mrfKeyword() {
return this.settings.description.find(setting => setting.key === ':mrf_keyword')
},
mrfKeywordData() {
return this.settings.settings[':pleroma'][':mrf_keyword']
},
mrfSubchain() {
return this.settings.description.find(setting => setting.key === ':mrf_subchain')
},
mrfSubchainData() {
return this.settings.settings[':pleroma'][':mrf_subchain']
},
mrfMention() {
return this.settings.description.find(setting => setting.key === ':mrf_mention')
},
mrfMentionData() {
return this.settings.settings[':pleroma'][':mrf_mention']
},
mrfNormalizeMarkup() {
return this.settings.description.find(setting => setting.key === ':mrf_normalize_markup')
},
mrfNormalizeMarkupData() {
return this.settings.settings[':pleroma'][':mrf_normalize_markup']
},
mrfVocabulary() {
return this.settings.description.find(setting => setting.key === ':mrf_vocabulary')
},
mrfVocabularyData() {
return this.settings.settings[':pleroma'][':mrf_vocabulary']
}
},
methods: {
addMrfSubchainRow() {
const updatedValue = this.matchActor.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting({ ...updatedValue, '': [] }, 'mrf_subchain', 'match_actor')
},
addMrfUserAllowlistRow() {
const updatedValue = this.userAllowlist.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.$store.dispatch('RewriteConfig', { data: { ...updatedValue, '': [] }, tab: 'mrf_user_allowlist' })
},
addReplaceRow() {
const updatedValue = this.replacePatterns.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting({ ...updatedValue, '': '' }, 'mrf_keyword', 'replace')
},
deleteMrfSubchainRow(index) {
const filteredValues = this.matchActor.filter((el, i) => index !== i)
const updatedValue = filteredValues.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'mrf_subchain', 'match_actor')
},
deleteMrfUserAllowlistRow(index) {
const filteredValues = this.userAllowlist.filter((el, i) => index !== i)
const updatedValue = filteredValues.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.$store.dispatch('RewriteConfig', { data: updatedValue, tab: 'mrf_user_allowlist' })
},
deleteReplaceRow(index) {
const filteredValues = this.replacePatterns.filter((el, i) => index !== i)
const updatedValue = filteredValues.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'mrf_keyword', 'replace')
},
parseMrfSubchain(value, inputType, index) {
const updatedValue = this.matchActor.reduce((acc, el, i) => {
if (index === i) {
return inputType === 'regExp' ? { ...acc, [value]: el[1] } : { ...acc, [el[0]]: value }
}
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'mrf_subchain', 'match_actor')
},
parseMrfUserAllowlist(value, inputType, index) {
const updatedValue = this.userAllowlist.reduce((acc, el, i) => {
if (index === i) {
return inputType === 'domain' ? { ...acc, [value]: el[1] } : { ...acc, [el[0]]: value }
}
return { ...acc, [el[0]]: el[1] }
}, {})
this.$store.dispatch('RewriteConfig', { data: updatedValue, tab: 'mrf_user_allowlist' })
},
parseReplace(value, inputType, index) {
const updatedValue = this.replacePatterns.reduce((acc, el, i) => {
if (index === i) {
return inputType === 'key' ? { ...acc, [value]: el[1] } : { ...acc, [el[0]]: value }
}
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'mrf_keyword', 'replace')
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,199 +1,14 @@
<template>
<div>
<el-form ref="mailer" :model="mailer" :label-width="labelWidth">
<el-form-item label="Enabled">
<el-switch :value="mailer.enabled" @change="updateSetting($event, 'Pleroma.Emails.Mailer', 'enabled')"/>
<p class="expl">Allows to enable or disable sending emails. Defaults to false.</p>
</el-form-item>
<el-form-item label="Adapter">
<el-select :value="mailer.adapter" clearable @change="updateSetting($event, 'Pleroma.Emails.Mailer', 'adapter')">
<el-option
v-for="item in adapterOptions"
:key="item.value"
:label="item.label"
:value="item.value"/>
</el-select>
</el-form-item>
<div class="line"/>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Sendmail'">
<el-form-item label="CMD Path">
<el-input :value="mailer.cmd_path" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'cmd_path')"/>
<p class="expl">E. g. <span class="code">/usr/bin/sendmail"</span></p>
</el-form-item>
<el-form-item label="CMD Args">
<el-input :value="mailer.cmd_args" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'cmd_args')"/>
<p class="expl">E. g. <span class="code">-N delay,failure,success</span></p>
</el-form-item>
<el-form-item label="Qmail">
<el-switch :value="mailer.qmail" @change="updateSetting($event, 'Pleroma.Emails.Mailer', 'qmail')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.SMTP'">
<el-form-item label="Relay">
<el-input :value="mailer.relay" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'relay')"/>
<p class="expl">E. g. <span class="code">smtp.avengers.com</span></p>
</el-form-item>
<el-form-item label="Username">
<el-input :value="mailer.username" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'username')"/>
</el-form-item>
<el-form-item label="Password">
<el-input :value="mailer.password" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'password')"/>
</el-form-item>
<el-form-item label="SSL">
<el-switch :value="mailer.ssl" @change="updateSetting($event, 'Pleroma.Emails.Mailer', 'ssl')"/>
</el-form-item>
<el-form-item label="TLS">
<el-input :value="mailer.tls" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'tls')"/>
<p class="expl">E.g. <span class="code">:always</span></p>
</el-form-item>
<el-form-item label="Auth">
<el-input :value="mailer.auth" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'auth')"/>
<p class="expl">E.g. <span class="code">:always</span></p>
</el-form-item>
<el-form-item label="Port">
<el-input :value="mailer.port" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'port')"/>
</el-form-item>
<el-form-item label="DKIM">
<editor v-model="editorContent" height="150" width="100%" lang="elixir" theme="chrome"/>
</el-form-item>
<el-form-item label="Retries">
<el-input-number :value="mailer.retries" :step="1" :min="0" size="large" @change="updateSetting($event, 'Pleroma.Emails.Mailer', 'retries')"/>
</el-form-item>
<el-form-item label="No mx lookups">
<el-switch :value="mailer.no_mx_lookups" @change="updateSetting($event, 'Pleroma.Emails.Mailer', 'no_mx_lookups')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Sendgrid'">
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Mandrill'">
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Mailgun'">
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
<el-form-item label="Domain">
<el-input :value="mailer.domain" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'domain')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Mailjet'">
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
<el-form-item label="Secret">
<el-input :value="mailer.secret" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'secret')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Postmark'">
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.SparkPost'">
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
<el-form-item label="Endpoint">
<el-input :value="mailer.endpoint" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'endpoint')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.AmazonSES'">
<el-form-item label="Region">
<el-input :value="mailer.region" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'region')"/>
</el-form-item>
<el-form-item label="Access key">
<el-input :value="mailer.access_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'access_key')"/>
</el-form-item>
<el-form-item label="Secret">
<el-input :value="mailer.secret" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'secret')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Dyn'">
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.SocketLabs'">
<el-form-item label="Server ID">
<el-input :value="mailer.server_id" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'server_id')"/>
</el-form-item>
<el-form-item label="API key">
<el-input :value="mailer.api_key" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'api_key')"/>
</el-form-item>
</div>
<div v-if="mailer.adapter === 'Swoosh.Adapters.Gmail'">
<el-form-item label="Access token">
<el-input :value="mailer.access_token" @input="updateSetting($event, 'Pleroma.Emails.Mailer', 'access_token')"/>
</el-form-item>
</div>
</el-form>
<el-form ref="emailNotifications" :model="emailNotifications" :label-width="labelWidth">
<el-form-item label="Email notifications"/>
<el-form-item label="Digest:"/>
<el-form-item label="Active">
<el-switch :value="emailNotifications.digest.active" @change="processNestedData($event, 'email_notifications', 'digest', 'active')"/>
<p class="expl">Globally enable or disable digest emails.</p>
</el-form-item>
<el-form-item label="Schedule">
<el-input :value="emailNotifications.digest.schedule" @input="processNestedData($event, 'email_notifications', 'digest', 'schedule')"/>
<p class="expl">When to send digest email, in <a
href="https://en.wikipedia.org/wiki/Cron"
rel="nofollow noreferrer noopener"
target="_blank">
crontab format
</a>.
"0 0 * * 0" is the default, meaning "once a week at midnight on Sunday morning"</p>
</el-form-item>
<el-form-item label="Interval">
<el-input-number :value="emailNotifications.digest.interval" :step="1" :min="0" size="large" @change="processNestedData($event, 'email_notifications', 'digest', 'interval')"/>
<p class="expl">Minimum interval between digest emails to one user.</p>
</el-form-item>
<el-form-item label="Inactivity threshold">
<el-input-number :value="emailNotifications.digest.inactivity_threshold" :step="1" :min="0" size="large" @change="processNestedData($event, 'email_notifications', 'digest', 'inactivity_threshold')"/>
<p class="expl">Minimum user inactivity threshold.</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="mailer" :model="mailerData" :label-width="labelWidth">
<setting :setting-group="mailer" :data="mailerData"/>
</el-form>
<div class="line"/>
<el-form ref="emailNotifications" :model="emailNotificationsData" :label-width="labelWidth">
<setting :setting-group="emailNotifications" :data="emailNotificationsData"/>
</el-form>
<el-form ref="userEmail" :model="userEmail" :label-width="labelWidth">
<el-form-item label="Digest template styles:"/>
<el-form-item label="Logo">
<el-input :value="userEmail.logo" @input="updateSetting($event, 'Pleroma.Emails.UserEmail', 'logo')"/>
<div class="upload-container">
<p class="text">or</p>
<el-upload
:http-request="sendEmailLogo"
:multiple="false"
:show-file-list="false"
action="/api/v1/media">
<el-button size="small" type="primary">Click to upload</el-button>
</el-upload>
</div>
<p class="expl">A path to a custom logo.</p>
</el-form-item>
<el-form-item label="Styling:"/>
<el-form-item label="Link color">
<el-input :value="userEmail.styling.link_color" @input="processNestedData($event, 'Pleroma.Emails.UserEmail', 'styling', 'link_color')"/>
</el-form-item>
<el-form-item label="Background color">
<el-input :value="userEmail.styling.background_color" @input="processNestedData($event, 'Pleroma.Emails.UserEmail', 'styling', 'background_color')"/>
</el-form-item>
<el-form-item label="Content background color">
<el-input :value="userEmail.styling.content_background_color" @input="processNestedData($event, 'Pleroma.Emails.UserEmail', 'styling', 'content_background_color')"/>
</el-form-item>
<el-form-item label="Header color">
<el-input :value="userEmail.styling.header_color" @input="processNestedData($event, 'Pleroma.Emails.UserEmail', 'styling', 'header_color')"/>
</el-form-item>
<el-form-item label="Text color">
<el-input :value="userEmail.styling.text_color" @input="processNestedData($event, 'Pleroma.Emails.UserEmail', 'styling', 'text_color')"/>
</el-form-item>
<el-form-item label="Text muted color">
<el-input :value="userEmail.styling.text_muted_color" @input="processNestedData($event, 'Pleroma.Emails.UserEmail', 'styling', 'text_muted_color')"/>
</el-form-item>
<setting :setting-group="userEmail" :data="userEmailData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -203,53 +18,57 @@
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import AceEditor from 'vue2-ace-editor'
import 'brace/mode/elixir'
import 'default-passive-events'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Mailer',
components: {
editor: AceEditor
Setting
},
computed: {
...mapGetters([
'emailNotifications',
'mailer',
'userEmail'
'settings'
]),
editorContent: {
get: function() {
return this.mailer.dkim ? this.mailer.dkim[0] : ''
},
set: function(value) {
this.updateSetting([value], 'Pleroma.Emails.Mailer', 'dkim')
}
emailNotifications() {
return this.settings.description.find(setting => setting.key === ':email_notifications')
},
adapterOptions() {
return options.adapterOptions
emailNotificationsData() {
return this.settings.settings[':pleroma'][':email_notifications']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.$store.state.settings.loading
},
mailer() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.Mailer')
},
mailerData() {
return this.settings.settings[':pleroma']['Pleroma.Emails.Mailer']
},
userEmail() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.UserEmail')
},
userEmailData() {
return this.settings.settings[':pleroma']['Pleroma.Emails.UserEmail']
}
},
methods: {
processNestedData(value, tab, inputName, childName) {
const updatedValue = { ...this.$store.state.settings.settings[tab][inputName], ...{ [childName]: value }}
this.updateSetting(updatedValue, tab, inputName)
},
sendEmailLogo({ file }) {
this.$store.dispatch('UploadMedia', { file, tab: 'Pleroma.Emails.UserEmail', inputName: 'logo' })
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,73 +1,6 @@
<template>
<el-form ref="mediaProxy" :model="mediaProxy" :label-width="labelWidth">
<el-form-item label="Enabled">
<el-switch :value="mediaProxy.enabled" @change="updateSetting($event, 'media_proxy', 'enabled')"/>
<p class="expl">Enables proxying of remote media to the instances proxy</p>
</el-form-item>
<el-form-item label="Base URL">
<el-input :value="mediaProxy.base_url" @input="updateSetting($event, 'media_proxy', 'base_url')"/>
<p class="expl">The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.</p>
</el-form-item>
<div class="line"/>
<el-form-item label="Proxy options:"/>
<el-form-item label="Redirect on failure">
<el-switch :value="mediaProxy.proxy_opts.redirect_on_failure" @change="processNestedData($event, 'media_proxy', 'proxy_opts', 'redirect_on_failure')"/>
<p class="expl">Redirects the client to the real remote URL if there's any HTTP errors. Any error during body processing will not be redirected as the response is chunked</p>
</el-form-item>
<el-form-item label="Max body length (MB)">
<el-input-number :value="mediaProxy.proxy_opts.max_body_length / 1048576" :step="1" :min="0" size="large" @change="processNestedData($event * 1048576, 'media_proxy', 'proxy_opts', 'max_body_length')"/>
<p class="expl">Limits the content length to be approximately the specified length</p>
</el-form-item>
<el-form-item label="Max read duration (s)">
<el-input-number :value="mediaProxy.proxy_opts.max_read_duration" :step="1" :min="0" size="large" @change="processNestedData($event, 'media_proxy', 'proxy_opts', 'max_read_duration')"/>
<p class="expl">The total time the connection is allowed to read from the remote upstream</p>
</el-form-item>
<el-form-item label="Inline content types">
<el-select :value="inlineContentTypes" clearable @change="processNestedData($event, 'media_proxy', 'proxy_opts', 'inline_content_types')">
<el-option :value="true" label="True"/>
<el-option :value="false" label="False"/>
<el-option value="whitelistedTypeArray" label="List of whitelisted content types"/>
<el-option value="keepUserAgent" label="Forward client's user-agent to the upstream"/>
</el-select>
<p v-if="inlineContentTypes === true" class="expl">Will not alter <span class="code">content-disposition</span> (up to the upstream)</p>
<p v-if="!inlineContentTypes" class="expl">Will add <span class="code">content-disposition: attachment</span> to any request</p>
<p v-if="inlineContentTypes === 'keepUserAgent'" class="expl">
Will forward the client's user-agent to the upstream. This may be useful if the upstream is
doing content transformation (encoding, ) depending on the request.
</p>
</el-form-item>
<el-form-item v-if="inlineContentTypes === 'whitelistedTypeArray'" label="Whitelisted content types">
<el-select :value="whitelistedContentTypes" multiple filterable allow-create @change="processNestedData($event, 'media_proxy', 'proxy_opts', 'inline_content_types')">
<el-option
v-for="item in whitelistedContentTypesOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form-item label="Request headers">
<el-select :value="mediaProxy.proxy_opts.req_headers || []" multiple filterable allow-create @change="processNestedData($event, 'media_proxy', 'proxy_opts', 'req_headers')"/>
<p class="expl"><span class="code">resp_headers</span> additional headers</p>
</el-form-item>
<el-form-item label="HTTP:"/>
<el-form-item label="Follow redirect">
<el-switch :value="http.follow_redirect" @change="processHttpSettings($event, 'media_proxy', 'proxy_opts', 'http', 'follow_redirect')"/>
</el-form-item>
<el-form-item label="Pool">
<el-select :value="http.pool" clearable @change="processHttpSettings($event, 'media_proxy', 'proxy_opts', 'http', 'pool')">
<el-option
v-for="item in hackneyPoolsOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
<p class="expl">{{ getPoolExpl(http.pool) }}</p>
</el-form-item>
<div class="line"/>
<el-form-item label="Whitelist">
<el-select :value="mediaProxy.whitelist || []" multiple filterable allow-create @change="updateSetting($event, 'media_proxy', 'whitelist')"/>
<p class="expl">List of domains to bypass the mediaproxy</p>
</el-form-item>
<el-form v-if="!loading" ref="mediaProxy" :model="mediaProxyData" :label-width="labelWidth">
<setting :setting-group="mediaProxy" :data="mediaProxyData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -76,57 +9,43 @@
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'MediaProxy',
components: { Setting },
computed: {
...mapGetters([
'mediaProxy'
'settings'
]),
inlineContentTypes() {
return Array.isArray(this.mediaProxy.proxy_opts.inline_content_types) ? 'whitelistedTypeArray' : this.mediaProxy.proxy_opts.inline_content_types
},
http() {
return this.mediaProxy.proxy_opts.http || {}
},
reqHeadersOptions() {
return this.mediaProxySettings.reqHeadersOptions
},
hackneyPoolsOptions() {
return options.hackneyPoolsOptions
},
whitelistedContentTypes() {
return Array.isArray(this.mediaProxy.proxy_opts.inline_content_types) ? this.mediaProxy.proxy_opts.inline_content_types : []
},
whitelistedContentTypesOptions() {
return options.whitelistedContentTypesOptions
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
mediaProxy() {
return this.settings.description.find(setting => setting.key === ':media_proxy')
},
mediaProxyData() {
return this.settings.settings[':pleroma'][':media_proxy']
}
},
methods: {
getPoolExpl(value) {
const pool = this.hackneyPoolsOptions.find(el => el.value === value)
return pool ? 'Max connections: ' + pool.max_connections + ', timeout: ' + pool.timeout : ''
},
processHttpSettings(value, tab, section, httpSection, input) {
const updatedValue = { ...this.mediaProxy[section][httpSection], ...{ [input]: value }}
this.processNestedData(updatedValue, tab, section, httpSection)
},
processNestedData(value, tab, section, input) {
const updatedValue = { ...this.$store.state.settings.settings[tab][section], ...{ [input]: value }}
this.updateSetting(updatedValue, tab, section)
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,48 +1,11 @@
<template>
<div>
<el-form ref="metadata" :model="metadata" :label-width="labelWidth">
<el-form-item label="Providers">
<el-select :value="metadata.providers || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.Web.Metadata', 'providers')">
<el-option value="Pleroma.Web.Metadata.Providers.OpenGraph"/>
<el-option value="Pleroma.Web.Metadata.Providers.TwitterCard"/>
<el-option value="Pleroma.Web.Metadata.Providers.RelMe"/>
</el-select>
<p class="expl">A list of metadata providers to enable.</p>
</el-form-item>
<el-form-item label="Unfurl NSFW">
<el-switch :value="metadata.unfurl_nsfw" @change="updateSetting($event, 'Pleroma.Web.Metadata', 'unfurl_nsfw')"/>
<p class="expl">If set to true nsfw attachments will be shown in previews.</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="metadata" :model="metadataData" :label-width="labelWidth">
<setting :setting-group="metadata" :data="metadataData"/>
</el-form>
<div class="line"/>
<el-form ref="richMedia" :model="richMedia" :label-width="labelWidth">
<el-form-item label="Rich media:"/>
<el-form-item label="Enabled">
<el-switch :value="richMedia.enabled" @change="updateSetting($event, 'rich_media', 'enabled')"/>
<p class="expl">If enabled the instance will parse metadata from attached links to generate link previews.</p>
</el-form-item>
<el-form-item label="Ignore hosts">
<el-select :value="richMedia.ignore_hosts || []" multiple filterable allow-create @change="updateSetting($event, 'rich_media', 'ignore_hosts')"/>
<p class="expl">List of hosts which will be ignored by the metadata parser.</p>
</el-form-item>
<el-form-item label="Ignore TLD">
<el-select :value="richMedia.ignore_tld || []" multiple filterable allow-create @change="updateSetting($event, 'rich_media', 'ignore_tld')"/>
<p class="expl">List TLDs (top-level domains) which will ignore for parse metadata.
Default is <span class="code">["local", "localdomain", "lan"]</span></p>
</el-form-item>
<el-form-item label="Parsers">
<el-select :value="richMedia.parsers || []" multiple filterable allow-create @change="updateSetting($event, 'rich_media', 'parsers')">
<el-option value="Pleroma.Web.RichMedia.Parsers.TwitterCard"/>
<el-option value="Pleroma.Web.RichMedia.Parsers.OGP"/>
<el-option value="Pleroma.Web.RichMedia.Parsers.OEmbed"/>
</el-select>
<p class="expl">List of Rich Media parsers</p>
</el-form-item>
<el-form-item label="TTL Setters">
<el-select :value="richMedia.ttl_setters || []" multiple filterable allow-create @change="updateSetting($event, 'rich_media', 'ttl_setters')">
<el-option value="Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl"/>
</el-select>
</el-form-item>
<el-form ref="richMedia" :model="richMediaData" :label-width="labelWidth">
<setting :setting-group="richMedia" :data="richMediaData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -52,27 +15,49 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Metadata',
components: { Setting },
computed: {
...mapGetters([
'metadata',
'richMedia'
'settings'
]),
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
metadata() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Metadata')
},
metadataData() {
return this.settings.settings[':pleroma']['Pleroma.Web.Metadata']
},
richMedia() {
return this.settings.description.find(setting => setting.key === ':rich_media')
},
richMediaData() {
return this.settings.settings[':pleroma'][':rich_media']
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,27 +1,11 @@
<template>
<div>
<el-form ref="formatEncoders" :model="formatEncoders" :label-width="labelWidth">
<el-form-item label="Phoenix Format encoders:"/>
<el-form-item label="JSON">
<el-input :value="formatEncoders.json" @input="updateSetting($event, 'format_encoders', 'json')"/>
</el-form-item>
<div v-if="!loading">
<el-form ref="teslaAdapter" :model="teslaAdapterData" :label-width="labelWidth">
<setting :setting-group="teslaAdapter" :data="teslaAdapterData"/>
</el-form>
<div class="line"/>
<el-form ref="teslaAdapter" :model="teslaAdapter" :label-width="labelWidth">
<el-form-item label="Tesla adapter">
<el-input :value="teslaAdapter.value" @input="updateSetting($event, 'adapter', 'value')"/>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="mimeTypesConfig" :model="mimeTypesConfig" :label-width="labelWidth">
<el-form-item label="Mime types">
<div v-for="([type, value], index) in mimeTypes" :key="index" class="setting-input">
<el-input :value="type" placeholder="type" class="name-input" @input="parseMimeTypes($event, 'type', index)"/> :
<el-select :value="value" multiple filterable allow-create class="value-input" @change="parseMimeTypes($event, 'value', index)"/>
<el-button icon="el-icon-minus" circle @click="deleteMimeTypes(index)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addRowToMimeTypes"/>
</el-form-item>
<el-form ref="mimeTypes" :model="mimeTypesData" :label-width="labelWidth">
<setting :setting-group="mimeTypes" :data="mimeTypesData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -31,53 +15,49 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Other',
components: { Setting },
computed: {
...mapGetters([
'formatEncoders',
'mimeTypesConfig',
'teslaAdapter'
'settings'
]),
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
mimeTypes() {
return Object.keys(this.mimeTypesConfig.value).map(key => [key, this.mimeTypesConfig.value[key]])
return this.settings.description.find(setting => setting.group === ':mime')
},
mimeTypesData() {
return this.settings.settings[':mime']
},
teslaAdapter() {
return this.settings.description.find(setting => setting.group === ':tesla')
},
teslaAdapterData() {
return this.settings.settings[':tesla']
}
},
methods: {
addRowToMimeTypes() {
const updatedValue = this.mimeTypes.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting({ ...updatedValue, '': [] }, 'types', 'value')
},
deleteMimeTypes(index) {
const filteredValues = this.mimeTypes.filter((el, i) => index !== i)
const updatedValue = filteredValues.reduce((acc, el, i) => {
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'types', 'value')
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
},
parseMimeTypes(value, inputType, index) {
const updatedValue = this.mimeTypes.reduce((acc, el, i) => {
if (index === i) {
return inputType === 'type' ? { ...acc, [value]: el[1] } : { ...acc, [el[0]]: value }
}
return { ...acc, [el[0]]: el[1] }
}, {})
this.updateSetting(updatedValue, 'types', 'value')
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,221 +1,6 @@
<template>
<el-form ref="rateLimiters" :model="rateLimiters" :label-width="labelWidth">
<el-form-item label="Search:">
<div v-if="!searchLimitAuthUsers">
<el-input :value="searchLimitAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'search', 'scale', 'oneLimit', searchLimitAllUsers)"/> :
<el-input :value="searchLimitAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'search', 'limit', 'oneLimit', searchLimitAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'search')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="searchLimitAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="searchLimitAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'search', 'scale', 'authUserslimit', [searchLimitUnauthUsers, searchLimitAuthUsers])"/> :
<el-input :value="searchLimitAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'search', 'limit', 'authUserslimit', [searchLimitUnauthUsers, searchLimitAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="searchLimitUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'search', 'scale', 'unauthUsersLimit', [searchLimitUnauthUsers, searchLimitAuthUsers])"/> :
<el-input :value="searchLimitUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'search', 'limit', 'unauthUsersLimit', [searchLimitUnauthUsers, searchLimitAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'search')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="App account creation:">
<div v-if="!appAccountCreationAuthUsers">
<el-input :value="appAccountCreationAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'app_account_creation', 'scale', 'oneLimit', appAccountCreationAllUsers)"/> :
<el-input :value="appAccountCreationAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'app_account_creation', 'limit', 'oneLimit', appAccountCreationAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'app_account_creation')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="appAccountCreationAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="appAccountCreationAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'app_account_creation', 'scale', 'authUserslimit', [appAccountCreationUnauthUsers, appAccountCreationAuthUsers])"/> :
<el-input :value="appAccountCreationAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'app_account_creation', 'limit', 'authUserslimit', [appAccountCreationUnauthUsers, appAccountCreationAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="appAccountCreationUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'app_account_creation', 'scale', 'unauthUsersLimit', [appAccountCreationUnauthUsers, appAccountCreationAuthUsers])"/> :
<el-input :value="appAccountCreationUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'app_account_creation', 'limit', 'unauthUsersLimit', [appAccountCreationUnauthUsers, appAccountCreationAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'app_account_creation')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="Relations actions:">
<div v-if="!relationsActionsAuthUsers">
<el-input :value="relationsActionsAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'relations_actions', 'scale', 'oneLimit', relationsActionsAllUsers)"/> :
<el-input :value="relationsActionsAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'relations_actions', 'limit', 'oneLimit', relationsActionsAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'relations_actions')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="relationsActionsAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="relationsActionsAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'relations_actions', 'scale', 'authUserslimit', [relationsActionsUnauthUsers, relationsActionsAuthUsers])"/> :
<el-input :value="relationsActionsAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'relations_actions', 'limit', 'authUserslimit', [relationsActionsUnauthUsers, relationsActionsAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="relationsActionsUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'relations_actions', 'scale', 'unauthUsersLimit', [relationsActionsUnauthUsers, relationsActionsAuthUsers])"/> :
<el-input :value="relationsActionsUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'relations_actions', 'limit', 'unauthUsersLimit', [relationsActionsUnauthUsers, relationsActionsAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'relations_actions')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="Relation ID Action:">
<div v-if="!relationIdActionAuthUsers">
<el-input :value="relationIdActionAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'relation_id_action', 'scale', 'oneLimit', relationIdActionAllUsers)"/> :
<el-input :value="relationIdActionAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'relation_id_action', 'limit', 'oneLimit', relationIdActionAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'relation_id_action')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="relationIdActionAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="relationIdActionAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'relation_id_action', 'scale', 'authUserslimit', [relationIdActionUnauthUsers, relationIdActionAuthUsers])"/> :
<el-input :value="relationIdActionAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'relation_id_action', 'limit', 'authUserslimit', [relationIdActionUnauthUsers, relationIdActionAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="relationIdActionUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'relation_id_action', 'scale', 'unauthUsersLimit', [relationIdActionUnauthUsers, relationIdActionAuthUsers])"/> :
<el-input :value="relationIdActionUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'relation_id_action', 'limit', 'unauthUsersLimit', [relationIdActionUnauthUsers, relationIdActionAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'relation_id_action')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="Statuses actions:">
<div v-if="!statusesActionsAuthUsers">
<el-input :value="statusesActionsAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'statuses_actions', 'scale', 'oneLimit', statusesActionsAllUsers)"/> :
<el-input :value="statusesActionsAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'statuses_actions', 'limit', 'oneLimit', statusesActionsAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'statuses_actions')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="statusesActionsAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="statusesActionsAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'statuses_actions', 'scale', 'authUserslimit', [statusesActionsUnauthUsers, statusesActionsAuthUsers])"/> :
<el-input :value="statusesActionsAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'statuses_actions', 'limit', 'authUserslimit', [statusesActionsUnauthUsers, statusesActionsAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="statusesActionsUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'statuses_actions', 'scale', 'unauthUsersLimit', [statusesActionsUnauthUsers, statusesActionsAuthUsers])"/> :
<el-input :value="statusesActionsUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'statuses_actions', 'limit', 'unauthUsersLimit', [statusesActionsUnauthUsers, statusesActionsAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'statuses_actions')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="Status ID Action:">
<div v-if="!statusIdActionAuthUsers">
<el-input :value="statusIdActionAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'status_id_action', 'scale', 'oneLimit', statusIdActionAllUsers)"/> :
<el-input :value="statusIdActionAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'status_id_action', 'limit', 'oneLimit', statusIdActionAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'status_id_action')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="statusIdActionAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="statusIdActionAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'status_id_action', 'scale', 'authUserslimit', [statusIdActionUnauthUsers, statusIdActionAuthUsers])"/> :
<el-input :value="statusIdActionAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'status_id_action', 'limit', 'authUserslimit', [statusIdActionUnauthUsers, statusIdActionAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="statusIdActionUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'status_id_action', 'scale', 'unauthUsersLimit', [statusIdActionUnauthUsers, statusIdActionAuthUsers])"/> :
<el-input :value="statusIdActionUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'status_id_action', 'limit', 'unauthUsersLimit', [statusIdActionUnauthUsers, statusIdActionAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'status_id_action')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="Password reset:">
<div v-if="!passwordResetAuthUsers">
<el-input :value="passwordResetAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'password_reset', 'scale', 'oneLimit', passwordResetAllUsers)"/> :
<el-input :value="passwordResetAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'password_reset', 'limit', 'oneLimit', passwordResetAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'password_reset')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="passwordResetAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="passwordResetAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'password_reset', 'scale', 'authUserslimit', [passwordResetUnauthUsers, passwordResetAuthUsers])"/> :
<el-input :value="passwordResetAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'password_reset', 'limit', 'authUserslimit', [passwordResetUnauthUsers, passwordResetAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="passwordResetUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'password_reset', 'scale', 'unauthUsersLimit', [passwordResetUnauthUsers, passwordResetAuthUsers])"/> :
<el-input :value="passwordResetUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'password_reset', 'limit', 'unauthUsersLimit', [passwordResetUnauthUsers, passwordResetAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'password_reset')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="Account confirmation resend:">
<div v-if="!accountConfirmationResendAuthUsers">
<el-input :value="accountConfirmationResendAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'account_confirmation_resend', 'scale', 'oneLimit', accountConfirmationResendAllUsers)"/> :
<el-input :value="accountConfirmationResendAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'account_confirmation_resend', 'limit', 'oneLimit', accountConfirmationResendAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'account_confirmation_resend')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="accountConfirmationResendAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="accountConfirmationResendAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'account_confirmation_resend', 'scale', 'authUserslimit', [accountConfirmationResendUnauthUsers, accountConfirmationResendAuthUsers])"/> :
<el-input :value="accountConfirmationResendAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'account_confirmation_resend', 'limit', 'authUserslimit', [accountConfirmationResendUnauthUsers, accountConfirmationResendAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="accountConfirmationResendUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'account_confirmation_resend', 'scale', 'unauthUsersLimit', [accountConfirmationResendUnauthUsers, accountConfirmationResendAuthUsers])"/> :
<el-input :value="accountConfirmationResendUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'account_confirmation_resend', 'limit', 'unauthUsersLimit', [accountConfirmationResendUnauthUsers, accountConfirmationResendAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'account_confirmation_resend')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form-item label="Activity pub routes:">
<div v-if="!activityPubRoutesAuthUsers">
<el-input :value="activityPubRoutesAllUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'ap_routes', 'scale', 'oneLimit', activityPubRoutesAllUsers)"/> :
<el-input :value="activityPubRoutesAllUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'ap_routes', 'limit', 'oneLimit', activityPubRoutesAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([{ 'tuple': [null, null] }, { 'tuple': [null, null] }], 'ap_routes')"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="activityPubRoutesAuthUsers">
<el-form-item label="Authenticated users:">
<el-input :value="activityPubRoutesAuthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'ap_routes', 'scale', 'authUserslimit', [activityPubRoutesUnauthUsers, activityPubRoutesAuthUsers])"/> :
<el-input :value="activityPubRoutesAuthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'ap_routes', 'limit', 'authUserslimit', [activityPubRoutesUnauthUsers, activityPubRoutesAuthUsers])"/>
</el-form-item>
<el-form-item label="Unauthenticated users:">
<el-input :value="activityPubRoutesUnauthUsers[0]" placeholder="scale" class="scale-input" @input="parseRateLimiter($event, 'ap_routes', 'scale', 'unauthUsersLimit', [activityPubRoutesUnauthUsers, activityPubRoutesAuthUsers])"/> :
<el-input :value="activityPubRoutesUnauthUsers[1]" placeholder="limit" class="limit-input" @input="parseRateLimiter($event, 'ap_routes', 'limit', 'unauthUsersLimit', [activityPubRoutesUnauthUsers, activityPubRoutesAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits({ 'tuple': [null, null] }, 'ap_routes')"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</el-form-item>
<el-form v-if="!loading" ref="rateLimiters" :model="rateLimitersData" :label-width="labelWidth">
<setting :setting-group="rateLimiters" :data="rateLimitersData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -224,162 +9,43 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'RateLimiters',
components: { Setting },
computed: {
...mapGetters([
'rateLimiters'
'settings'
]),
accountConfirmationResendAllUsers() {
return this.rateLimiters.account_confirmation_resend ? this.rateLimiters.account_confirmation_resend.tuple : [null, null]
rateLimiters() {
return this.settings.description.find(setting => setting.key === ':rate_limit')
},
accountConfirmationResendAuthUsers() {
return Array.isArray(this.rateLimiters.account_confirmation_resend)
? this.rateLimiters.account_confirmation_resend[1].tuple
: false
},
accountConfirmationResendUnauthUsers() {
return Array.isArray(this.rateLimiters.account_confirmation_resend)
? this.rateLimiters.account_confirmation_resend[0].tuple
: false
},
activityPubRoutesAllUsers() {
return this.rateLimiters.ap_routes ? this.rateLimiters.ap_routes.tuple : [null, null]
},
activityPubRoutesAuthUsers() {
return Array.isArray(this.rateLimiters.ap_routes)
? this.rateLimiters.ap_routes[1].tuple
: false
},
activityPubRoutesUnauthUsers() {
return Array.isArray(this.rateLimiters.ap_routes)
? this.rateLimiters.ap_routes[0].tuple
: false
},
appAccountCreationAllUsers() {
return this.rateLimiters.app_account_creation ? this.rateLimiters.app_account_creation.tuple : [null, null]
},
appAccountCreationAuthUsers() {
return Array.isArray(this.rateLimiters.app_account_creation)
? this.rateLimiters.app_account_creation[1].tuple
: false
},
appAccountCreationUnauthUsers() {
return Array.isArray(this.rateLimiters.app_account_creation)
? this.rateLimiters.app_account_creation[0].tuple
: false
},
passwordResetAllUsers() {
return this.rateLimiters.password_reset ? this.rateLimiters.password_reset.tuple : [null, null]
},
passwordResetAuthUsers() {
return Array.isArray(this.rateLimiters.password_reset)
? this.rateLimiters.password_reset[1].tuple
: false
},
passwordResetUnauthUsers() {
return Array.isArray(this.rateLimiters.password_reset)
? this.rateLimiters.password_reset[0].tuple
: false
},
relationsActionsAllUsers() {
return this.rateLimiters.relations_actions ? this.rateLimiters.relations_actions.tuple : [null, null]
},
relationsActionsAuthUsers() {
return Array.isArray(this.rateLimiters.relations_actions)
? this.rateLimiters.relations_actions[1].tuple
: false
},
relationsActionsUnauthUsers() {
return Array.isArray(this.rateLimiters.relations_actions)
? this.rateLimiters.relations_actions[0].tuple
: false
},
relationIdActionAllUsers() {
return this.rateLimiters.relation_id_action ? this.rateLimiters.relation_id_action.tuple : [null, null]
},
relationIdActionAuthUsers() {
return Array.isArray(this.rateLimiters.relation_id_action)
? this.rateLimiters.relation_id_action[1].tuple
: false
},
relationIdActionUnauthUsers() {
return Array.isArray(this.rateLimiters.relation_id_action)
? this.rateLimiters.relation_id_action[0].tuple
: false
},
searchLimitAllUsers() {
return this.rateLimiters.search ? this.rateLimiters.search.tuple : [null, null]
},
searchLimitAuthUsers() {
return Array.isArray(this.rateLimiters.search)
? this.rateLimiters.search[1].tuple
: false
},
searchLimitUnauthUsers() {
return Array.isArray(this.rateLimiters.search)
? this.rateLimiters.search[0].tuple
: false
},
statusesActionsAllUsers() {
return this.rateLimiters.statuses_actions ? this.rateLimiters.statuses_actions.tuple : [null, null]
},
statusesActionsAuthUsers() {
return Array.isArray(this.rateLimiters.statuses_actions)
? this.rateLimiters.statuses_actions[1].tuple
: false
},
statusesActionsUnauthUsers() {
return Array.isArray(this.rateLimiters.statuses_actions)
? this.rateLimiters.statuses_actions[0].tuple
: false
},
statusIdActionAllUsers() {
return this.rateLimiters.status_id_action ? this.rateLimiters.status_id_action.tuple : [null, null]
},
statusIdActionAuthUsers() {
return Array.isArray(this.rateLimiters.status_id_action)
? this.rateLimiters.status_id_action[1].tuple
: false
},
statusIdActionUnauthUsers() {
return Array.isArray(this.rateLimiters.status_id_action)
? this.rateLimiters.status_id_action[0].tuple
: false
rateLimitersData() {
return this.settings.settings[':pleroma'][':rate_limit']
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.$store.state.settings.loading
}
},
methods: {
parseRateLimiter(value, input, typeOfInput, typeOfLimit, currentValue) {
if (typeOfLimit === 'oneLimit') {
const valueToSend = typeOfInput === 'scale' ? { 'tuple': [value, currentValue[1]] } : { 'tuple': [currentValue[0], value] }
this.updateSetting(valueToSend, 'rate_limit', input)
} else if (typeOfLimit === 'authUserslimit') {
const valueToSend = typeOfInput === 'scale'
? [{ 'tuple': [currentValue[0][0], currentValue[0][1]] }, { 'tuple': [value, currentValue[1][1]] }]
: [{ 'tuple': [currentValue[0][0], currentValue[0][1]] }, { 'tuple': [currentValue[1][0], value] }]
this.updateSetting(valueToSend, 'rate_limit', input)
} else if (typeOfLimit === 'unauthUsersLimit') {
const valueToSend = typeOfInput === 'scale'
? [{ 'tuple': [value, currentValue[0][1]] }, { 'tuple': [currentValue[1][0], currentValue[1][1]] }]
: [{ 'tuple': [currentValue[0][0], value] }, { 'tuple': [currentValue[1][0], currentValue[1][1]] }]
this.updateSetting(valueToSend, 'rate_limit', input)
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
},
toggleLimits(value, input) {
this.updateSetting(value, 'rate_limit', input)
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -0,0 +1,103 @@
<template>
<div v-if="!loading">
<el-form-item v-if="settingGroup.description" class="description-container">
<p class="description">{{ settingGroup.description }}</p>
</el-form-item>
<div v-if="settingGroup.key === 'Pleroma.Emails.Mailer'">
<div v-for="setting in settingGroup.children.filter(setting => !setting.group)" :key="setting.key">
<inputs
:setting-group="settingGroup"
:setting="setting"
:data="data"/>
</div>
<div
v-for="setting in emailAdapterChildren"
:key="setting.key">
<inputs
:setting-group="settingGroup"
:setting="setting"
:data="data"/>
</div>
</div>
<div v-else>
<div v-for="setting in settingGroup.children" :key="setting.key">
<div v-if="!compound(setting)">
<inputs
:setting-group="settingGroup"
:setting="setting"
:data="data"
:nested="false"/>
</div>
<div v-if="compound(setting)">
<el-form-item :label="`${setting.label}:`"/>
<div v-for="subSetting in setting.children" :key="subSetting.key">
<inputs
:setting-group="settingGroup"
:setting-parent="[setting, subSetting]"
:setting="subSetting"
:data="data[setting.key]"
:nested="true"/>
</div>
<div v-if="!setting.children">
<inputs :setting-group="settingGroup" :setting="setting" :data="data[setting.key]" :nested="true"/>
</div>
<div class="line"/>
</div>
</div>
</div>
</div>
</template>
<script>
import AceEditor from 'vue2-ace-editor'
import Inputs from './Inputs'
import 'brace/mode/elixir'
import 'default-passive-events'
export default {
name: 'Setting',
components: {
editor: AceEditor,
Inputs
},
props: {
settingGroup: {
type: Object,
default: function() {
return {}
}
},
data: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
emailAdapterChildren() {
const adapter = this.$store.state.settings.settings[':pleroma']['Pleroma.Emails.Mailer'][':adapter']
return this.settingGroup.children.filter(child => child.group && child.group.includes(adapter))
},
loading() {
return this.$store.state.settings.loading
}
},
methods: {
compound({ type, key, children }) {
return type === 'keyword' ||
type === 'map' ||
type.includes('keyword') ||
key === ':replace'
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View file

@ -1,141 +1,26 @@
<template>
<div>
<el-form ref="upload" :model="upload" :label-width="labelWidth">
<el-form-item label="Uploader">
<el-input :value="upload.uploader" @input="updateSetting($event, 'Pleroma.Upload', 'uploader')"/>
</el-form-item>
<el-form-item label="Filters">
<el-select :value="upload.filters || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.Upload', 'filters')"/>
</el-form-item>
<el-form-item label="Link name">
<el-switch :value="upload.link_name" @change="updateSetting($event, 'Pleroma.Upload', 'link_name')"/>
<p class="expl">When enabled Pleroma will add a name parameter to the url of the upload, for example
<span class="code">https://instance.tld/media/corndog.png?name=corndog.png</span></p>
</el-form-item>
<el-form-item label="Base URL">
<el-input :value="upload.base_url" @input="updateSetting($event, 'Pleroma.Upload', 'base_url')"/>
<p class="expl">The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host</p>
</el-form-item>
<el-form-item label="Proxy remote">
<el-switch :value="upload.proxy_remote" @change="updateSetting($event, 'Pleroma.Upload', 'proxy_remote')"/>
<p class="expl">If you're using a remote uploader, Pleroma will proxy media requests instead of redirecting to it</p>
</el-form-item>
<div class="line"/>
<el-form-item label="Proxy options:"/>
<el-form-item label="Redirect on failure">
<el-switch :value="upload.proxy_opts.redirect_on_failure" @change="processNestedData($event, 'Pleroma.Upload', 'proxy_opts', 'redirect_on_failure')"/>
<p class="expl">Redirects the client to the real remote URL if there's any HTTP errors.
Any error during body processing will not be redirected as the response is chunked</p>
</el-form-item>
<el-form-item label="Max body length (MB)">
<el-input-number :value="upload.proxy_opts.max_body_length / 1048576" :step="1" :min="0" size="large" @change="processNestedData($event * 1048576, 'Pleroma.Upload', 'proxy_opts', 'max_body_length')"/>
<p class="expl">Limits the content length to be approximately the specified length</p>
</el-form-item>
<el-form-item label="Max read duration (s)">
<el-input-number :value="upload.proxy_opts.max_read_duration" :step="1" :min="0" size="large" @change="processNestedData($event, 'Pleroma.Upload', 'proxy_opts', 'max_read_duration')"/>
<p class="expl">The total time the connection is allowed to read from the remote upstream</p>
</el-form-item>
<el-form-item label="Inline content types">
<el-select :value="inlineContentTypes" clearable @change="processNestedData($event, 'Pleroma.Upload', 'proxy_opts', 'inline_content_types')">
<el-option :value="true" label="True"/>
<el-option :value="false" label="False"/>
<el-option value="whitelistedTypeArray" label="List of whitelisted content types"/>
<el-option value="keepUserAgent" label="Forward client's user-agent to the upstream"/>
</el-select>
<p v-if="inlineContentTypes === true" class="expl">Will not alter <span class="code">content-disposition</span> (up to the upstream)</p>
<p v-if="!inlineContentTypes" class="expl">Will add <span class="code">content-disposition: attachment</span> to any request</p>
<p v-if="inlineContentTypes === 'keepUserAgent'" class="expl">
Will forward the client's user-agent to the upstream. This may be useful if the upstream is
doing content transformation (encoding, ) depending on the request.
</p>
</el-form-item>
<el-form-item v-if="inlineContentTypes === 'whitelistedTypeArray'" label="Whitelisted content types">
<el-select :value="whitelistedContentTypes" multiple filterable allow-create @change="processNestedData($event, 'Pleroma.Upload', 'proxy_opts', 'inline_content_types')">
<el-option
v-for="item in whitelistedContentTypesOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form-item label="Request headers">
<el-select :value="upload.proxy_opts.req_headers || []" multiple filterable allow-create @change="processNestedData($event, 'Pleroma.Upload', 'proxy_opts', 'req_headers')"/>
<p class="expl"><span class="code">resp_headers</span> additional headers</p>
</el-form-item>
<el-form-item label="HTTP:"/>
<el-form-item label="Follow redirect">
<el-switch :value="http.follow_redirect" @change="processHttpSettings($event, 'Pleroma.Upload', 'proxy_opts', 'http', 'follow_redirect')"/>
</el-form-item>
<el-form-item label="Pool">
<el-select :value="http.pool" clearable @change="processHttpSettings($event, 'Pleroma.Upload', 'proxy_opts', 'http', 'pool')">
<el-option
v-for="item in hackneyPoolsOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
<p class="expl">{{ getPoolExpl(http.pool) }}</p>
</el-form-item>
<div v-if="!loading">
<el-form ref="uploadData" :model="uploadData" :label-width="labelWidth">
<setting :setting-group="upload" :data="uploadData"/>
</el-form>
<el-form ref="uploadersLocal" :model="uploadersLocalData" :label-width="labelWidth">
<setting :setting-group="uploadersLocal" :data="uploadersLocalData"/>
</el-form>
<div class="line"/>
<el-form ref="uploadersLocal" :model="uploadersLocal" :label-width="labelWidth">
<el-form-item label="Uploaders.Local:"/>
<el-form-item label="Directory for user-uploads">
<el-input :value="uploadersLocal.uploads" @input="updateSetting($event, 'Pleroma.Uploaders.Local', 'uploads')"/>
<p class="expl">Which directory to store the user-uploads in, relative to pleromas working directory</p>
</el-form-item>
<el-form ref="uploadersS3" :model="uploadersS3Data" :label-width="labelWidth">
<setting :setting-group="uploadersS3" :data="uploadersS3Data"/>
</el-form>
<div class="line"/>
<el-form ref="uploadFilterMogrify" :model="uploadFilterMogrify" :label-width="labelWidth">
<el-form-item label="Actions for Mogrify">
<el-select :value="uploadFilterMogrify.args || []" multiple filterable allow-create @change="updateSetting($event, 'Pleroma.Upload.Filter.Mogrify', 'args')">
<el-option
v-for="item in mogrifyActionsOptions"
:label="item.label"
:key="item.value"
:value="item.value"/>
</el-select>
</el-form-item>
<el-form ref="uploadersMDII" :model="uploadersMDIIData" :label-width="labelWidth">
<setting :setting-group="uploadersMDII" :data="uploadersMDIIData"/>
</el-form>
<div class="line"/>
<el-form ref="uploadAnonymizeFilename" :model="uploadAnonymizeFilename" :label-width="labelWidth">
<el-form-item label="Anonymize filename">
<el-input :value="uploadAnonymizeFilename.text" @input="updateSetting($event, 'Pleroma.Upload.Filter.AnonymizeFilename', 'text')"/>
<p class="expl">Text to replace filenames in links. If empty, <span class="code">{random}.extension</span> will be used</p>
</el-form-item>
<el-form ref="uploadFilterMogrify" :model="uploadFilterMogrifyData" :label-width="labelWidth">
<setting :setting-group="uploadFilterMogrify" :data="uploadFilterMogrifyData"/>
</el-form>
<div class="line"/>
<el-form ref="uploadS3" :model="uploadS3" :label-width="labelWidth">
<el-form-item label="S3 Config:"/>
<el-form-item label="Bucket">
<el-input :value="uploadS3.bucket" @input="updateSetting($event, 'Pleroma.Uploaders.S3', 'bucket')"/>
<p class="expl">S3 bucket name</p>
</el-form-item>
<el-form-item label="Bucket namespace">
<el-input :value="uploadS3.bucket_namespace" @input="updateSetting($event, 'Pleroma.Uploaders.S3', 'bucket_namespace')"/>
<p class="expl">S3 bucket namespace</p>
</el-form-item>
<el-form-item label="Public endpoint">
<el-input :value="uploadS3.public_endpoint" @input="updateSetting($event, 'Pleroma.Uploaders.S3', 'public_endpoint')"/>
<p class="expl">S3 endpoint that the user finally accesses</p>
</el-form-item>
<el-form-item label="Truncated namespace">
<el-input :value="uploadS3.truncated_namespace" @input="updateSetting($event, 'Pleroma.Uploaders.S3', 'truncated_namespace')"/>
<p class="expl">If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
For example, when using CDN to S3 virtual host format, set "".
At this time, write CNAME to CDN in <span class="code">public_endpoint</span>.
</p>
</el-form-item>
</el-form>
<div class="line"/>
<el-form ref="uploadMDII" :model="uploadMDII" :label-width="labelWidth">
<el-form-item label="Uploaders.MDII Config:"/>
<el-form-item label="CGI">
<el-input :value="uploadMDII.cgi" @input="updateSetting($event, 'Pleroma.Uploaders.MDII', 'cgi')"/>
</el-form-item>
<el-form-item label="Files">
<el-input :value="uploadMDII.files" @input="updateSetting($event, 'Pleroma.Uploaders.MDII', 'files')"/>
</el-form-item>
<el-form ref="uploadAnonymizeFilename" :model="uploadAnonymizeFilenameData" :label-width="labelWidth">
<setting :setting-group="uploadAnonymizeFilename" :data="uploadAnonymizeFilenameData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -145,69 +30,73 @@
<script>
import { mapGetters } from 'vuex'
import { options } from './options'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'Upload',
components: { Setting },
computed: {
...mapGetters([
'uploadAnonymizeFilename',
'upload',
'uploadFilterMogrify',
'uploadersLocal',
'uploadMDII',
'uploadS3'
'settings'
]),
inlineContentTypes() {
return Array.isArray(this.upload.proxy_opts.inline_content_types) ? 'whitelistedTypeArray' : this.upload.proxy_opts.inline_content_types
},
http() {
return this.upload.proxy_opts.http || {}
},
hackneyPoolsOptions() {
return options.hackneyPoolsOptions
},
whitelistedContentTypes() {
return Array.isArray(this.upload.proxy_opts.inline_content_types) ? this.upload.proxy_opts.inline_content_types : []
},
whitelistedContentTypesOptions() {
return options.whitelistedContentTypesOptions
},
mogrifyActionsOptions() {
return options.mogrifyActionsOptions
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
upload() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Upload')
},
uploadData() {
return this.settings.settings[':pleroma']['Pleroma.Upload']
},
uploadersLocal() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.Local')
},
uploadersLocalData() {
return this.settings.settings[':pleroma']['Pleroma.Uploaders.Local']
},
uploadersS3() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.S3')
},
uploadersS3Data() {
return this.settings.settings[':pleroma']['Pleroma.Uploaders.S3']
},
uploadersMDII() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Uploaders.MDII')
},
uploadersMDIIData() {
return this.settings.settings[':pleroma']['Pleroma.Uploaders.MDII']
},
uploadFilterMogrify() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.Mogrify')
},
uploadFilterMogrifyData() {
return this.settings.settings[':pleroma']['Pleroma.Upload.Filter.Mogrify']
},
uploadAnonymizeFilename() {
return this.settings.description.find(setting => setting.key === 'Pleroma.Upload.Filter.AnonymizeFilename')
},
uploadAnonymizeFilenameData() {
return this.settings.settings[':pleroma']['Pleroma.Upload.Filter.AnonymizeFilename']
}
},
methods: {
getPoolExpl(value) {
const pool = this.hackneyPoolsOptions.find(el => el.value === value)
return pool ? 'Max connections: ' + pool.max_connections + ', timeout: ' + pool.timeout : ''
},
processHttpSettings(value, tab, section, httpSection, input) {
const updatedValue = { ...this.upload[section][httpSection], ...{ [input]: value }}
this.processNestedData(updatedValue, tab, section, httpSection)
},
processNestedData(value, tab, section, input) {
const updatedValue = { ...this.$store.state.settings.settings[tab][section], ...{ [input]: value }}
this.updateSetting(updatedValue, tab, section)
},
updateInlineContentTypes() {
if (this.$data.inlineContentTypes === 'whitelistedTypeArray') {
this.processNestedData(this.$data.whitelistedContentTypes, 'Pleroma.Upload', 'proxy_opts', 'inline_content_types')
} else {
this.processNestedData(this.$data.inlineContentTypes, 'Pleroma.Upload', 'proxy_opts', 'inline_content_types')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
},
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -1,19 +1,6 @@
<template>
<el-form ref="vapidDetails" :model="vapidDetails" :label-width="labelWidth">
<el-form-item label="Subject">
<el-input :value="vapidDetails.subject" @input="updateSetting($event, 'vapid_details', 'subject')"/>
<p class="expl">A mailto link for the administrative contact. Its best if this email is not a personal email address,
but rather a group email so that if a person leaves an organization, is unavailable for an extended period,
or otherwise cant respond, someone else on the list can.</p>
</el-form-item>
<el-form-item label="Public key">
<el-input :value="vapidDetails.public_key" @input="updateSetting($event, 'vapid_details', 'public_key')"/>
<p class="expl">VAPID public key</p>
</el-form-item>
<el-form-item label="Private key">
<el-input :value="vapidDetails.private_key" @input="updateSetting($event, 'vapid_details', 'private_key')"/>
<p class="expl">VAPID private key</p>
</el-form-item>
<el-form v-if="!loading" ref="vapidDetailsData" :model="vapidDetailsData" :label-width="labelWidth">
<setting :setting-group="vapidDetails" :data="vapidDetailsData"/>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
@ -22,26 +9,43 @@
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import Setting from './Setting'
export default {
name: 'WebPush',
components: { Setting },
computed: {
...mapGetters([
'vapidDetails'
'settings'
]),
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
return this.isMobile ? '100px' : '240px'
},
loading() {
return this.settings.loading
},
vapidDetails() {
return this.settings.description.find(setting => setting.key === ':vapid_details')
},
vapidDetailsData() {
return this.settings.settings[':web_push_encryption'][':vapid_details']
}
},
methods: {
updateSetting(value, tab, input) {
this.$store.dispatch('UpdateSettings', { tab, data: { [input]: value }})
},
onSubmit() {
this.$store.dispatch('SubmitChanges')
async onSubmit() {
try {
await this.$store.dispatch('SubmitChanges')
} catch (e) {
return
}
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}

View file

@ -0,0 +1,69 @@
<template>
<div>
<div v-if="setting.key === ':class' || setting.key === ':rel'">
<el-switch :value="autoLinkerBooleanValue(setting.key)" @change="processTwoTypeValue($event, setting.key)"/>
<el-input v-if="autoLinkerBooleanValue(setting.key)" :value="autoLinkerStringValue(setting.key)" @input="processTwoTypeValue($event, setting.key)"/>
</div>
<div v-if="setting.key === ':truncate'">
<el-switch :value="autoLinkerBooleanValue(setting.key)" @change="processTwoTypeValue($event, setting.key)"/>
<el-input-number v-if="autoLinkerBooleanValue(setting.key)" :value="autoLinkerIntegerValue(setting.key)" @input="processTwoTypeValue($event, setting.key)"/>
</div>
</div>
</template>
<script>
export default {
name: 'AutoLinkerInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
methods: {
autoLinkerBooleanValue(key) {
const value = this.data[this.setting.key]
return typeof value === 'string' || typeof value === 'number'
},
autoLinkerIntegerValue(key) {
const value = this.data[this.setting.key]
return value || 0
},
autoLinkerStringValue(key) {
const value = this.data[this.setting.key]
return value || ''
},
processTwoTypeValue(value, input) {
if (value === true) {
const data = input === ':truncate' ? 0 : ''
this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
} else {
this.updateSetting(value, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
}
},
updateSetting(value, group, key, input, type) {
this.$store.dispatch('UpdateSettings', { group, key, input, value, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,113 @@
<template>
<div>
<div v-if="setting.key === ':replace'">
<div v-for="element in data" :key="getId(element)" class="setting-input">
<el-input :value="getKey(element)" placeholder="pattern" class="name-input" @input="parseEditableKeyword($event, 'key', element)"/> :
<el-input :value="getValue(element)" placeholder="replacement" class="value-input" @input="parseEditableKeyword($event, 'value', element)"/>
<el-button icon="el-icon-minus" circle @click="deleteEditableKeywordRow(element)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addRowToEditableKeyword"/>
</div>
<div v-else-if="editableKeywordWithInteger">
<div v-for="element in data" :key="getId(element)" class="setting-input">
<el-input :value="getKey(element)" placeholder="key" class="name-input" @input="parseEditableKeyword($event, 'key', element)"/> :
<el-input-number :value="getValue(element)" :min="0" size="large" class="value-input" @change="parseEditableKeyword($event, 'value', element)"/>
<el-button icon="el-icon-minus" circle @click="deleteEditableKeywordRow(element)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addRowToEditableKeyword"/>
</div>
<div v-else>
<div v-for="element in data" :key="getId(element)" class="setting-input">
<el-input :value="getKey(element)" placeholder="key" class="name-input" @input="parseEditableKeyword($event, 'key', element)"/> :
<el-select :value="getValue(element)" multiple filterable allow-create class="value-input" @change="parseEditableKeyword($event, 'value', element)"/>
<el-button icon="el-icon-minus" circle @click="deleteEditableKeywordRow(element)"/>
</div>
<el-button icon="el-icon-plus" circle @click="addRowToEditableKeyword"/>
</div>
</div>
</template>
<script>
export default {
name: 'EditableKeywordInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
editableKeywordWithInteger() {
return Array.isArray(this.setting.type) && this.setting.type.includes('keyword') && this.setting.type.includes('integer')
}
},
methods: {
addRowToEditableKeyword() {
const updatedValue = [...this.data, { '': { value: '', id: this.generateID() }}]
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
deleteEditableKeywordRow(element) {
const deletedId = this.getId(element)
const filteredValues = this.data.filter(element => Object.values(element)[0].id !== deletedId)
this.updateSetting(filteredValues, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
generateID() {
return `f${(~~(Math.random() * 1e8)).toString(16)}`
},
getKey(element) {
return Object.keys(element)[0]
},
getId(element) {
const { id } = Object.values(element)[0]
return id
},
getValue(element) {
const { value } = Object.values(element)[0]
return value
},
parseEditableKeyword(value, inputType, element) {
const updatedId = this.getId(element)
const updatedValue = this.data.map((element, index) => {
if (Object.values(element)[0].id === updatedId) {
return inputType === 'key'
? { [value]: Object.values(this.data[index])[0] }
: { [Object.keys(element)[0]]: { ...Object.values(this.data[index])[0], value }}
}
return element
})
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const updatedSettings = type !== 'map'
? value.reduce((acc, element) => {
return { ...acc, [Object.keys(element)[0]]: [['list'], Object.values(element)[0].value] }
}, {})
: value.reduce((acc, element) => {
return { ...acc, [Object.keys(element)[0]]: Object.values(element)[0].value }
}, {})
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSettings, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,106 @@
<template>
<div>
<div v-for="(icon, index) in data" :key="index" class="mascot-container">
<div class="icons-container">
<div class="icon-container">
<div v-for="{ key, value, id } in icon" :key="id" class="icon-values-container">
<el-input :value="key" placeholder="key" class="icon-key-input" @input="parseIcons($event, 'key', index, id)"/> :
<el-input :value="value" placeholder="value" class="icon-value-input" @input="parseIcons($event, 'value', index, id)"/>
</div>
</div>
<el-button icon="el-icon-minus" circle class="icon-minus-button" @click="deleteIcondRow(index)"/>
</div>
<div class="icons-button-container">
<el-button icon="el-icon-plus" circle @click="addValueToIcons(index)"/>
<span class="icons-button-desc">Add another `key - value` pair to this icon</span>
</div>
<div class="line"/>
</div>
<div class="icons-button-container">
<el-button icon="el-icon-plus" circle @click="addIconToIcons"/>
<span class="icons-button-desc">Add another icon configuration</span>
</div>
</div>
</template>
<script>
export default {
name: 'EditableKeywordInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
},
methods: {
addIconToIcons() {
const updatedValue = [...this.data, [{ key: '', value: '', id: this.generateID() }]]
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key)
},
addValueToIcons(index) {
const updatedValue = this.data.map((icon, i) => {
if (i === index) {
return [...icon, { key: '', value: '', id: this.generateID() }]
}
return icon
})
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key)
},
deleteIcondRow(index) {
const filteredValues = this.data.filter((icon, i) => i !== index)
this.updateSetting(filteredValues, this.settingGroup.group, this.settingGroup.key, this.setting.key)
},
generateID() {
return `f${(~~(Math.random() * 1e8)).toString(16)}`
},
parseIcons(value, inputType, index, id) {
const updatedValue = this.data.map((icon, i) => {
if (i === index) {
return icon.map(setting => {
if (setting.id === id) {
return inputType === 'key'
? { ...setting, key: value }
: { ...setting, value }
}
return setting
})
}
return icon
})
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const updatedSettings = value.map(icon => {
return icon.reduce((acc, { key, value }) => {
return { ...acc, [key]: value }
}, {})
}, {})
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSettings, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,103 @@
<template>
<div>
<div v-for="mascot in data" :key="getId(mascot)" class="mascot-container">
<el-form-item label="Name" label-width="100px">
<div class="mascot-name-container">
<el-input :value="getName(mascot)" placeholder="Name" class="mascot-name-input" @input="parseMascots($event, 'name', mascot)"/>
<el-button icon="el-icon-minus" circle @click="deleteMascotsRow(mascot)"/>
</div>
</el-form-item>
<el-form-item label="URL" label-width="100px">
<el-input :value="getUrl(mascot)" placeholder="URL" class="mascot-input" @input="parseMascots($event, 'url', mascot)"/>
</el-form-item>
<el-form-item label="Mime type" label-width="100px">
<el-input :value="getMimeType(mascot)" placeholder="Mime type" class="mascot-input" @input="parseMascots($event, 'mimeType', mascot)"/>
</el-form-item>
</div>
<el-button icon="el-icon-plus" circle @click="addRowToMascots"/>
</div>
</template>
<script>
export default {
name: 'MascotsInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
methods: {
addRowToMascots() {
const updatedValue = [...this.data, { '': { ':url': '', ':mime_type': '', id: this.generateID() }}]
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
deleteMascotsRow(mascot) {
const deletedId = this.getId(mascot)
const filteredValues = this.data.filter(mascot => Object.values(mascot)[0].id !== deletedId)
this.updateSetting(filteredValues, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
generateID() {
return `f${(~~(Math.random() * 1e8)).toString(16)}`
},
getId(mascot) {
const { id } = Object.values(mascot)[0]
return id
},
getName(mascot) {
return Object.keys(mascot)[0]
},
getUrl(mascot) {
const [value] = Object.values(mascot)
return value[':url']
},
getMimeType(mascot) {
const [value] = Object.values(mascot)
return value[':mime_type']
},
parseMascots(value, inputType, mascot) {
const updatedId = this.getId(mascot)
const updatedValue = this.data.map((mascot, index) => {
if (Object.values(mascot)[0].id === updatedId) {
if (inputType === 'name') {
return { [value]: Object.values(this.data[index])[0] }
} else if (inputType === 'url') {
return { [Object.keys(mascot)[0]]: { ...Object.values(this.data[index])[0], ':url': value }}
} else {
return { [Object.keys(mascot)[0]]: { ...Object.values(this.data[index])[0], ':mime_type': value }}
}
}
return mascot
})
this.updateSetting(updatedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const mascotsWithoutIDs = value.reduce((acc, mascot) => {
const { id, ...mascotValue } = Object.values(mascot)[0]
return { ...acc, [Object.keys(mascot)[0]]: mascotValue }
}, {})
this.$store.dispatch('UpdateSettings', { group, key, input, value: mascotsWithoutIDs, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,63 @@
<template>
<div>
<el-select
v-if="setting.key === ':backends'"
:value="data.value"
multiple
filterable
allow-create
@change="updateSetting($event, settingGroup.group, settingGroup.key, setting.key, setting.type)">
<el-option value=":console" label="console"/>
<el-option value=":ex_syslogger" label="ExSyslogger"/>
<el-option value="Quack.Logger" label="Quack.Logger"/>
</el-select>
<el-select
v-if="setting.key === ':args'"
:value="data[setting.key]"
multiple
filterable
allow-create
@change="updateSetting($event, settingGroup.group, settingGroup.key, setting.key, setting.type)">
<el-option value="strip" label="strip"/>
<el-option value="auto-orient" label="auto-orient"/>
<el-option value="implode" label="implode"/>
</el-select>
</div>
</template>
<script>
export default {
name: 'MultipleSelect',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
methods: {
updateSetting(value, group, key, input, type) {
this.$store.dispatch('UpdateSettings', { group, key, input, value, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,97 @@
<template>
<div class="setting-input">
<el-input
:value="proxyUrlData.host"
placeholder="host (e.g. localhost or 127.0.0.1)"
class="proxy-url-value-input"
@input="updateProxyUrl($event, 'host')"/> :
<el-input
:value="proxyUrlData.port"
placeholder="port (e.g 9020 or 3090)"
class="proxy-url-value-input"
@input="updateProxyUrl($event, 'port')"/>
<el-checkbox :value="proxyUrlData.socks5" class="name-input" @change="updateProxyUrl($event, 'socks5')">Socks5</el-checkbox>
</div>
</template>
<script>
import { processNested } from '@/store/modules/normalizers'
export default {
name: 'ProxyUrlInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
},
parents: {
type: Array,
default: function() {
return []
},
required: false
}
},
computed: {
settings() {
return this.$store.state.settings.settings
},
updatedSettings() {
return this.$store.state.settings.updatedSettings
},
proxyUrlData() {
return Object.keys(this.data).length === 0 ? { socks5: false, host: null, port: null } : this.data
}
},
methods: {
updateProxyUrl(value, inputType) {
let data
if (inputType === 'socks5') {
data = { ...this.proxyUrlData, socks5: value }
} else if (inputType === 'host') {
data = { ...this.proxyUrlData, host: value }
} else {
data = { ...this.proxyUrlData, port: value }
}
this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const assembledData = value.socks5
? [':socks5', value.host, value.port]
: `${value.host}:${value.port}`
if (this.parents.length > 0) {
const { valueForState,
valueForUpdatedSettings,
setting } = processNested(value, assembledData, group, key, this.parents.reverse(), this.settings, this.updatedSettings)
this.$store.dispatch('UpdateSettings',
{ group, key, input: setting.key, value: valueForUpdatedSettings, type: setting.type })
this.$store.dispatch('UpdateState',
{ group, key, input: setting.key, value: valueForState })
} else {
this.$store.dispatch('UpdateSettings', { group, key, input, value: assembledData, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,82 @@
<template>
<div>
<el-radio-group v-model="prune">
<el-radio label=":disabled">Disabled</el-radio>
<el-radio label=":maxlen">Limit-based</el-radio>
<el-radio label=":maxage">Time-based</el-radio>
</el-radio-group>
<el-form-item v-if="prune === ':maxlen'" label="max length" label-width="100" label-position="left">
<el-input-number
:value="data[1]"
:min="0"
placeholder="1500"
size="large"
class="top-margin"
@change="updateIntInput($event, ':maxlen')"/>
</el-form-item>
<el-form-item v-if="prune === ':maxage'" label="max age" label-width="100" label-position="left">
<el-input-number
:value="data[1]"
:min="0"
placeholder="3600"
size="large"
class="top-margin"
@change="updateIntInput($event, ':maxage')"/>
</el-form-item>
</div>
</template>
<script>
export default {
name: 'PruneInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
prune: {
get: function() {
return this.data[0]
},
set: function(value) {
this.updateRadioInput(value)
}
}
},
methods: {
updateIntInput(value, input) {
this.updateSetting([input, value], this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
},
updateSetting(value, group, key, input, type) {
const updatedSetting = value.includes(':disabled') ? ':disabled' : value
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSetting, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
},
updateRadioInput(value) {
const processedValue = value === ':disabled' ? [value] : [value, 0]
this.updateSetting(processedValue, this.settingGroup.group, this.settingGroup.key, this.setting.key, this.setting.type)
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,123 @@
<template>
<div>
<div v-if="!rateLimitAuthUsers">
<el-input
:value="rateLimitAllUsers[0]"
placeholder="scale"
class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'oneLimit', rateLimitAllUsers)"/> :
<el-input
:value="rateLimitAllUsers[1]"
placeholder="limit"
class="limit-input"
@input="parseRateLimiter($event, setting.key, 'limit', 'oneLimit', rateLimitAllUsers)"/>
<div class="limit-button-container">
<el-button icon="el-icon-plus" circle @click="toggleLimits([['', ''], ['', '']], setting.key)"/>
<p class="expl limit-expl">Set different limits for unauthenticated and authenticated users</p>
</div>
</div>
<div v-if="rateLimitAuthUsers">
<el-form-item label="Unauthenticated users:">
<el-input
:value="rateLimitUnauthUsers[0]"
placeholder="scale"
class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/> :
<el-input
:value="rateLimitUnauthUsers[1]"
placeholder="limit"
class="limit-input"
@input="parseRateLimiter($event, setting.key, 'limit', 'unauthUsersLimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/>
</el-form-item>
<el-form-item label="Authenticated users:">
<el-input
:value="rateLimitAuthUsers[0]"
placeholder="scale"
class="scale-input"
@input="parseRateLimiter($event, setting.key, 'scale', 'authUserslimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/> :
<el-input
:value="rateLimitAuthUsers[1]"
placeholder="limit"
class="limit-input"
@input="parseRateLimiter($event, setting.key, 'limit', 'authUserslimit', [rateLimitUnauthUsers, rateLimitAuthUsers])"/>
</el-form-item>
<div class="limit-button-container">
<el-button icon="el-icon-minus" circle @click="toggleLimits(['', ''], setting.key)"/>
<p class="expl limit-expl">Set limit for all users</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RateLimitInput',
props: {
data: {
type: [Object, Array],
default: function() {
return {}
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
}
},
computed: {
rateLimitAllUsers() {
return this.data[this.setting.key] ? this.data[this.setting.key] : ['', '']
},
rateLimitAuthUsers() {
return this.data[this.setting.key] && Array.isArray(this.data[this.setting.key][0])
? this.data[this.setting.key][1]
: false
},
rateLimitUnauthUsers() {
return this.data[this.setting.key] && Array.isArray(this.data[this.setting.key][1])
? this.data[this.setting.key][0]
: false
}
},
methods: {
parseRateLimiter(value, input, typeOfInput, typeOfLimit, currentValue) {
let valueToSend
if (typeOfLimit === 'oneLimit') {
valueToSend = typeOfInput === 'scale' ? [value, currentValue[1]] : [currentValue[0], value]
} else if (typeOfLimit === 'unauthUsersLimit') {
valueToSend = typeOfInput === 'scale'
? [[value, currentValue[0][1]], [currentValue[1][0], currentValue[1][1]]]
: [[currentValue[0][0], value], [currentValue[1][0], currentValue[1][1]]]
} else if (typeOfLimit === 'authUserslimit') {
valueToSend = typeOfInput === 'scale'
? [[currentValue[0][0], currentValue[0][1]], [value, currentValue[1][1]]]
: [[currentValue[0][0], currentValue[0][1]], [currentValue[1][0], value]]
}
this.updateSetting(valueToSend, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
},
toggleLimits(value, input) {
this.updateSetting(value, this.settingGroup.group, this.settingGroup.key, input)
},
updateSetting(value, group, key, input, type) {
const updatedSettings = Array.isArray(value[0])
? value.map(element => { return { 'tuple': element } })
: { 'tuple': value }
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSettings, type })
this.$store.dispatch('UpdateState', { group, key, input, value })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,93 @@
<template>
<div>
<div v-for="subSetting in setting.children" :key="subSetting.key">
<el-form-item :label="subSetting.label" :label-width="customLabelWidth">
<el-select
v-if="subSetting.type.includes('list') && subSetting.type.includes('atom')"
:value="subSettingValue(subSetting)"
multiple
filterable
allow-create
@change="update($event, subSetting.key)">
<el-option v-for="(option, index) in subSetting.suggestions" :key="index" :value="option"/>
</el-select>
<p class="expl">{{ subSetting.description }}</p>
</el-form-item>
</div>
</div>
</template>
<script>
export default {
name: 'SslOptionsInput',
props: {
customLabelWidth: {
type: String,
default: function() {
return this.labelWidth
},
required: false
},
data: {
type: [Object, Array],
default: function() {
return {}
}
},
nested: {
type: Boolean,
default: function() {
return false
}
},
setting: {
type: Object,
default: function() {
return {}
}
},
settingGroup: {
type: Object,
default: function() {
return {}
}
},
settingParent: {
type: Object,
default: function() {
return {}
},
required: false
}
},
methods: {
inputValue(key) {
return this.data[this.setting.key][key]
},
subSettingValue(subSetting) {
return this.data && this.data[this.setting.key] ? this.data[this.setting.key][subSetting.key] : []
},
update(value, childKey) {
const [group, key, parentKey, input] = [this.settingGroup.group, this.settingGroup.key, this.setting.key, this.settingParent.key]
const { updatedSettings, description } = this.$store.state.settings
const type = description
.find(element => element.group === group && element.key === key).children
.find(child => child.key === ':adapter').children.find(child => child.key === ':ssl_options').children
.find(child => child.key === childKey).type
const updatedState = { ...this.data, [parentKey]: { ...this.data[parentKey], [childKey]: value }}
const updatedSetting = !updatedSettings[group] || !updatedSettings[group][key]
? { [parentKey]: ['keyword', { [childKey]: [type, value] }] }
: { ...updatedSettings[group][key][parentKey], [parentKey]: { ...updatedSettings[group][key][parentKey], [childKey]: [type, value] }}
this.$store.dispatch('UpdateSettings', { group, key, input, value: updatedSetting, type: this.settingParent.type })
this.$store.dispatch('UpdateState', { group, key, input, value: updatedState })
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../../styles/main';
@include settings
</style>

View file

@ -0,0 +1,9 @@
export { default as AutoLinkerInput } from './AutoLinkerInput'
export { default as EditableKeywordInput } from './EditableKeywordInput'
export { default as IconsInput } from './IconsInput'
export { default as MascotsInput } from './MascotsInput'
export { default as MultipleSelect } from './MultipleSelect'
export { default as ProxyUrlInput } from './ProxyUrlInput'
export { default as PruneInput } from './PruneInput'
export { default as RateLimitInput } from './RateLimitInput'
export { default as SslOptionsInput } from './SslOptionsInput'

View file

@ -1,117 +0,0 @@
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: Doesnt modify activities (default)' },
{ label: 'DropPolicy', value: 'Pleroma.Web.ActivityPub.MRF.DropPolicy', expl: 'DropPolicy: Drops all activities. It generally doesnt 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)' },
{ label: 'VocabularyPolicy', value: 'Pleroma.Web.ActivityPub.MRF.VocabularyPolicy', expl: 'Restricts activities to a configured set of vocabulary' }
],
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' }
]
}

View file

@ -1,74 +1,74 @@
<template>
<div class="settings-container">
<h1>{{ $t('settings.settings') }}</h1>
<el-tabs :tab-position="tabPosition">
<el-tab-pane :label="$t('settings.activityPub')">
<el-tabs v-model="activeTab" :tab-position="tabPosition">
<el-tab-pane :label="$t('settings.activityPub')" lazy>
<activity-pub/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.auth')">
<el-tab-pane :label="$t('settings.auth')" lazy>
<authentication/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.autoLinker')">
<el-tab-pane :label="$t('settings.autoLinker')" lazy>
<auto-linker/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.esshd')">
<el-tab-pane :label="$t('settings.esshd')" lazy>
<esshd/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.captcha')">
<el-tab-pane :label="$t('settings.captcha')" lazy>
<captcha/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.database')">
<el-tab-pane :label="$t('settings.database')" lazy>
<database/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.endpoint')">
<endpoint/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.emojiPacks')">
<el-tab-pane :label="$t('settings.emojiPacks')" lazy>
<emoji-packs/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.frontend')">
<el-tab-pane :label="$t('settings.endpoint')" lazy>
<endpoint/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.frontend')" lazy>
<frontend/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.gopher')">
<el-tab-pane :label="$t('settings.gopher')" lazy>
<gopher/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.http')">
<el-tab-pane :label="$t('settings.http')" lazy>
<http/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.instance')">
<el-tab-pane :label="$t('settings.instance')" name="instance">
<instance/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.jobQueue')">
<el-tab-pane :label="$t('settings.jobQueue')" lazy>
<job-queue/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.logger')">
<el-tab-pane :label="$t('settings.logger')" lazy>
<logger/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.mailer')">
<el-tab-pane :label="$t('settings.mailer')" lazy>
<mailer/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.mediaProxy')">
<el-tab-pane :label="$t('settings.mediaProxy')" lazy>
<media-proxy/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.metadata')">
<el-tab-pane :label="$t('settings.metadata')" lazy>
<metadata/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.mrf')">
<el-tab-pane :label="$t('settings.mrf')" lazy>
<mrf/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.rateLimiters')">
<el-tab-pane :label="$t('settings.rateLimiters')" lazy>
<rate-limiters/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.relays')">
<el-tab-pane :label="$t('settings.relays')" lazy>
<relays/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.upload')">
<upload/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.webPush')">
<el-tab-pane :label="$t('settings.webPush')" lazy>
<web-push/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.other')">
<el-tab-pane :label="$t('settings.upload')" lazy>
<upload/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.other')" lazy>
<other/>
</el-tab-pane>
</el-tabs>
@ -81,6 +81,11 @@ import EmojiPacks from '../emojiPacks/index'
export default {
components: { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, EmojiPacks, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Relays, Upload, WebPush },
data() {
return {
activeTab: 'instance'
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'

View file

@ -8,9 +8,28 @@
font-family: monospace;
padding: 0 3px 0 3px;
}
.description {
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;
}
.description-container {
overflow-wrap: break-word;
margin-bottom: 0;
}
.el-form-item {
margin-right: 30px;
}
.center-label label {
text-align: center;
}
.el-input-group__prepend {
padding-left: 10px;
padding-right: 10px;
}
.el-select {
width: 100%;
}
@ -27,6 +46,47 @@
.highlight {
background-color: #e6e6e6;
}
.icons-button-container {
width: 100%;
margin-bottom: 10px;
}
.icons-button-desc {
font-size: 14px;
color: #606266;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei";
margin-left: 5px;
}
.icon-container {
flex-direction: column;
width: 95%;
}
.icon-values-container {
display: flex;
margin: 0 10px 10px 0;
}
.icon-key-input {
width: 30%;
margin-right: 8px
}
.icon-minus-button {
width: 36px;
height: 36px;
}
.icon-value-input {
width: 70%;
margin-left: 8px;
}
.icons-container {
display: flex;
}
.keyword-inner-input {
margin-bottom: 22px;
}
label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.limit-button-container {
display: flex;
align-items: baseline;
@ -61,22 +121,15 @@
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
}
.proxy-url-value-input {
width: 35%;
margin-left: 8px;
margin-right: 10px
}
.setting-input {
display: flex;
margin-bottom: 10px;
@ -97,6 +150,9 @@
line-height: 20px;
margin-right: 15px
}
.ssl-tls-opts {
margin: 36px 0 0 0;
}
.upload-container {
display: flex;
align-items: baseline;

View file

@ -0,0 +1,48 @@
import { checkPartialUpdate } from '@/store/modules/normalizers'
import _ from 'lodash'
describe('Partial update', () => {
it('partial update for settings that do not allow partial update', () => {
const settings = { ':auto_linker': { ':opts':
{ ':strip_prefix': true, ':new_window': false, ':rel': 'ugc', ':truncate': 3 }
}}
const updatedSettings = { ':auto_linker': { ':opts': { ':new_window': false }}}
const description = [{
children: [
{ key: ':strip_prefix', type: 'boolean' },
{ key: ':truncate', type: ['integer', false] },
{ key: ':new_window', type: 'boolean' }],
description: 'Configuration for the auto_linker library',
group: ':auto_linker',
key: ':opts',
label: 'Opts',
type: 'group'
}]
const expectedData = { ':auto_linker': { ':opts': {
':strip_prefix': ['boolean', true],
':new_window': ['boolean', false],
':rel': ['', 'ugc'],
':truncate': [['integer', false], 3]
}}}
const updatedData = checkPartialUpdate(settings, updatedSettings, description)
expect(_.isEqual(updatedData, expectedData)).toBeTruthy()
})
it('partial update for settings that allow partial update', () => {
const settings = { ':pleroma': { 'Pleroma.Captcha': { ':enabled': true, ':seconds_valid': 70, ':method': 'Pleroma.Captcha.Kocaptcha' }}}
const updatedSettings = { ':pleroma': { 'Pleroma.Captcha': { ':seconds_valid': ['integer', 70] }}}
const description = [{
children: [],
description: 'Captcha-related settings',
group: ':pleroma',
key: 'Pleroma.Captcha',
label: 'Pleroma.Captcha',
type: 'group'
}]
const expectedData = { ':pleroma': { 'Pleroma.Captcha': { ':seconds_valid': ['integer', 70] }}}
const updatedData = checkPartialUpdate(settings, updatedSettings, description)
expect(_.isEqual(updatedData, expectedData)).toBeTruthy()
})
})

View file

@ -0,0 +1,269 @@
import { parseTuples } from '@/store/modules/normalizers'
import _ from 'lodash'
describe('Parse tuples', () => {
it('parses tuples', () => {
const tuples = [
{ tuple: [':enabled', false]},
{ tuple: [':host', 'localhost']},
{ tuple: [':port', 389]},
{ tuple: [':tlsopts', ['test', 'test1']]},
{ tuple: [':sslopts', [{ tuple: [':cacertfile', 'path/to/file'] }, { tuple: [':verify', ':verify_peer'] }]]}
]
const expectedResult = {
':enabled': false,
':host': 'localhost',
':port': 389,
':tlsopts': ['test', 'test1'],
':sslopts': { ':cacertfile': 'path/to/file', ':verify': ':verify_peer' }
}
const result = parseTuples(tuples, ':ldap')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses rate limiters setting', () => {
const tuples = [
{ tuple: [':authentication', { tuple: [60000, 15] }]},
{ tuple: [':app_account_creation', [{ tuple: [100, 55] }, { tuple: [150, 10] }]]}
]
const expectedResult = {
':authentication': [60000, 15],
':app_account_creation': [[100, 55], [150, 10]]
}
const result = parseTuples(tuples, ':rate_limit')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses icons setting', () => {
const tuples = [
{ tuple: [':icons', [
{ ':src': '/static/logo.png', ':type': 'image/png' },
{ ':icon': '/test/test.png'}
]]}
]
const expectedResult = [
[{ key: ':src', value: '/static/logo.png' }, { key: ':type', value: 'image/png' }],
[{ key: ':icon', value: '/test/test.png' }]
]
const parsed = parseTuples(tuples, ':manifest')
expect(typeof parsed).toBe('object')
expect(':icons' in parsed).toBeTruthy()
expect('id' in parsed[':icons'][0][0]).toBeTruthy()
const result = parsed[':icons'].map(icon => {
const iconWithoutId = icon.map(el => {
const { id, ...rest } = el
return rest
})
return iconWithoutId
})
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses retries setting', () => {
const tuples = [
{ tuple: [':retries', [
{ tuple: [':federator_incoming', 5] },
{ tuple: [':federator_outgoing', 10] }
]]}
]
const parsed = parseTuples(tuples, ':workers')
expect(typeof parsed).toBe('object')
expect(':retries' in parsed).toBeTruthy()
expect(Array.isArray(parsed[':retries'])).toBeTruthy()
expect(':federator_incoming' in parsed[':retries'][0]).toBeTruthy()
expect('id' in parsed[':retries'][0][':federator_incoming']).toBeTruthy()
expect(parsed[':retries'][0][':federator_incoming']['value']).toEqual(5)
})
it('parses objects', () => {
const tuples = [
{ tuple: [':pleroma_fe', { ':alwaysShowSubjectInput': true, ':redirectRootNoLogin': '/main/all' }]},
{ tuple: [':masto_fe', { ':showInstanceSpecificPanel': true }]}
]
const expectedResult = {
':masto_fe': { ':showInstanceSpecificPanel': true },
':pleroma_fe': { ':alwaysShowSubjectInput': true, ':redirectRootNoLogin': '/main/all' }
}
const result = parseTuples(tuples, ':frontend_configurations')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses ip', () => {
const tuples = [
{ tuple: [':enabled', false]},
{ tuple: [':ip', { tuple: [0, 0, 0, 0] }]}
]
const expectedResult = { ':enabled': false, ':ip': '0.0.0.0' }
const result = parseTuples(tuples, ':gopher')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses prune setting that is a tuple', () => {
const tuples = [
{ tuple: [':verbose', false]},
{ tuple: [':prune', { tuple: [':maxlen', 1500] }]},
{ tuple: [':queues', [
{ tuple: [':activity_expiration', 10]},
{ tuple: [':federator_incoming', 50]}
] ]}
]
const expectedResult = {
':verbose': false,
':prune': [':maxlen', 1500],
':queues': { ':activity_expiration': 10, ':federator_incoming': 50 } }
const result = parseTuples(tuples, 'Oban')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses prune setting that is an atom', () => {
const tuples = [{ tuple: [':prune', ':disabled' ]}]
const expectedResult = { ':prune': [':disabled'] }
const result = parseTuples(tuples, 'Oban')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses mrf_keyword settings', () => {
const tuples = [
{ tuple: [':reject', ['foo', '~r/foo/iu'] ]},
{ tuple: [':replace', [{ tuple: ['pattern', 'replacement']}, { tuple: ['foo', 'bar']}]]}
]
const expectedResult = {
':reject': ['foo', '~r/foo/iu'],
':replace': [{ 'pattern': { value:'replacement' }}, { 'foo': { value:'bar' }}]
}
const parsed = parseTuples(tuples, ':mrf_keyword')
expect(typeof parsed).toBe('object')
expect(':reject' in parsed).toBeTruthy()
expect(':replace' in parsed).toBeTruthy()
const result = { ...parsed, ':replace': parsed[':replace'].map(el => {
const key = Object.keys(el)[0]
const { id, ...rest } = el[key]
return { [key]: rest }
})}
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses assets settings', () => {
const tuples = [
{ tuple: [':mascots', [
{ tuple: [':pleroma_fox_tan', { ':mime_type': 'image/png', ':url': '/images/pleroma-fox-tan-smol.png'}]},
{ tuple: [':pleroma_fox_tan_shy', { ':mime_type': 'image/png', ':url': '/images/pleroma-fox-tan-shy.png'}]}
]]},
{ tuple: [':default_mascot', ':pleroma_fox_tan']}
]
const expectedResult = {
':default_mascot': ':pleroma_fox_tan',
':mascots': [
{ ':pleroma_fox_tan': { ':mime_type': 'image/png', ':url':'/images/pleroma-fox-tan-smol.png' }},
{ ':pleroma_fox_tan_shy': { ':mime_type': 'image/png', ':url':'/images/pleroma-fox-tan-shy.png' }}]
}
const parsed = parseTuples(tuples, ':assets')
expect(typeof parsed).toBe('object')
expect(':default_mascot' in parsed).toBeTruthy()
expect(':mascots' in parsed).toBeTruthy()
const result = { ...parsed, ':mascots': parsed[':mascots'].map(el => {
const key = Object.keys(el)[0]
const { id, ...rest } = el[key]
return { [key]: rest }
})}
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
it('parses groups setting in emoji group', () => {
const tuples = [{ tuple: [':groups', [{ tuple: [':Custom', ['/emoji/*.png', '/emoji/**/*.png']]}]]}]
const expectedResult = { ':groups': [{ ':Custom': { value: ['/emoji/*.png', '/emoji/**/*.png']}}] }
const parsed = parseTuples(tuples, ':emoji')
expect(typeof parsed).toBe('object')
expect(':groups' in parsed).toBeTruthy()
const result = { ...parsed, ':groups': parsed[':groups'].map(el => {
const key = Object.keys(el)[0]
const { id, ...rest } = el[key]
return { [key]: rest }
})}
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
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 parsed = parseTuples(tuples, ':mrf_subchain')
expect(typeof parsed).toBe('object')
expect(':match_actor' in parsed).toBeTruthy()
const result = { ...parsed, ':match_actor': parsed[':match_actor'].map(el => {
const key = Object.keys(el)[0]
const { id, ...rest } = el[key]
return { [key]: rest }
})}
expect(_.isEqual(expectedResult, result)).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()
expect(_.isEqual(expectedProxyUrlString, parseTuples(proxyUrlString, ':http'))).toBeTruthy()
})
it('parses args setting in Pleroma.Upload.Filter.Mogrify', () => {
const tuples = [{ tuple: [":args", ["strip", { tuple: ["implode", "1"] }]]}]
const expectedResult = { ":args": ["strip", "implode"] }
const result = parseTuples(tuples, 'Pleroma.Upload.Filter.Mogrify')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
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"] }
]]},
]]}]
const expectedResult = { ':proxy_opts': {
":redirect_on_failure": false,
":max_body_length": 26214400,
":http": {
":follow_redirect": true,
":pool": ":media"
}
}}
const result = parseTuples(tuples, ':media_proxy')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
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 result = parseTuples(tuples, ':rich_media')
expect(_.isEqual(expectedResult, result)).toBeTruthy()
})
})

View file

@ -0,0 +1,42 @@
import { wrapUpdatedSettings } from '@/store/modules/normalizers'
import _ from 'lodash'
describe('Wrap settings', () => {
it('wraps values without keys with type atom', () => {
const settings = { ':level': { _value: ['atom', 'warn'] }}
const result = wrapUpdatedSettings(':quack', settings, {})
const expectedResult = [{ group: ':quack', key: ':level', value: ':warn' }]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
it('wraps :backends setting in group :logger', () => {
const settings = { ':backends': { _value:
[['atom', 'tuple', 'module'], [':console', 'Quack.Logger', ':ex_syslogger']]
}}
const result = wrapUpdatedSettings(':logger', settings, {})
const expectedResult = [{
group: ':logger',
key: ':backends',
value: [':console', 'Quack.Logger', { 'tuple': ['ExSyslogger', ':ex_syslogger'] }]
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
it('wraps :types setting in group :mime', () => {
const settings = { ':types': { _value: ['map', {
'application/ld+json': [['list', 'string'], ['activity+json']],
'application/xml': [['list', 'string'], ['xml']],
'application/xrd+xml': [['list', 'string'], ['xrd+xml']]
}]}}
const result = wrapUpdatedSettings(':mime', settings, {})
const expectedResult = [{
group: ':mime',
key: ':types', value: {
'application/ld+json': ['activity+json'],
'application/xml': ['xml'],
'application/xrd+xml': ['xrd+xml']
}
}]
expect(_.isEqual(result, expectedResult)).toBeTruthy()
})
})