Add forms for configuring server-settings

This commit is contained in:
Angelina Filippova 2019-08-09 16:45:25 +00:00
parent 031ae4ce57
commit 6846ccf670
37 changed files with 5157 additions and 3 deletions

View File

@ -141,7 +141,8 @@ module.exports = {
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
'args': 'none',
'ignoreRestSiblings': true
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,

View File

@ -39,6 +39,7 @@
"axios": "0.18.0",
"clipboard": "1.7.1",
"codemirror": "5.39.2",
"default-passive-events": "^1.0.10",
"driver.js": "0.8.1",
"dropzone": "5.2.0",
"echarts": "4.1.0",
@ -63,6 +64,7 @@
"vue-i18n": "^8.9.0",
"vue-router": "3.0.2",
"vue-splitpane": "1.0.2",
"vue2-ace-editor": "^0.0.13",
"vuedraggable": "^2.16.0",
"vuex": "3.0.1",
"xlsx": "^0.11.16"

View File

@ -0,0 +1,110 @@
export const initialSettings = [
{
group: 'pleroma',
key: ':instance',
value: [
{ 'tuple': [':name', 'Pleroma'] },
{ 'tuple': [':email', 'example@example.com'] },
{ 'tuple': [':notify_email', 'noreply@example.com'] },
{ 'tuple': [':description', 'A Pleroma instance, an alternative fediverse server'] },
{ 'tuple': [':limit', 5000] },
{ 'tuple': [':remote_limit', 100000] },
{ 'tuple': [':upload_limit', 16 * 1048576] },
{ 'tuple': [':avatar_upload_limit', 2 * 1048576] },
{ 'tuple': [':background_upload_limit', 4 * 1048576] },
{ 'tuple': [':banner_upload_limit', 4 * 1048576] },
{ 'tuple': [':poll_limits', [
{ 'tuple': [':max_options', 20] },
{ 'tuple': [':max_option_chars', 200] },
{ 'tuple': [':min_expiration', 0] },
{ 'tuple': [':max_expiration', 365 * 86400] }
]] },
{ 'tuple': [':registrations_open', true] },
{ 'tuple': [':invites_enabled', false] },
{ 'tuple': [':account_activation_required', false] },
{ 'tuple': [':federating', true] },
{ 'tuple': [':federation_reachability_timeout_days', 7] },
{ 'tuple':
[':federation_publisher_modules', ['Pleroma.Web.ActivityPub.Publisher', 'Pleroma.Web.Websub', 'Pleroma.Web.Salmon']] },
{ 'tuple': [':allow_relay', true] },
{ 'tuple': [':rewrite_policy', 'Pleroma.Web.ActivityPub.MRF.NoOpPolicy'] },
{ 'tuple': [':public', true] },
{ 'tuple': [':managed_config', true] },
{ 'tuple': [':static_dir', 'instance/static/'] },
{ 'tuple': [':allowed_post_formats', ['text/plain', 'text/html', 'text/markdown', 'text/bbcode']] },
{ 'tuple': [':mrf_transparency', true] },
{ 'tuple': [':extended_nickname_format', false] },
{ 'tuple': [':max_pinned_statuses', 1] },
{ 'tuple': [':no_attachment_links', false] },
{ 'tuple': [':max_report_comment_size', 1000] },
{ 'tuple': [':safe_dm_mentions', false] },
{ 'tuple': [':healthcheck', false] },
{ 'tuple': [':remote_post_retention_days', 90] },
{ 'tuple': [':skip_thread_containment', true] },
{ 'tuple': [':limit_to_local_content', ':unauthenticated'] },
{ 'tuple': [':dynamic_configuration', true] }
]
},
{
group: 'mime',
key: ':types',
value: {
'application/activity+json': ['activity+json'],
'application/jrd+json': ['jrd+json'],
'application/ld+json': ['activity+json'],
'application/xml': ['xml'],
'application/xrd+xml': ['xrd+xml']
}
},
{
group: 'cors_plug',
key: ':max_age',
value: 86400
},
{
group: 'cors_plug',
key: ':methods',
value: ['POST', 'PUT', 'DELETE', 'GET', 'PATCH', 'OPTIONS']
},
{
group: 'cors_plug',
key: ':expose',
value: [
'Link',
'X-RateLimit-Reset',
'X-RateLimit-Limit',
'X-RateLimit-Remaining',
'X-Request-Id',
'Idempotency-Key'
]
},
{
group: 'cors_plug',
key: ':credentials',
value: true
},
{
group: 'cors_plug',
key: ':headers',
value: ['Authorization', 'Content-Type', 'Idempotency-Key']
},
{
group: 'tesla',
key: ':adapter',
value: 'Tesla.Adapter.Hackney'
},
{
group: 'pleroma',
key: ':markup',
value: [
{ 'tuple': [':allow_inline_images', true] },
{ 'tuple': [':allow_headings', false] },
{ 'tuple': [':allow_tables', false] },
{ 'tuple': [':allow_fonts', false] },
{ 'tuple': [':scrub_policy', [
'Pleroma.HTML.Transform.MediaProxy',
'Pleroma.HTML.Scrubber.Default'
]] }
]
}
]

36
src/api/settings.js Normal file
View File

@ -0,0 +1,36 @@
import request from '@/utils/request'
import { getToken } from '@/utils/auth'
import { baseName } from './utils'
export async function fetchSettings(authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/config`,
method: 'get',
headers: authHeaders(token)
})
}
export async function updateSettings(configs, authHost, token) {
return await request({
baseURL: baseName(authHost),
url: `/api/pleroma/admin/config`,
method: 'post',
headers: authHeaders(token),
data: { configs }
})
}
export async function uploadMedia(file, authHost, token) {
const formData = new FormData()
formData.append('file', file)
return await request({
baseURL: baseName(authHost),
url: `/api/v1/media`,
method: 'post',
headers: authHeaders(token),
data: formData
})
}
const authHeaders = (token) => token ? { 'Authorization': `Bearer ${getToken()}` } : {}

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 490.2 490.2" style="enable-background:new 0 0 490.2 490.2;" xml:space="preserve">
<g>
<g>
<g>
<path d="M469.1,173.1h-37.5c-1-3.1-3.1-6.3-4.2-9.4l26.1-26.1c8.3-8.3,8.3-20.9,0-29.2l-71.9-71.9c-8.3-8.3-20.9-8.3-29.2,0
l-26.1,26.1c-3.1-2.1-6.3-3.1-9.4-4.2V20.9C316.9,9.4,307.5,0,296,0H193.9C182.4,0,173,9.4,173,20.9v37.5c-3.1,1-6.3,3.1-9.4,4.2
l-26.1-26.1c-8.3-8.3-20.9-8.3-29.2,0l-71.9,71.9c-4.2,4.2-6.3,9.4-6.3,14.6s2.1,10.4,6.3,14.6l26.1,26.1
c-2.1,3.1-3.1,6.3-4.2,9.4H20.9C9.4,173.1,0,182.5,0,194v102.2c0,11.5,9.4,20.9,20.9,20.9h37.5c1,3.1,3.1,6.3,4.2,9.4l-26.1,26.1
c-4.2,4.2-6.3,9.4-6.3,14.6s2.1,10.4,6.3,14.6l71.9,71.9c8.3,8.3,20.9,8.3,29.2,0l26.1-26.1c3.1,2.1,6.3,3.1,9.4,4.2v37.5
c0,11.5,9.4,20.9,20.9,20.9h102.2c11.5,0,20.9-9.4,20.9-20.9v-37.5c3.1-1,6.3-3.1,9.4-4.2l26.1,26.1c8.3,8.3,20.9,8.3,29.2,0
l71.9-71.9c8.3-8.3,8.3-20.9,0-29.2l-26.1-26.1c2.1-3.1,3.1-6.3,4.2-9.4h37.5c11.5,0,20.9-9.4,20.9-20.9V193.9
C490,182.4,480.6,173.1,469.1,173.1z M448.3,275.2H417c-9.4,0-16.7,6.3-19.8,14.6c-3.1,10.4-7.3,20.9-12.5,30.2
c-5.2,8.3-3.1,18.8,3.1,25l21.9,21.9L367,409.7l-21.9-21.9c-7.3-6.3-16.7-7.3-25-3.1c-9.4,5.2-19.8,9.4-30.2,12.5
c-8.3,2.1-14.6,10.4-14.6,19.8v31.3h-60.5l0,0V417c0-9.4-6.3-16.7-14.6-19.8c-10.4-3.1-20.9-7.3-30.2-12.5
c-8.3-5.2-18.8-3.1-25,3.1l-22,21.9L80.3,367l21.9-21.9c6.3-7.3,7.3-16.7,3.1-25c-5.2-9.4-9.4-19.8-12.5-30.2
c-2.1-8.3-10.4-14.6-19.8-14.6H41.7v-60.5H73c9.4,0,16.7-6.3,19.8-14.6c3.1-10.4,7.3-20.9,12.5-30.2c5.2-8.3,3.1-18.8-3.1-25
l-21.9-22L123,80.3l21.9,21.9c7.3,6.3,16.7,7.3,25,3.1c9.4-5.2,19.8-9.4,30.2-12.5c8.3-2.1,14.6-10.4,14.6-19.8V41.7h60.5V73
c0,9.4,6.3,16.7,14.6,19.8c10.4,3.1,20.9,7.3,30.2,12.5c8.3,5.2,18.8,3.1,25-3.1l22-21.9l42.7,42.7l-21.9,21.9
c-6.3,7.3-7.3,16.7-3.1,25c5.2,9.4,9.4,19.8,12.5,30.2c2.1,8.3,10.4,14.6,19.8,14.6h31.3L448.3,275.2L448.3,275.2z"/>
<path d="M245,131.4c-62.6,0-113.6,51.1-113.6,113.6s51,113.6,113.6,113.6s113.6-51,113.6-113.6S307.6,131.4,245,131.4z
M245,316.9c-39.6,0-71.9-32.3-71.9-71.9s32.3-71.9,71.9-71.9s71.9,32.3,71.9,71.9S284.6,316.9,245,316.9z"/>
</g>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -65,7 +65,8 @@ export default {
i18n: 'I18n',
externalLink: 'External Link',
users: 'Users',
reports: 'Reports'
reports: 'Reports',
settings: 'Settings'
},
navbar: {
logOut: 'Log Out',
@ -273,5 +274,30 @@ export default {
open: 'Open',
closed: 'Closed',
resolved: 'Resolved'
},
settings: {
settings: 'Settings',
instance: 'Instance',
upload: 'Upload',
mailer: 'Mailer',
logger: 'Logger',
activityPub: 'ActivityPub',
auth: 'Authentication',
autoLinker: 'Auto Linker',
captcha: 'Captcha',
frontend: 'Frontend',
http: 'HTTP',
mrf: 'MRF',
mediaProxy: 'Media Proxy',
metadata: 'Metadata',
gopher: 'Gopher',
endpoint: 'Endpoint',
jobQueue: 'Job queue',
webPush: 'Web push encryption',
esshd: 'BBS / SSH access',
rateLimiters: 'Rate limiters',
database: 'Database',
other: 'Other',
success: 'Settings changed successfully!'
}
}

View File

@ -76,6 +76,18 @@ export const asyncRouterMap = [
}
]
},
{
path: '/settings',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/settings/index'),
name: 'Settings',
meta: { title: 'settings', icon: 'settings', noCache: true }
}
]
},
{
path: '/users/:id',
component: Layout,

View File

@ -16,6 +16,82 @@ const getters = {
addRouters: state => state.permission.addRouters,
errorLogs: state => state.errorLog.logs,
users: state => state.users.fetchedUsers,
authHost: state => state.user.authHost
authHost: state => state.user.authHost,
activityPubConfig: state => state.settings.settings['activitypub'],
adminTokenConfig: state => state.settings.settings['admin_token'],
assetsConfig: state => state.settings.settings['assets'],
authConfig: state => state.settings.settings['auth'],
autoLinkerConfig: state => state.settings.settings['auto_linker'],
captchaConfig: state => state.settings.settings['Pleroma.Captcha'],
chatConfig: state => state.settings.settings['chat'],
consoleConfig: state => state.settings.settings['console'],
corsPlugCredentials: state => state.settings.settings['credentials'],
corsPlugExposeConfig: state => state.settings.settings['expose'],
corsPlugHeaders: state => state.settings.settings['headers'],
corsPlugMaxAge: state => state.settings.settings['max_age'],
corsPlugMethods: state => state.settings.settings['methods'],
databaseConfig: state => state.settings.settings['database'],
ectoReposConfig: state => state.settings.settings['ecto_repos'],
emojiConfig: state => state.settings.settings['emoji'],
enabledConfig: state => state.settings.settings['enabled'],
endpointConfig: state => state.settings.settings['Pleroma.Web.Endpoint'],
exsysloggerConfig: state => state.settings.settings['ex_syslogger'],
facebookConfig: state => state.settings.settings['Ueberauth.Strategy.Facebook.OAuth'],
fetchInitialPostsConfig: state => state.settings.settings['fetch_initial_posts'],
formatEncodersConfig: state => state.settings.settings['format_encoders'],
frontendConfig: state => state.settings.settings['frontend_configurations'],
googleConfig: state => state.settings.settings['Ueberauth.Strategy.Google.OAuth'],
gopherConfig: state => state.settings.settings['gopher'],
hackneyPoolsConfig: state => state.settings.settings['hackney_pools'],
handlerConfig: state => state.settings.settings['handler'],
httpConfig: state => state.settings.settings['http'],
httpSecurityConfig: state => state.settings.settings['http_security'],
instanceConfig: state => state.settings.settings['instance'],
kocaptchaConfig: state => state.settings.settings['Pleroma.Captcha.Kocaptcha'],
levelConfig: state => state.settings.settings['level'],
ldapConfig: state => state.settings.settings['ldap'],
loggerBackendsConfig: state => state.settings.settings['backends'],
mailerConfig: state => state.settings.settings['Pleroma.Emails.Mailer'],
markupConfig: state => state.settings.settings['markup'],
mediaProxyConfig: state => state.settings.settings['media_proxy'],
metaConfig: state => state.settings.settings['meta'],
metadataConfig: state => state.settings.settings['Pleroma.Web.Metadata'],
microsoftConfig: state => state.settings.settings['Ueberauth.Strategy.Microsoft.OAuth'],
mimeTypesConfig: state => state.settings.settings['types'],
mrfHellthreadConfig: state => state.settings.settings['mrf_hellthread'],
mrfKeywordConfig: state => state.settings.settings['mrf_keyword'],
mrfMentionConfig: state => state.settings.settings['mrf_mention'],
mrfNormalizeMarkupConfig: state => state.settings.settings['mrf_normalize_markup'],
mrfRejectnonpublicConfig: state => state.settings.settings['mrf_rejectnonpublic'],
mrfSimpleConfig: state => state.settings.settings['mrf_simple'],
mrfSubchainConfig: state => state.settings.settings['mrf_subchain'],
mrfUserAllowlistConfig: state => state.settings.settings['mrf_user_allowlist'],
oauth2Config: state => state.settings.settings['oauth2'],
passwordAuthenticatorConfig: state => state.settings.settings['password_authenticator'],
pleromaAuthenticatorConfig: state => state.settings.settings['Pleroma.Web.Auth.Authenticator'],
pleromaRepoConfig: state => state.settings.settings['Pleroma.Repo'],
pleromaUserConfig: state => state.settings.settings['Pleroma.User'],
portConfig: state => state.settings.settings['port'],
privDirConfig: state => state.settings.settings['priv_dir'],
queuesConfig: state => state.settings.settings['queues'],
rateLimitersConfig: state => state.settings.settings['rate_limit'],
retryQueueConfig: state => state.settings.settings['Pleroma.Web.Federator.RetryQueue'],
richMediaConfig: state => state.settings.settings['rich_media'],
suggestionsConfig: state => state.settings.settings['suggestions'],
scheduledActivityConfig: state => state.settings.settings['Pleroma.ScheduledActivity'],
teslaAdapterConfig: state => state.settings.settings['adapter'],
twitterConfig: state => state.settings.settings['Ueberauth.Strategy.Twitter.OAuth'],
ueberauthConfig: state => state.settings.settings['Ueberauth'],
uploadAnonymizeFilenameConfig: state => state.settings.settings['Pleroma.Upload.Filter.AnonymizeFilename'],
uploadConfig: state => state.settings.settings['Pleroma.Upload'],
uploadFilterMogrifyConfig: state => state.settings.settings['Pleroma.Upload.Filter.Mogrify'],
uploadersLocalConfig: state => state.settings.settings['Pleroma.Uploaders.Local'],
uploadMDIIConfig: state => state.settings.settings['Pleroma.Uploaders.MDII'],
uploadS3Config: state => state.settings.settings['Pleroma.Uploaders.S3'],
uriSchemesConfig: state => state.settings.settings['uri_schemes'],
userConfig: state => state.settings.settings['user'],
vapidDetailsConfig: state => state.settings.settings['vapid_details'],
webhookUrlConfig: state => state.settings.settings['webhook_url']
}
export default getters

View File

@ -4,6 +4,7 @@ import app from './modules/app'
import errorLog from './modules/errorLog'
import permission from './modules/permission'
import reports from './modules/reports'
import settings from './modules/settings'
import tagsView from './modules/tagsView'
import user from './modules/user'
import userProfile from './modules/userProfile'
@ -18,6 +19,7 @@ const store = new Vuex.Store({
errorLog,
permission,
reports,
settings,
tagsView,
user,
userProfile,

View File

@ -0,0 +1,188 @@
const nonAtomsTuples = ['replace', 'match_actor', ':replace', ':match_actor']
const groups = {
'cors_plug': [
'credentials',
'expose',
'headers',
'max_age',
'methods'
],
'esshd': [
'enabled',
'handler',
'password_authenticator',
'port',
'priv_dir'
],
'logger': ['backends', 'console', 'ex_syslogger'],
'mime': ['types'],
'phoenix': ['format_encoders'],
'pleroma': [
'Pleroma.Captcha',
'Pleroma.Captcha.Kocaptcha',
'Pleroma.Emails.Mailer',
'Pleroma.Repo',
'Pleroma.ScheduledActivity',
'Pleroma.Upload',
'Pleroma.Upload.Filter.AnonymizeFilename',
'Pleroma.Upload.Filter.Mogrify',
'Pleroma.Uploaders.Local',
'Pleroma.Uploaders.MDII',
'Pleroma.Uploaders.S3',
'Pleroma.User',
'Pleroma.Web.Auth.Authenticator',
'Pleroma.Web.Endpoint',
'Pleroma.Web.Federator.RetryQueue',
'Pleroma.Web.Metadata',
'activitypub',
'admin_token',
'assets',
'auth',
'auto_linker',
'chat',
'database',
'ecto_repos',
'emoji',
'env',
'fetch_initial_posts',
'frontend_configurations',
'gopher',
'hackney_pools',
'http',
'http_security',
'instance',
'ldap',
'markup',
'media_proxy',
'mrf_hellthread',
'mrf_keyword',
'mrf_mention',
'mrf_normalize_markup',
'mrf_rejectnonpublic',
'mrf_simple',
'mrf_subchain',
'mrf_user_allowlist',
'oauth2',
'rate_limit',
'rich_media',
'suggestions',
'uri_schemes',
'user'
],
'pleroma_job_queue': ['queues'],
'quack': ['level', 'meta', 'webhook_url'],
'tesla': ['adapter'],
'ueberauth': [
'Ueberauth',
'Ueberauth.Strategy.Facebook.OAuth',
'Ueberauth.Strategy.Google.OAuth',
'Ueberauth.Strategy.Microsoft.OAuth',
'Ueberauth.Strategy.Twitter.OAuth'
],
'web_push_encryption': ['vapid_details']
}
export const filterIgnored = (settings, ignored) => {
if (settings.enabled.value === true) {
return settings
}
return ignored.reduce((acc, name) => {
const { [name]: ignored, ...newAcc } = acc
return newAcc
}, settings)
}
// REFACTOR
export const parseTuples = (tuples, key) => {
return tuples.reduce((accum, item) => {
if (key === 'rate_limit') {
accum[item.tuple[0].substr(1)] = item.tuple[1]
} else if (Array.isArray(item.tuple[1]) &&
(typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) {
nonAtomsTuples.includes(item.tuple[0])
? accum[item.tuple[0].substr(1)] = parseNonAtomTuples(item.tuple[1])
: accum[item.tuple[0].substr(1)] = parseTuples(item.tuple[1])
} else if (item.tuple[1] && typeof item.tuple[1] === 'object' && 'tuple' in item.tuple[1]) {
accum[item.tuple[0].substr(1)] = item.tuple[1]['tuple'].join('.')
} else {
key === 'mrf_user_allowlist'
? accum[item.tuple[0]] = item.tuple[1]
: accum[item.tuple[0].substr(1)] = item.tuple[1]
}
return accum
}, {})
}
const parseNonAtomTuples = (tuples) => {
return tuples.reduce((acc, item) => {
acc[item.tuple[0]] = item.tuple[1]
return acc
}, {})
}
export const valueHasTuples = (key, value) => {
const valueIsArrayOfNonObjects = Array.isArray(value) && value.length > 0 && typeof value[0] !== 'object'
return key === 'meta' ||
key === 'types' ||
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean' ||
valueIsArrayOfNonObjects
}
// REFACTOR
export const wrapConfig = settings => {
return Object.keys(settings).map(config => {
const group = getGroup(config)
const key = config.startsWith('Pleroma') || config.startsWith('Ueberauth') ? config : `:${config}`
const value = (settings[config]['value'] !== undefined)
? settings[config]['value']
: Object.keys(settings[config]).reduce((acc, settingName) => {
const data = settings[config][settingName]
if (data === '') {
return acc
} else if (key === ':rate_limit') {
return [...acc, { 'tuple': [`:${settingName}`, data] }]
} else if (settingName === 'ip') {
const ip = data.split('.')
return [...acc, { 'tuple': [`:${settingName}`, { 'tuple': ip }] }]
} else if (!Array.isArray(data) && typeof data === 'object') {
return nonAtomsTuples.includes(settingName)
? [...acc, { 'tuple': [`:${settingName}`, wrapNonAtomsTuples(data)] }]
: [...acc, { 'tuple': [`:${settingName}`, wrapNestedTuples(data)] }]
}
return key === ':mrf_user_allowlist'
? [...acc, { 'tuple': [`${settingName}`, settings[config][settingName]] }]
: [...acc, { 'tuple': [`:${settingName}`, settings[config][settingName]] }]
}, [])
return { group, key, value }
})
}
const wrapNestedTuples = setting => {
return Object.keys(setting).reduce((acc, settingName) => {
const data = setting[settingName]
if (data === '') {
return acc
} else if (settingName === 'ip') {
const ip = data.split('.')
return [...acc, { 'tuple': [`:${settingName}`, { 'tuple': ip }] }]
} else if (!Array.isArray(data) && typeof data === 'object') {
return [...acc, { 'tuple': [`:${settingName}`, wrapNestedTuples(data)] }]
}
return [...acc, { 'tuple': [`:${settingName}`, setting[settingName]] }]
}, [])
}
const wrapNonAtomsTuples = setting => {
return Object.keys(setting).reduce((acc, settingName) => {
return [...acc, { 'tuple': [`${settingName}`, setting[settingName]] }]
}, [])
}
const getGroup = key => {
return Object.keys(groups).find(i => groups[i].includes(key))
}

View File

@ -0,0 +1,145 @@
import { fetchSettings, updateSettings, uploadMedia } from '@/api/settings'
import { initialSettings } from '@/api/initialDataForConfig'
import { filterIgnored, parseTuples, valueHasTuples, wrapConfig } from './normalizers'
const settings = {
state: {
settings: {
'activitypub': {},
'adapter': {},
'admin_token': {},
'assets': { mascots: {}},
'auth': {},
'auto_linker': { opts: {}},
'backends': {},
'chat': {},
'console': { colors: {}},
'credentials': {},
'database': {},
'ecto_repos': {},
'emoji': { groups: {}},
'enabled': {},
'ex_syslogger': {},
'expose': {},
'fetch_initial_posts': {},
'format_encoders': {},
'frontend_configurations': { pleroma_fe: {}, masto_fe: {}},
'gopher': {},
'hackney_pools': { federation: {}, media: {}, upload: {}},
'handler': {},
'headers': {},
'http': { adapter: {}},
'http_security': {},
'instance': { poll_limits: {}},
'level': {},
'ldap': {},
'markup': {},
'max_age': {},
'media_proxy': { proxy_opts: {}},
'meta': {},
'methods': {},
'mrf_hellthread': {},
'mrf_keyword': { replace: {}},
'mrf_mention': {},
'mrf_normalize_markup': {},
'mrf_rejectnonpublic': {},
'mrf_simple': {},
'mrf_subchain': { match_actor: {}},
'mrf_user_allowlist': {},
'oauth2': {},
'password_authenticator': {},
'Pleroma.Captcha': {},
'Pleroma.Captcha.Kocaptcha': {},
'Pleroma.Emails.Mailer': {},
'Pleroma.Repo': {},
'Pleroma.ScheduledActivity': {},
'Pleroma.Upload': { proxy_opts: {}},
'Pleroma.Upload.Filter.AnonymizeFilename': {},
'Pleroma.Upload.Filter.Mogrify': {},
'Pleroma.Uploaders.Local': {},
'Pleroma.Uploaders.MDII': {},
'Pleroma.Uploaders.S3': {},
'Pleroma.User': {},
'Pleroma.Web.Auth.Authenticator': {},
'Pleroma.Web.Endpoint':
{ http: false, url: {}, render_errors: {}, pubsub: {}},
'Pleroma.Web.Federator.RetryQueue': {},
'Pleroma.Web.Metadata': {},
'port': {},
'priv_dir': {},
'queues': {},
'rate_limit': {},
'rich_media': {},
'suggestions': {},
'types': { value: {}},
'Ueberauth': {},
'Ueberauth.Strategy.Facebook.OAuth': {},
'Ueberauth.Strategy.Google.OAuth': {},
'Ueberauth.Strategy.Microsoft.OAuth': {},
'Ueberauth.Strategy.Twitter.OAuth': {},
'user': {},
'uri_schemes': {},
'vapid_details': {},
'webhook_url': {}
},
ignoredIfNotEnabled: ['enabled', 'handler', 'password_authenticator', 'port', 'priv_dir'],
loading: true
},
mutations: {
REWRITE_CONFIG: (state, { tab, data }) => {
state.settings[tab] = data
},
SET_LOADING: (state, status) => {
state.loading = status
},
SET_SETTINGS: (state, data) => {
const newSettings = data.reduce((acc, config) => {
const key = config.key[0] === ':' ? config.key.substr(1) : config.key
const value = valueHasTuples(key, config.value) ? { value: config.value } : parseTuples(config.value, key)
acc[key] = { ...acc[key], ...value }
return acc
}, state.settings)
state.settings = newSettings
},
UPDATE_SETTINGS: (state, { tab, data }) => {
Object.keys(state.settings).map(configName => {
if (configName === tab) {
state.settings[configName] = { ...state.settings[configName], ...data }
}
})
}
},
actions: {
async FetchSettings({ commit, dispatch, getters }) {
commit('SET_LOADING', true)
const response = await fetchSettings(getters.authHost, getters.token)
if (response.data.configs.length === 0) {
dispatch('SubmitChanges', initialSettings)
} else {
commit('SET_SETTINGS', response.data.configs)
}
commit('SET_LOADING', false)
},
RewriteConfig({ commit }, { tab, data }) {
commit('REWRITE_CONFIG', { tab, data })
},
async SubmitChanges({ getters, commit, state }, data) {
const filteredSettings = filterIgnored(state.settings, state.ignoredIfNotEnabled)
const configs = data || wrapConfig(filteredSettings)
const response = await updateSettings(configs, getters.authHost, getters.token)
if (data) {
commit('SET_SETTINGS', response.data.configs)
}
},
UpdateSettings({ commit }, { tab, data }) {
commit('UPDATE_SETTINGS', { tab, data })
},
async UploadMedia({ dispatch, getters, state }, { file, tab, inputName, childName }) {
const response = await uploadMedia(file, getters.authHost, getters.token)
const updatedValue = { ...state.settings[tab][inputName], ...{ [childName]: response.data.url }}
dispatch('UpdateSettings', { tab, data: { [inputName]: updatedValue }})
}
}
}
export default settings

View File

@ -0,0 +1,84 @@
<template>
<div>
<el-form ref="activityPub" :model="activityPub" :label-width="labelWidth">
<el-form-item label="Accept blocks">
<el-switch :value="activityPub.accept_blocks" @change="updateSetting($event, 'activitypub', 'accept_blocks')"/>
<p class="expl">Whether to accept incoming block activities from other instances</p>
</el-form-item>
<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>
</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>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
export default {
name: 'ActivityPub',
computed: {
...mapGetters([
'activityPubConfig',
'userConfig'
]),
activityPub() {
return this.activityPubConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
},
user() {
return this.userConfig
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,303 @@
<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>
</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-input :value="auth.oauth_consumer_strategies" @input="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>
<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>
<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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
export default {
name: 'Authentication',
computed: {
...mapGetters([
'pleromaAuthenticatorConfig',
'ldapConfig',
'authConfig',
'ueberauthConfig',
'oauth2Config',
'facebookConfig',
'googleConfig',
'twitterConfig',
'microsoftConfig'
]),
auth() {
return this.authConfig
},
ldap() {
return this.ldapConfig
},
oauth2() {
return this.oauth2Config
},
pleromaAuthenticator() {
return this.pleromaAuthenticatorConfig
},
ueberauth() {
return this.ueberauthConfig
},
facebook() {
return this.facebookConfig
},
google() {
return this.googleConfig
},
twitter() {
return this.twitterConfig
},
microsoft() {
return this.microsoftConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,127 @@
<template>
<el-form v-if="!loading" ref="auto_linker" :model="auto_linker" :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="auto_linker.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="auto_linker.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="auto_linker.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="auto_linker.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="auto_linker.opts.validate_tld" @change="processNestedData($event, 'auto_linker', 'opts', 'validate_tld')"/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
export default {
name: 'AutoLinker',
computed: {
...mapGetters([
'autoLinkerConfig'
]),
auto_linker() {
return this.autoLinkerConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
},
loading() {
return this.$store.state.settings.loading
},
booleanClass() {
return this.getBooleanValue('class')
},
booleanRel() {
return this.getBooleanValue('rel')
},
booleanTruncate() {
return this.getBooleanValue('truncate')
}
},
methods: {
getBooleanValue(name) {
const value = this.autoLinkerConfig.opts[name]
return typeof value === 'string' || typeof value === 'number'
},
getNumValue(name) {
const value = this.autoLinkerConfig.opts[name]
return value || 0
},
getStringValue(name) {
const value = this.autoLinkerConfig.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)
}
},
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,76 @@
<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" placeholder="Select" @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>
</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>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
export default {
name: 'Captcha',
computed: {
...mapGetters([
'captchaConfig',
'kocaptchaConfig'
]),
captcha() {
return this.captchaConfig
},
kocaptcha() {
return this.kocaptchaConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,183 @@
<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 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" @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 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 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" @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>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'Instance',
computed: {
...mapGetters([
'databaseConfig',
'ectoReposConfig',
'pleromaRepoConfig'
]),
database() {
return this.databaseConfig
},
ectoRepos() {
return this.ectoReposConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
},
pleromaRepo() {
return this.pleromaRepoConfig
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,266 @@
<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 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-input :value="endpoint.render_errors.accepts" @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>
<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-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>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import { options } from './options'
import AceEditor from 'vue2-ace-editor'
import 'brace/mode/elixir'
import 'default-passive-events'
export default {
name: 'Endpoint',
components: {
editor: AceEditor
},
computed: {
...mapGetters([
'endpointConfig'
]),
editorContentHttp: {
get: function() {
return this.endpointConfig.http.dispatch ? this.endpointConfig.http.dispatch[0] : ''
},
set: function(value) {
this.processNestedData([value], 'Pleroma.Web.Endpoint', 'http', 'dispatch')
}
},
editorContentHttps: {
get: function() {
return this.endpointConfig.https.dispatch ? this.endpointConfig.https.dispatch[0] : ''
},
set: function(value) {
this.processNestedData([value], 'Pleroma.Web.Endpoint', 'https', 'dispatch')
}
},
configureHttp() {
return !this.endpoint.http === false
},
configureHttps() {
return !this.endpoint.https === false
},
endpoint() {
return this.endpointConfig
},
endpointHttp() {
return this.endpoint.http || {}
},
endpointHttps() {
return this.endpoint.https || {}
},
extraCookieAttrsOptions() {
return options.extraCookieAttrsOptions
},
instrumentersOptions() {
return options.instrumentersOptions
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
},
loading() {
return this.$store.state.settings.loading
}
},
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)
}
},
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,114 @@
<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>
<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
<span class="code">ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key</span>.
After restarting, you should be able to connect to your Pleroma instance with <span class="code">ssh username@server -p $PORT</span>
</p>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'Instance',
computed: {
...mapGetters([
'enabledConfig',
'handlerConfig',
'passwordAuthenticatorConfig',
'portConfig',
'privDirConfig'
]),
enabled() {
return this.enabledConfig
},
handler() {
return this.handlerConfig
},
passwordAuthenticator() {
return this.passwordAuthenticatorConfig
},
port() {
return this.portConfig
},
privDir() {
return this.privDirConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
toggleEsshd(value) {
this.$store.dispatch('ToggleEsshd', value)
},
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,451 @@
<template>
<div>
<el-form ref="frontend" :model="frontend" :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" @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" @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" @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" @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>
</el-form>
<div class="line"/>
<el-form ref="assets" :model="assets" :label-width="labelWidth">
<el-form-item label="Assets:"/>
<el-form-item label="Default mascot">
<el-select :value="assets.default_mascot" @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>
</el-form>
<div class="line"/>
<el-form ref="emoji" :model="emoji" :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>
</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>
<div class="line"/>
<el-form ref="markup" :model="markup" :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>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import { options } from './options'
export default {
name: 'Frontend',
computed: {
...mapGetters([
'assetsConfig',
'frontendConfig',
'emojiConfig',
'chatConfig',
'markupConfig'
]),
assets() {
return this.assetsConfig
},
chat() {
return this.chatConfig
},
emoji() {
return this.emojiConfig
},
frontend() {
return this.frontendConfig
},
groups() {
return Object.keys(this.emojiConfig.groups).map(key => [key, this.emojiConfig.groups[key]])
},
markup() {
return this.markupConfig
},
mascots() {
return Object.keys(this.assetsConfig.mascots)
.map(mascotName =>
[mascotName, this.assetsConfig.mascots[mascotName].url, this.assetsConfig.mascots[mascotName].mime_type])
},
themeOptions() {
return options.themeOptions
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
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')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,63 @@
<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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'Gopher',
computed: {
...mapGetters([
'gopherConfig'
]),
gopher() {
return this.gopherConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,190 @@
<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>
</el-form>
<div class="line"/>
<el-form ref="corsPlugMaxAge" :model="corsPlugMaxAge" :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>
</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" placeholder="Select" @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>
<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>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'HTTP',
computed: {
...mapGetters([
'corsPlugCredentials',
'corsPlugExposeConfig',
'corsPlugHeaders',
'corsPlugMaxAge',
'corsPlugMethods',
'hackneyPoolsConfig',
'httpConfig',
'httpSecurityConfig',
'metricsExporter'
]),
corsPlugExpose() {
return this.corsPlugExposeConfig
},
hackneyPools() {
return this.hackneyPoolsConfig
},
http() {
return this.httpConfig
},
httpSecurity() {
return this.httpSecurityConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
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')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,407 @@
<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="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 @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 @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 rewritePolicy"
: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 placeholder="Select" @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 placeholder="Select" @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" @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 placeholder="Select" @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" @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="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>
</el-form>
<div class="line"/>
<el-form ref="uri_schemes" :model="uri_schemes" :label-width="labelWidth">
<el-form-item label="URI schemes">
<el-select :value="uri_schemes.valid_schemes || []" multiple filterable allow-create placeholder="Select" @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>
<div class="line"/>
<el-form ref="admin_token" :model="admin_token" :label-width="labelWidth">
<el-form-item label="Admin token">
<el-input :value="admin_token.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>
<div class="line"/>
<el-form ref="scheduled_activity" :model="scheduled_activity" :label-width="labelWidth">
<el-form-item label="Scheduled activity:"/>
<el-form-item label="Daily user limit">
<el-input-number :value="scheduled_activity.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="scheduled_activity.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="scheduled_activity.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>
<div class="line"/>
<el-form ref="fetch_initial_posts" :model="fetch_initial_posts" :label-width="labelWidth">
<el-form-item label="Fetch initial posts">
<el-switch :value="fetch_initial_posts.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="fetch_initial_posts.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>
<div class="line"/>
<el-form ref="suggestions" :model="suggestions" :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>
</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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
import { options } from './options'
export default {
name: 'Instance',
computed: {
...mapGetters([
'adminTokenConfig',
'fetchInitialPostsConfig',
'instanceConfig',
'pleromaUserConfig',
'scheduledActivityConfig',
'suggestionsConfig',
'uriSchemesConfig'
]),
admin_token() {
return this.adminTokenConfig
},
autofollowedNicknamesOptions() {
return options.autofollowedNicknamesOptions
},
federationPublisherModulesOptions() {
return options.federationPublisherModulesOptions
},
fetch_initial_posts() {
return this.fetchInitialPostsConfig
},
instance() {
return this.instanceConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
},
pleromaUser() {
return this.pleromaUserConfig
},
quarantinedInstancesOptions() {
return options.quarantinedInstancesOptions
},
restrictedNicknamesOptions() {
return options.restrictedNicknamesOptions
},
rewritePolicy() {
return typeof this.instance.rewrite_policy === 'string' ? [this.instance.rewrite_policy] : this.instance.rewrite_policy
},
rewritePolicyOptions() {
return options.rewritePolicyOptions
},
scheduled_activity() {
return this.scheduledActivityConfig
},
suggestions() {
return this.suggestionsConfig
},
uri_schemes() {
return this.uriSchemesConfig
},
uriSchemesOptions() {
return options.uriSchemesOptions
}
},
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')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,95 @@
<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>
</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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'JobQueue',
computed: {
...mapGetters([
'queuesConfig',
'retryQueueConfig'
]),
queues() {
return this.queuesConfig
},
retryQueue() {
return this.retryQueueConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,247 @@
<template>
<div>
<el-form ref="loggerBackends" :model="loggerBackends" :label-width="labelWidth">
<el-form-item label="Backends">
<el-select :value="loggerBackendsValue" multiple @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">List of instances to remove medias from</p>
</el-form-item>
</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" @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>
<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" @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 @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>
<div class="line"/>
<el-form ref="webhookUrl" :model="webhookUrl" :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" @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>
<el-form-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import { options } from './options'
export default {
name: 'Logger',
computed: {
...mapGetters([
'consoleConfig',
'exsysloggerConfig',
'levelConfig',
'loggerBackendsConfig',
'metaConfig',
'webhookUrlConfig'
]),
consoleLogger() {
return this.consoleConfig
},
exsyslogger() {
return this.exsysloggerConfig
},
level() {
return this.levelConfig
},
loggerBackends() {
return this.loggerBackendsConfig
},
loggerBackendsValue() {
return this.loggerBackends.value ? this.loggerBackends.value.map(el => JSON.stringify(el)) : []
},
loggerBackendsOptions() {
return options.loggerBackendsOptions
},
meta() {
return this.metaConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
},
webhookUrl() {
return this.webhookUrlConfig
}
},
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')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,285 @@
<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>
</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>
<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>
<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>
<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>
<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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
import { options } from './options'
export default {
name: 'MRF',
data: function() {
return {
removableDoubleOptions: ['replace', 'mrfUserAllowlist'],
removableSingleOptions: ['keywordReject', 'federatedTimelineRemoval']
}
},
computed: {
...mapGetters([
'mrfHellthreadConfig',
'mrfKeywordConfig',
'mrfMentionConfig',
'mrfNormalizeMarkupConfig',
'mrfSimpleConfig',
'mrfSubchainConfig',
'mrfRejectnonpublicConfig',
'mrfUserAllowlistConfig'
]),
matchActor() {
return Object.keys(this.mrfSubchain.match_actor).map(key => [key, this.mrfSubchain.match_actor[key]])
},
mrfHellthread() {
return this.mrfHellthreadConfig
},
mrfKeyword() {
return this.mrfKeywordConfig
},
mrfMention() {
return this.mrfMentionConfig
},
mrfNormalizeMarkup() {
return this.mrfNormalizeMarkupConfig
},
mrfSimple() {
return this.mrfSimpleConfig
},
mrfSubchain() {
return this.mrfSubchainConfig
},
mrfRejectnonpublic() {
return this.mrfRejectnonpublicConfig
},
mrfUserAllowlist() {
return this.mrfUserAllowlistConfig
},
policiesOptions() {
return options.rewritePolicyOptions
},
replacePatterns() {
return Object.keys(this.mrfKeywordConfig.replace).map(key => [key, this.mrfKeywordConfig.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'
}
},
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')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,195 @@
<template>
<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" @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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
import { options } from './options'
import AceEditor from 'vue2-ace-editor'
import 'brace/mode/elixir'
import 'default-passive-events'
export default {
name: 'Mailer',
components: {
editor: AceEditor
},
computed: {
...mapGetters([
'mailerConfig'
]),
editorContent: {
get: function() {
return this.mailerConfig.dkim ? this.mailerConfig.dkim[0] : ''
},
set: function(value) {
this.updateSetting([value], 'Pleroma.Emails.Mailer', 'dkim')
}
},
mailer() {
return this.mailerConfig
},
adapterOptions() {
return options.adapterOptions
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,146 @@
<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" @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 @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" @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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
import { options } from './options'
export default {
name: 'MediaProxy',
computed: {
...mapGetters([
'mediaProxyConfig'
]),
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 || {}
},
mediaProxy() {
return this.mediaProxyConfig
},
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'
}
},
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')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,95 @@
<template>
<div>
<el-form ref="metadata" :model="metadata" :label-width="labelWidth">
<el-form-item label="Providers">
<el-select :value="metadata.providers || []" multiple @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>
</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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import i18n from '@/lang'
export default {
name: 'Metadata',
computed: {
...mapGetters([
'metadataConfig',
'richMediaConfig'
]),
metadata() {
return this.metadataConfig
},
richMedia() {
return this.richMediaConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,100 @@
<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>
</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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'Other',
computed: {
...mapGetters([
'formatEncodersConfig',
'mimeTypesConfig',
'teslaAdapterConfig'
]),
formatEncoders() {
return this.formatEncodersConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
},
mimeTypes() {
return Object.keys(this.mimeTypesConfig.value).map(key => [key, this.mimeTypesConfig.value[key]])
},
teslaAdapter() {
return this.teslaAdapterConfig
}
},
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')
this.$message({
type: 'success',
message: i18n.t('settings.success')
})
},
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 }})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,362 @@
<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>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'RateLimiters',
computed: {
...mapGetters([
'rateLimitersConfig'
]),
accountConfirmationResendAllUsers() {
return this.rateLimiters.account_confirmation_resend ? this.rateLimiters.account_confirmation_resend.tuple : [null, null]
},
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
},
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
},
rateLimiters() {
return this.rateLimitersConfig
},
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
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
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)
}
},
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,238 @@
<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" @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 @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" @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>
</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>
<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>
<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>
<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="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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
import { options } from './options'
export default {
name: 'Upload',
computed: {
...mapGetters([
'uploadAnonymizeFilenameConfig',
'uploadConfig',
'uploadFilterMogrifyConfig',
'uploadersLocalConfig',
'uploadMDIIConfig',
'uploadS3Config'
]),
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 || {}
},
upload() {
return this.uploadConfig
},
uploadersLocal() {
return this.uploadersLocalConfig
},
uploadAnonymizeFilename() {
return this.uploadAnonymizeFilenameConfig
},
uploadFilterMogrify() {
return this.uploadFilterMogrifyConfig
},
uploadMDII() {
return this.uploadMDIIConfig
},
uploadS3() {
return this.uploadS3Config
},
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'
}
},
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.uploadConfig[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')
}
},
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,61 @@
<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-item>
<el-button type="primary" @click="onSubmit">Submit</el-button>
</el-form-item>
</el-form>
</template>
<script>
import i18n from '@/lang'
import { mapGetters } from 'vuex'
export default {
name: 'WebPush',
computed: {
...mapGetters([
'vapidDetailsConfig'
]),
vapidDetails() {
return this.vapidDetailsConfig
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
labelWidth() {
return this.isMobile ? '100px' : '210px'
}
},
methods: {
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')
})
}
}
}
</script>
<style rel='stylesheet/scss' lang='scss'>
@import '../styles/main';
@include settings
</style>

View File

@ -0,0 +1,21 @@
export { default as ActivityPub } from './ActivityPub'
export { default as Authentication } from './Authentication'
export { default as AutoLinker } from './AutoLinker'
export { default as Captcha } from './Captcha'
export { default as Database } from './Database'
export { default as Endpoint } from './Endpoint'
export { default as Esshd } from './Esshd'
export { default as Frontend } from './Frontend'
export { default as Gopher } from './Gopher'
export { default as Http } from './Http'
export { default as Instance } from './Instance'
export { default as JobQueue } from './JobQueue'
export { default as Logger } from './Logger'
export { default as Mailer } from './Mailer'
export { default as MediaProxy } from './MediaProxy'
export { default as Metadata } from './Metadata'
export { default as Mrf } from './MRF'
export { default as Other } from './Other'
export { default as RateLimiters } from './RateLimiters'
export { default as Upload } from './Upload'
export { default as WebPush } from './WebPush'

View File

@ -0,0 +1,116 @@
export const options = {
federationPublisherModulesOptions: [
{ label: 'Pleroma.Web.ActivityPub.Publisher', value: 'Pleroma.Web.ActivityPub.Publisher' },
{ label: 'Pleroma.Web.Websub', value: 'Pleroma.Web.Websub' },
{ label: 'Pleroma.Web.Salmon', value: 'Pleroma.Web.Salmon' }],
rewritePolicyOptions: [
{ label: 'NoOpPolicy', value: 'Pleroma.Web.ActivityPub.MRF.NoOpPolicy', expl: 'NoOpPolicy: 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)' }
],
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

@ -0,0 +1,100 @@
<template>
<div class="settings-container">
<h1>{{ $t('settings.settings') }}</h1>
<el-tabs :tab-position="tabPosition">
<el-tab-pane :label="$t('settings.activityPub')">
<activity-pub/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.auth')">
<authentication/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.autoLinker')">
<auto-linker/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.esshd')">
<esshd/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.captcha')">
<captcha/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.database')">
<database/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.endpoint')">
<endpoint/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.frontend')">
<frontend/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.gopher')">
<gopher/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.http')">
<http/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.instance')">
<instance/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.jobQueue')">
<job-queue/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.logger')">
<logger/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.mailer')">
<mailer/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.mediaProxy')">
<media-proxy/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.metadata')">
<metadata/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.mrf')">
<mrf/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.rateLimiters')">
<rate-limiters/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.upload')">
<upload/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.webPush')">
<web-push/>
</el-tab-pane>
<el-tab-pane :label="$t('settings.other')">
<other/>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Upload, WebPush } from './components'
export default {
components: { ActivityPub, Authentication, AutoLinker, Captcha, Database, Endpoint, Esshd, Frontend, Gopher, Http, Instance, JobQueue, Logger, Mailer, MediaProxy, Metadata, Mrf, Other, RateLimiters, Upload, WebPush },
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
tabPosition() {
return this.isMobile ? 'top' : 'left'
}
},
mounted: function() {
this.$store.dispatch('FetchSettings')
}
}
</script>
<style rel='stylesheet/scss' lang='scss' scoped>
.settings-container {
.el-tabs {
margin-top: 20px
}
h1 {
margin: 22px 0 0 15px;
}
}
</style>

View File

@ -0,0 +1,156 @@
@mixin settings {
a {
text-decoration: underline;
}
.code {
background-color: #adbed67a;
border-radius: 3px;
font-family: monospace;
padding: 0 3px 0 3px;
}
.el-form-item {
margin-right: 30px;
}
.el-select {
width: 100%;
}
.esshd-list {
margin: 0;
}
.expl {
color: #666666;
font-size: 13px;
line-height: 22px;
margin: 5px 0 0 0;
overflow-wrap: break-word;
}
.highlight {
background-color: #e6e6e6;
}
.limit-button-container {
display: flex;
align-items: baseline;
}
.limit-expl {
margin-left: 10px;
}
.limit-input {
width: 48%;
margin: 0 0 5px 8px
}
.line {
width: 100%;
height: 0;
border: 1px solid #eee;
margin-bottom: 22px;
}
.mascot-container {
margin-bottom: 15px;
}
.mascot-input {
margin-bottom: 7px;
}
.mascot-name-container {
display: flex;
margin-bottom: 7px;
}
.mascot-name-input {
margin-right: 10px
}
.name-input {
width: 30%;
margin-right: 8px
}
.options-paragraph {
font-size: 14px;
color: #606266;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei";
font-weight: 700;
line-height: 20px;
margin: 0 0 14px 0;
}
.options-paragraph-container {
overflow-wrap: break-word;
margin-bottom: 0;
}
.pattern-input {
width: 20%;
margin-right: 8px
}
.setting-input {
display: flex;
margin-bottom: 10px;
}
.single-input {
margin-right: 10px
}
.scale-input {
width: 48%;
margin: 0 8px 5px 0
}
.replacement-input {
width: 80%;
margin-left: 8px;
margin-right: 10px
}
.text {
line-height: 20px;
margin-right: 15px
}
.upload-container {
display: flex;
align-items: baseline;
}
.value-input {
width: 70%;
margin-left: 8px;
margin-right: 10px
}
@media only screen and (max-width: 760px),
(min-device-width: 768px) and (max-device-width: 1024px) {
.el-form-item {
margin-right: 15px;
}
.el-input__inner {
padding: 0 5px 0 5px
}
.el-form-item__label:not(.no-top-margin) {
padding-left: 3px;
padding-right: 10px;
line-height: 22px;
margin-top: 7px;
}
.el-message {
min-width: 80%;
}
.el-select__tags {
overflow: hidden;
}
.name-input {
width: 40%;
margin-right: 5px
}
p.expl {
line-height: 20px;
}
.pattern-input {
width: 40%;
margin-right: 4px
}
.replacement-input {
width: 60%;
margin-left: 4px;
margin-right: 5px
}
.top-margin {
position: absolute;
top: 25%;
}
.value-input {
width: 60%;
margin-left: 5px;
margin-right: 8px
}
}
}

View File

@ -1868,6 +1868,11 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58"
integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=
braces@^2.2.2, braces@^2.3.1, braces@^2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
@ -3144,6 +3149,11 @@ default-gateway@^2.6.0:
execa "^0.10.0"
ip-regex "^2.1.0"
default-passive-events@^1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/default-passive-events/-/default-passive-events-1.0.10.tgz#28ad3269648a76a0158f413d66e37af24dad053a"
integrity sha1-KK0yaWSKdqAVj0E9ZuN68k2tBTo=
default-require-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-2.0.0.tgz#f5f8fbb18a7d6d50b21f641f649ebb522cfe24f7"
@ -10190,6 +10200,13 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
vue2-ace-editor@^0.0.13:
version "0.0.13"
resolved "https://registry.yarnpkg.com/vue2-ace-editor/-/vue2-ace-editor-0.0.13.tgz#5528998ce2c13e8ed3a294f714298199fd107dc2"
integrity sha512-uQICyvJzYNix16xeYjNAINuNUQhPbqMR7UQsJeI+ycpEd2otsiNNU73jcZqHkpjuz0uaHDHnrpzQuI/RApsKXA==
dependencies:
brace "^0.11.0"
vue@^2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.8.tgz#f21cbc536bfc14f7d1d792a137bb12f69e60ea91"