logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git

adminSettings.js (7973B)


  1. import { set, get, cloneDeep, differenceWith, isEqual, flatten } from 'lodash'
  2. export const defaultState = {
  3. frontends: [],
  4. loaded: false,
  5. needsReboot: null,
  6. config: null,
  7. modifiedPaths: null,
  8. descriptions: null,
  9. draft: null,
  10. dbConfigEnabled: null
  11. }
  12. export const newUserFlags = {
  13. ...defaultState.flagStorage
  14. }
  15. const adminSettingsStorage = {
  16. state: {
  17. ...cloneDeep(defaultState)
  18. },
  19. mutations: {
  20. setInstanceAdminNoDbConfig (state) {
  21. state.loaded = false
  22. state.dbConfigEnabled = false
  23. },
  24. setAvailableFrontends (state, { frontends }) {
  25. state.frontends = frontends.map(f => {
  26. f.installedRefs = f.installed_refs
  27. if (f.name === 'pleroma-fe') {
  28. f.refs = ['master', 'develop']
  29. } else {
  30. f.refs = [f.ref]
  31. }
  32. return f
  33. })
  34. },
  35. updateAdminSettings (state, { config, modifiedPaths }) {
  36. state.loaded = true
  37. state.dbConfigEnabled = true
  38. state.config = config
  39. state.modifiedPaths = modifiedPaths
  40. },
  41. updateAdminDescriptions (state, { descriptions }) {
  42. state.descriptions = descriptions
  43. },
  44. updateAdminDraft (state, { path, value }) {
  45. const [group, key, subkey] = path
  46. const parent = [group, key, subkey]
  47. set(state.draft, path, value)
  48. // force-updating grouped draft to trigger refresh of group settings
  49. if (path.length > parent.length) {
  50. set(state.draft, parent, cloneDeep(get(state.draft, parent)))
  51. }
  52. },
  53. resetAdminDraft (state) {
  54. state.draft = cloneDeep(state.config)
  55. }
  56. },
  57. actions: {
  58. loadFrontendsStuff ({ state, rootState, dispatch, commit }) {
  59. rootState.api.backendInteractor.fetchAvailableFrontends()
  60. .then(frontends => commit('setAvailableFrontends', { frontends }))
  61. },
  62. loadAdminStuff ({ state, rootState, dispatch, commit }) {
  63. rootState.api.backendInteractor.fetchInstanceDBConfig()
  64. .then(backendDbConfig => {
  65. if (backendDbConfig.error) {
  66. if (backendDbConfig.error.status === 400) {
  67. backendDbConfig.error.json().then(errorJson => {
  68. if (/configurable_from_database/.test(errorJson.error)) {
  69. commit('setInstanceAdminNoDbConfig')
  70. }
  71. })
  72. }
  73. } else {
  74. dispatch('setInstanceAdminSettings', { backendDbConfig })
  75. }
  76. })
  77. if (state.descriptions === null) {
  78. rootState.api.backendInteractor.fetchInstanceConfigDescriptions()
  79. .then(backendDescriptions => dispatch('setInstanceAdminDescriptions', { backendDescriptions }))
  80. }
  81. },
  82. setInstanceAdminSettings ({ state, commit, dispatch }, { backendDbConfig }) {
  83. const config = state.config || {}
  84. const modifiedPaths = new Set()
  85. backendDbConfig.configs.forEach(c => {
  86. const path = [c.group, c.key]
  87. if (c.db) {
  88. // Path elements can contain dot, therefore we use ' -> ' as a separator instead
  89. // Using strings for modified paths for easier searching
  90. c.db.forEach(x => modifiedPaths.add([...path, x].join(' -> ')))
  91. }
  92. const convert = (value) => {
  93. if (Array.isArray(value) && value.length > 0 && value[0].tuple) {
  94. return value.reduce((acc, c) => {
  95. return { ...acc, [c.tuple[0]]: convert(c.tuple[1]) }
  96. }, {})
  97. } else {
  98. return value
  99. }
  100. }
  101. set(config, path, convert(c.value))
  102. })
  103. commit('updateAdminSettings', { config, modifiedPaths })
  104. commit('resetAdminDraft')
  105. },
  106. setInstanceAdminDescriptions ({ state, commit, dispatch }, { backendDescriptions }) {
  107. const convert = ({ children, description, label, key = '<ROOT>', group, suggestions }, path, acc) => {
  108. const newPath = group ? [group, key] : [key]
  109. const obj = { description, label, suggestions }
  110. if (Array.isArray(children)) {
  111. children.forEach(c => {
  112. convert(c, newPath, obj)
  113. })
  114. }
  115. set(acc, newPath, obj)
  116. }
  117. const descriptions = {}
  118. backendDescriptions.forEach(d => convert(d, '', descriptions))
  119. commit('updateAdminDescriptions', { descriptions })
  120. },
  121. // This action takes draft state, diffs it with live config state and then pushes
  122. // only differences between the two. Difference detection only work up to subkey (third) level.
  123. pushAdminDraft ({ rootState, state, commit, dispatch }) {
  124. // TODO cleanup paths in modifiedPaths
  125. const convert = (value) => {
  126. if (typeof value !== 'object') {
  127. return value
  128. } else if (Array.isArray(value)) {
  129. return value.map(convert)
  130. } else {
  131. return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
  132. }
  133. }
  134. // Getting all group-keys used in config
  135. const allGroupKeys = flatten(
  136. Object
  137. .entries(state.config)
  138. .map(
  139. ([group, lv1data]) => Object
  140. .keys(lv1data)
  141. .map((key) => ({ group, key }))
  142. )
  143. )
  144. // Only using group-keys where there are changes detected
  145. const changedGroupKeys = allGroupKeys.filter(({ group, key }) => {
  146. return !isEqual(state.config[group][key], state.draft[group][key])
  147. })
  148. // Here we take all changed group-keys and get all changed subkeys
  149. const changed = changedGroupKeys.map(({ group, key }) => {
  150. const config = state.config[group][key]
  151. const draft = state.draft[group][key]
  152. // We convert group-key value into entries arrays
  153. const eConfig = Object.entries(config)
  154. const eDraft = Object.entries(draft)
  155. // Then those entries array we diff so only changed subkey entries remain
  156. // We use the diffed array to reconstruct the object and then shove it into convert()
  157. return ({ group, key, value: convert(Object.fromEntries(differenceWith(eDraft, eConfig, isEqual))) })
  158. })
  159. rootState.api.backendInteractor.pushInstanceDBConfig({
  160. payload: {
  161. configs: changed
  162. }
  163. })
  164. .then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
  165. .then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
  166. },
  167. pushAdminSetting ({ rootState, state, commit, dispatch }, { path, value }) {
  168. const [group, key, ...rest] = Array.isArray(path) ? path : path.split(/\./g)
  169. const clone = {} // not actually cloning the entire thing to avoid excessive writes
  170. set(clone, rest, value)
  171. // TODO cleanup paths in modifiedPaths
  172. const convert = (value) => {
  173. if (typeof value !== 'object') {
  174. return value
  175. } else if (Array.isArray(value)) {
  176. return value.map(convert)
  177. } else {
  178. return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
  179. }
  180. }
  181. rootState.api.backendInteractor.pushInstanceDBConfig({
  182. payload: {
  183. configs: [{
  184. group,
  185. key,
  186. value: convert(clone)
  187. }]
  188. }
  189. })
  190. .then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
  191. .then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
  192. },
  193. resetAdminSetting ({ rootState, state, commit, dispatch }, { path }) {
  194. const [group, key, subkey] = path.split(/\./g)
  195. state.modifiedPaths.delete(path)
  196. return rootState.api.backendInteractor.pushInstanceDBConfig({
  197. payload: {
  198. configs: [{
  199. group,
  200. key,
  201. delete: true,
  202. subkeys: [subkey]
  203. }]
  204. }
  205. })
  206. .then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
  207. .then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
  208. }
  209. }
  210. }
  211. export default adminSettingsStorage