diff --git a/CHANGELOG.md b/CHANGELOG.md
index d7a6de166906b833b7d4faba38693782ebccca03..bd324b8112c681826e0c6664f32249aba5ae624f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Add Report show page and link Moderation log references to the respective reports
 - Add Unconfimed filter for Users table
 - Filter users by actor type: Person, Bot or Application
-- Add ability to configure Media Preview Proxy, User Backup and Websocket based federation settings
+- Add ability to configure Media Preview Proxy, User Backup, Websocket based federation and Pleroma.Web.Endpoint.MetricsExporter settings
 - Mobile and Tablet UI for Single Report show page
 ### Changed
 
@@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Move `:restrict_unauthenticated` settings from Authentication tab to Instance tab
 - Replace regular inputs with textareas for setting welcome messages in the Settings section
 - Update rendering Moderation Log Messages so that all usernames are links to the pages of the corresponding users in Admin-FE
+- Remove Websocket based federation settings
 
 ### Fixed
 
diff --git a/src/store/modules/normalizers.js b/src/store/modules/normalizers.js
index e647a4a78dc4a0d08900551554aa35b5f1d3dd71..150865d00e1ccb97d8111b4fb3203fc51faebb83 100644
--- a/src/store/modules/normalizers.js
+++ b/src/store/modules/normalizers.js
@@ -57,10 +57,18 @@ export const parseNonTuples = (key, value) => {
 // REFACTOR
 export const parseTuples = (tuples, key) => {
   return tuples.reduce((accum, item) => {
-    if (key === ':rate_limit') {
-      accum[item.tuple[0]] = Array.isArray(item.tuple[1])
-        ? item.tuple[1].map(el => el.tuple)
-        : item.tuple[1].tuple
+    if (key === ':rate_limit' ||
+      (key === 'Pleroma.Web.Endpoint.MetricsExporter' && item.tuple[0] === ':auth')) {
+      const getValue = () => {
+        if (typeof item.tuple[1] === 'boolean') {
+          return item.tuple[1]
+        } else if (Array.isArray(item.tuple[1])) {
+          return item.tuple[1].map(el => el.tuple)
+        } else {
+          return item.tuple[1].tuple
+        }
+      }
+      accum[item.tuple[0]] = getValue()
     } else if (item.tuple[0] === ':mascots') {
       accum[item.tuple[0]] = item.tuple[1].reduce((acc, mascot) => {
         return [...acc, { [mascot.tuple[0]]: { ...mascot.tuple[1], id: `f${(~~(Math.random() * 1e8)).toString(16)}` }}]
@@ -92,6 +100,8 @@ export const parseTuples = (tuples, key) => {
       accum[item.tuple[0]] = parseStringOrTupleValue(item.tuple[0], item.tuple[1])
     } else if (item.tuple[0] === ':args') {
       accum[item.tuple[0]] = parseNonTuples(item.tuple[0], item.tuple[1])
+    } else if (item.tuple[0] === ':ip_whitelist') {
+      accum[item.tuple[0]] = item.tuple[1].map(ip => typeof ip === 'string' ? ip : ip.tuple.join('.'))
     } else if (Array.isArray(item.tuple[1]) &&
       (typeof item.tuple[1][0] === 'object' && !Array.isArray(item.tuple[1][0])) && item.tuple[1][0]['tuple']) {
       accum[item.tuple[0]] = parseTuples(item.tuple[1], item.tuple[0])
@@ -237,8 +247,9 @@ const wrapValues = (settings, currentState) => {
       return { 'tuple': [setting, wrapValues(value, currentState)] }
     } else if (prependWithСolon(type, value)) {
       return { 'tuple': [setting, `:${value}`] }
-    } else if (type.includes('tuple') && (type.includes('string') || type.includes('atom'))) {
-      return typeof value === 'string'
+    } else if (type.includes('tuple') &&
+      (type.includes('string') || type.includes('atom') || type.includes('boolean'))) {
+      return typeof value === 'string' || typeof value === 'boolean'
         ? { 'tuple': [setting, value] }
         : { 'tuple': [setting, { 'tuple': value }] }
     } else if (type === 'reversed_tuple') {
diff --git a/src/views/settings/components/Http.vue b/src/views/settings/components/Http.vue
index 7b82537e6490c61b97a094b4219bc3ed36e5f54b..8aa8a23bf9fbc2b1eba409333aed197d35e53e32 100644
--- a/src/views/settings/components/Http.vue
+++ b/src/views/settings/components/Http.vue
@@ -11,14 +11,10 @@
     <el-form :model="httpSecurityData" :label-position="labelPosition" :label-width="labelWidth">
       <setting :setting-group="httpSecurity" :data="httpSecurityData"/>
     </el-form>
-    <el-divider v-if="httpSecurity" class="divider thick-line"/>
+    <el-divider v-if="webCacheTtl" class="divider thick-line"/>
     <el-form :model="webCacheTtlData" :label-position="labelPosition" :label-width="labelWidth">
       <setting :setting-group="webCacheTtl" :data="webCacheTtlData"/>
     </el-form>
-    <el-divider v-if="fedSockets" class="divider thick-line"/>
-    <el-form :model="fedSocketsData" :label-position="labelPosition" :label-width="labelWidth">
-      <setting :setting-group="fedSockets" :data="fedSocketsData"/>
-    </el-form>
     <div class="submit-button-container">
       <el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button>
     </div>
@@ -44,12 +40,6 @@ export default {
     corsPlugData() {
       return _.get(this.settings.settings, [':cors_plug']) || {}
     },
-    fedSockets() {
-      return this.settings.description.find(setting => setting.key === ':fed_sockets')
-    },
-    fedSocketsData() {
-      return _.get(this.settings.settings, [':pleroma', ':fed_sockets']) || {}
-    },
     http() {
       return this.settings.description.find(setting => setting.key === ':http')
     },
diff --git a/src/views/settings/components/Inputs.vue b/src/views/settings/components/Inputs.vue
index 85e41ef094a5bd06930da3d27528c4449dc4a264..e052fefd058ca964f63a747bae3ce8c1d9c9f963 100644
--- a/src/views/settings/components/Inputs.vue
+++ b/src/views/settings/components/Inputs.vue
@@ -113,7 +113,7 @@
         <!-- special inputs -->
         <editable-keyword-input v-if="editableKeyword(setting.key, setting.type)" :data="keywordData" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
         <icons-input v-if="setting.key === ':icons'" :data="iconsData" :setting-group="settingGroup" :setting="setting"/>
-        <link-formatter-input v-if="booleanCombinedInput" :data="data" :setting-group="settingGroup" :setting="setting"/>
+        <boolean-combined-input v-if="booleanCombinedInput" :data="data" :setting-group="settingGroup" :setting="setting"/>
         <mascots-input v-if="setting.key === ':mascots'" :data="keywordData" :setting-group="settingGroup" :setting="setting"/>
         <proxy-url-input v-if="setting.key === ':proxy_url'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting" :parents="settingParent"/>
         <prune-input v-if="setting.key === ':prune'" :data="data[setting.key]" :setting-group="settingGroup" :setting="setting"/>
@@ -141,7 +141,7 @@ import {
   EditableKeywordInput,
   IconsInput,
   ImageUploadInput,
-  LinkFormatterInput,
+  BooleanCombinedInput,
   MascotsInput,
   ProxyUrlInput,
   PruneInput,
@@ -160,7 +160,7 @@ export default {
     EditableKeywordInput,
     IconsInput,
     ImageUploadInput,
-    LinkFormatterInput,
+    BooleanCombinedInput,
     MascotsInput,
     ProxyUrlInput,
     PruneInput,
@@ -363,6 +363,7 @@ export default {
     },
     renderMultipleSelect(type) {
       return !this.reducedSelects && Array.isArray(type) && this.setting.key !== ':backends' && this.setting.key !== ':args' && (
+        this.setting.key === ':ip_whitelist' ||
         type.includes('module') ||
         (type.includes('list') && type.includes('string')) ||
         (type.includes('list') && type.includes('atom')) ||
diff --git a/src/views/settings/components/Other.vue b/src/views/settings/components/Other.vue
index 0a69713897fe53a244b5a34773fd6f4927a4c7cb..91a9fe2a60d8df6242ff837bd3fd237606826a9b 100644
--- a/src/views/settings/components/Other.vue
+++ b/src/views/settings/components/Other.vue
@@ -2,6 +2,10 @@
   <div v-if="!loading" :class="isSidebarOpen" class="form-container">
     <editor-input v-model="termsOfServicesContent" :name="'terms-of-service'" @input="handleEditorUpdate"/>
     <el-divider class="divider thick-line"/>
+    <el-form :model="prometheusMetricsData" :label-position="labelPosition" :label-width="labelWidth">
+      <setting :setting-group="prometheusMetrics" :data="prometheusMetricsData"/>
+    </el-form>
+    <el-divider v-if="prometheusMetrics" class="divider thick-line"/>
     <el-form :model="backupData" :label-position="labelPosition" :label-width="labelWidth">
       <setting :setting-group="backup" :data="backupData"/>
     </el-form>
@@ -94,6 +98,12 @@ export default {
     modulesData() {
       return _.get(this.settings.settings, [':pleroma', ':modules']) || {}
     },
+    prometheusMetrics() {
+      return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Endpoint.MetricsExporter')
+    },
+    prometheusMetricsData() {
+      return _.get(this.settings.settings, [':prometheus', 'Pleroma.Web.Endpoint.MetricsExporter']) || {}
+    },
     remoteIp() {
       return this.settings.description.find(setting => setting.key === 'Pleroma.Web.Plugs.RemoteIp')
     },
diff --git a/src/views/settings/components/inputComponents/LinkFormatterInput.vue b/src/views/settings/components/inputComponents/BooleanCombinedInput.vue
similarity index 52%
rename from src/views/settings/components/inputComponents/LinkFormatterInput.vue
rename to src/views/settings/components/inputComponents/BooleanCombinedInput.vue
index 38ec2e5091d53ec87211b9a29d4a3960f85be3e5..e47ade3de54d12d8d5628e6e5ad19e4fe08f2453 100644
--- a/src/views/settings/components/inputComponents/LinkFormatterInput.vue
+++ b/src/views/settings/components/inputComponents/BooleanCombinedInput.vue
@@ -1,34 +1,46 @@
 <template>
   <div>
     <div v-if="setting.type.includes('string')" :data-search="setting.key || setting.group">
-      <el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
+      <el-switch :value="booleanValue" @change="processTwoTypeValue($event, setting.key)"/>
       <el-input
-        v-if="autoLinkerBooleanValue"
-        :value="autoLinkerStringValue"
+        v-if="booleanValue"
+        :value="stringValue"
         @input="processTwoTypeValue($event, setting.key)"/>
     </div>
     <div v-if="setting.type.includes('integer')" :data-search="setting.key || setting.group">
-      <el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
+      <el-switch :value="booleanValue" @change="processTwoTypeValue($event, setting.key)"/>
       <el-input-number
-        v-if="autoLinkerBooleanValue"
-        :value="autoLinkerIntegerValue"
+        v-if="booleanValue"
+        :value="integerValue"
         @input="processTwoTypeValue($event, setting.key)"/>
     </div>
     <div v-if="setting.type.includes('atom')" :data-search="setting.key || setting.group">
-      <el-switch :value="autoLinkerBooleanValue" @change="processTwoTypeValue($event, setting.key)"/>
+      <el-switch :value="booleanValue" @change="processTwoTypeValue($event, setting.key)"/>
       <el-input
-        v-if="autoLinkerBooleanValue"
-        :value="autoLinkerAtomValue"
+        v-if="booleanValue"
+        :value="atomValue"
         @input="processTwoTypeValue($event, setting.key)">
         <template slot="prepend">:</template>
       </el-input>
     </div>
+    <div v-if="setting.type.includes('tuple')" :data-search="setting.key || setting.group">
+      <el-switch :value="booleanValue" @change="processTupleTwoTypeValue($event, setting.key)"/>
+      <div v-if="booleanValue" class="tuple-input-container">
+        <el-input
+          v-for="(item, index) in tupleValue"
+          :value="item"
+          :key="index"
+          :placeholder="getPlaceholder[index]"
+          class="tuple-input"
+          @input="processTupleTwoTypeValue($event, setting.key, index)"/>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
 export default {
-  name: 'LinkFormatterInput',
+  name: 'BooleanCombinedInput',
   props: {
     data: {
       type: [Object, Array],
@@ -50,24 +62,42 @@ export default {
     }
   },
   computed: {
-    autoLinkerAtomValue() {
+    atomValue() {
       return this.data[this.setting.key] &&
         this.data[this.setting.key][0] === ':' ? this.data[this.setting.key].substr(1) : this.data[this.setting.key]
     },
-    autoLinkerBooleanValue() {
+    booleanValue() {
       const value = this.data[this.setting.key]
-      return typeof value === 'string' || typeof value === 'number'
+      return typeof value !== 'boolean'
+    },
+    getPlaceholder() {
+      return { 0: ':basic', 1: 'username', 2: 'password' }
     },
-    autoLinkerIntegerValue() {
+    integerValue() {
       const value = this.data[this.setting.key]
       return value || 0
     },
-    autoLinkerStringValue() {
+    stringValue() {
       const value = this.data[this.setting.key]
       return value || ''
+    },
+    tupleValue() {
+      const value = this.data[this.setting.key]
+      return value || ['', '', '']
     }
   },
   methods: {
+    processTupleTwoTypeValue(value, input, _index) {
+      if (value === false) {
+        this.updateSetting(value, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
+      } else if (value === true) {
+        this.updateSetting(['', '', ''], this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
+      } else {
+        const data = [...this.tupleValue]
+        data[_index] = value
+        this.updateSetting(data, this.settingGroup.group, this.settingGroup.key, input, this.setting.type)
+      }
+    },
     processTwoTypeValue(value, input) {
       if (value === true) {
         const data = input === ':truncate' ? 0 : ''
diff --git a/src/views/settings/components/inputComponents/index.js b/src/views/settings/components/inputComponents/index.js
index f7801b8e8ae55bafdb39b4a32255ede5b447bed4..be8097d2c4f941f4ddf1e7867b1ee90466dbc6db 100644
--- a/src/views/settings/components/inputComponents/index.js
+++ b/src/views/settings/components/inputComponents/index.js
@@ -1,8 +1,8 @@
+export { default as BooleanCombinedInput } from './BooleanCombinedInput'
 export { default as EditableKeywordInput } from './EditableKeywordInput'
 export { default as EditorInput } from './EditorInput'
 export { default as IconsInput } from './IconsInput'
 export { default as ImageUploadInput } from './ImageUploadInput'
-export { default as LinkFormatterInput } from './LinkFormatterInput'
 export { default as MascotsInput } from './MascotsInput'
 export { default as ProxyUrlInput } from './ProxyUrlInput'
 export { default as PruneInput } from './PruneInput'
diff --git a/src/views/settings/components/tabs.js b/src/views/settings/components/tabs.js
index 6871ca9bb67efbe884f354fe58a54c5478dba160..e3a98a3c84f048a347f0825e0f73828f938fc83d 100644
--- a/src/views/settings/components/tabs.js
+++ b/src/views/settings/components/tabs.js
@@ -26,7 +26,7 @@ export const tabs = description => {
     },
     'http': {
       label: 'settings.http',
-      settings: [':cors_plug', ':http', ':fed_sockets', ':http_security', ':web_cache_ttl']
+      settings: [':cors_plug', ':http', ':http_security', ':web_cache_ttl']
     },
     'instance': {
       label: 'settings.instance',
@@ -78,7 +78,7 @@ export const tabs = description => {
     },
     'other': {
       label: 'settings.other',
-      settings: [':mime', 'Pleroma.User.Backup', 'Pleroma.Web.Plugs.RemoteIp', ':modules', 'Pleroma.Web.ApiSpec.CastAndValidate', ':terms_of_services']
+      settings: [':mime', 'Pleroma.User.Backup', 'Pleroma.Web.Plugs.RemoteIp', 'Pleroma.Web.Endpoint.MetricsExporter', ':modules', 'Pleroma.Web.ApiSpec.CastAndValidate', ':terms_of_services']
     }
   }
 }
diff --git a/src/views/settings/styles/main.scss b/src/views/settings/styles/main.scss
index db55ac707aab9e54948b0e4b0aed6e19daa68c59..c55208ad22c33523707066a6970bede0b685e858 100644
--- a/src/views/settings/styles/main.scss
+++ b/src/views/settings/styles/main.scss
@@ -352,6 +352,15 @@
     line-height: 20px;
     margin-right: 15px
   }
+  .tuple-input {
+    margin-right: 15px;
+  }
+  .tuple-input:last-child {
+    margin-right: 0;
+  }
+  .tuple-input-container {
+    display: flex;
+  }
   .upload-container {
     display: flex;
     align-items: baseline;