pleroma-fe/src/modules/adminSettings.js

286 lines
11 KiB
JavaScript

import { set, get, cloneDeep, differenceWith, isEqual, flatten } from 'lodash'
export const defaultState = {
frontends: [],
loaded: false,
needsReboot: null,
config: null,
modifiedPaths: null,
descriptions: null,
draft: null,
dbConfigEnabled: null
}
export const newUserFlags = {
...defaultState.flagStorage
}
const adminSettingsStorage = {
state: {
...cloneDeep(defaultState)
},
mutations: {
setInstanceAdminNoDbConfig (state) {
state.loaded = false
state.dbConfigEnabled = false
},
setAvailableFrontends (state, { frontends }) {
state.frontends = frontends.map(f => {
f.installedRefs = f.installed_refs
if (f.name === 'pleroma-fe') {
f.refs = ['master', 'develop']
} else {
f.refs = [f.ref]
}
return f
})
},
updateAdminSettings (state, { config, modifiedPaths }) {
state.loaded = true
state.dbConfigEnabled = true
state.config = config
state.modifiedPaths = modifiedPaths
},
updateAdminDescriptions (state, { descriptions }) {
state.descriptions = descriptions
},
updateAdminDraft (state, { path, value }) {
const [group, key, subkey] = path
const parent = [group, key, subkey]
set(state.draft, path, value)
// force-updating grouped draft to trigger refresh of group settings
if (path.length > parent.length) {
set(state.draft, parent, cloneDeep(get(state.draft, parent)))
}
},
resetAdminDraft (state) {
state.draft = cloneDeep(state.config)
}
},
actions: {
async fetchAdminUsers (store, opts) {
const users = await store.rootState.api.backendInteractor.adminListUsers({opts})
users.forEach(user => store.dispatch('fetchUserIfMissing', user.id))
return users
},
adminAddUserToAdminGroup (store, user) {
store.rootState.api.backendInteractor.adminAddUserToAdminGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'admin', value: res.is_admin }))
},
adminRemoveUserFromAdminGroup (store, user) {
// prevent revokation of own rights
if (user.id !== store.rootState.users.currentUser.id) {
return store.rootState.api.backendInteractor.adminRemoveUserFromAdminGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'admin', value: res.is_admin }))
}
},
adminAddUserToModeratorGroup (store, user) {
return store.rootState.api.backendInteractor.adminAddUserToModeratorGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'moderator', value: res.is_moderator }))
},
adminRemoveUserFromModeratorGroup (store, user) {
// prevent revokation of own rights
if (user.id !== store.state.users.currentUser.id) {
return store.rootState.api.backendInteractor.adminRemoveUserFromModeratorGroup({ user })
.then(res => store.commit('updateRight', { user, right: 'moderator', value: res.is_moderator }))
}
},
adminActivateUser (store, user) {
return store.rootState.api.backendInteractor.activateUser({ user })
.then(res => { const deactivated = !res.is_active; store.commit('updateActivationStatus', { user, deactivated })})
},
adminDeactivateUser (store, user) {
return store.rootState.api.backendInteractor.deactivateUser({ user })
.then(res => { const deactivated = !res.is_active; store.commit('updateActivationStatus', { user, deactivated })})
},
adminDeleteUser (store, user) {
return store.rootState.api.backendInteractor.deleteUser({ user })
},
adminConfirmUser (store, user) {
return store.rootState.api.backendInteractor.adminConfirmUser({ user })
.then(() => store.dispatch('fetchUser', user.id))
},
adminResendConfirmationEmail (store, user) {
return store.rootState.api.backendInteractor.adminResendConfirmationEmail({ user })
},
adminApproveUser (store, user) {
return store.rootState.api.backendInteractor.adminApproveUser({ user })
},
adminListStatuses (store, { user, opts }) {
return store.rootState.api.backendInteractor.adminListStatuses({ user, opts })
},
adminChangeStatusScope (store, { opts }) {
return store.rootState.api.backendInteractor.adminChangeStatusScope({ opts })
},
adminDisableMFA (store, { opts }) {
return store.rootState.api.backendInteractor.adminDisableMFA({ opts })
},
loadFrontendsStuff ({ rootState, commit }) {
rootState.api.backendInteractor.fetchAvailableFrontends()
.then(frontends => commit('setAvailableFrontends', { frontends }))
},
loadAdminStuff ({ state, rootState, dispatch, commit }) {
rootState.api.backendInteractor.fetchInstanceDBConfig()
.then(backendDbConfig => {
if (backendDbConfig.error) {
if (backendDbConfig.error.status === 400) {
backendDbConfig.error.json().then(errorJson => {
if (/configurable_from_database/.test(errorJson.error)) {
commit('setInstanceAdminNoDbConfig')
}
})
}
} else {
dispatch('setInstanceAdminSettings', { backendDbConfig })
}
})
if (state.descriptions === null) {
rootState.api.backendInteractor.fetchInstanceConfigDescriptions()
.then(backendDescriptions => dispatch('setInstanceAdminDescriptions', { backendDescriptions }))
}
},
setInstanceAdminSettings ({ state, commit }, { backendDbConfig }) {
const config = state.config || {}
const modifiedPaths = new Set()
backendDbConfig.configs.forEach(c => {
const path = [c.group, c.key]
if (c.db) {
// Path elements can contain dot, therefore we use ' -> ' as a separator instead
// Using strings for modified paths for easier searching
c.db.forEach(x => modifiedPaths.add([...path, x].join(' -> ')))
}
const convert = (value) => {
if (Array.isArray(value) && value.length > 0 && value[0].tuple) {
return value.reduce((acc, c) => {
return { ...acc, [c.tuple[0]]: convert(c.tuple[1]) }
}, {})
} else {
return value
}
}
set(config, path, convert(c.value))
})
commit('updateAdminSettings', { config, modifiedPaths })
commit('resetAdminDraft')
},
setInstanceAdminDescriptions ({ commit }, { backendDescriptions }) {
const convert = ({ children, description, label, key = '<ROOT>', group, suggestions }, path, acc) => {
const newPath = group ? [group, key] : [key]
const obj = { description, label, suggestions }
if (Array.isArray(children)) {
children.forEach(c => {
convert(c, newPath, obj)
})
}
set(acc, newPath, obj)
}
const descriptions = {}
backendDescriptions.forEach(d => convert(d, '', descriptions))
commit('updateAdminDescriptions', { descriptions })
},
// This action takes draft state, diffs it with live config state and then pushes
// only differences between the two. Difference detection only work up to subkey (third) level.
pushAdminDraft ({ rootState, state, dispatch }) {
// TODO cleanup paths in modifiedPaths
const convert = (value) => {
if (typeof value !== 'object') {
return value
} else if (Array.isArray(value)) {
return value.map(convert)
} else {
return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
}
}
// Getting all group-keys used in config
const allGroupKeys = flatten(
Object
.entries(state.config)
.map(
([group, lv1data]) => Object
.keys(lv1data)
.map((key) => ({ group, key }))
)
)
// Only using group-keys where there are changes detected
const changedGroupKeys = allGroupKeys.filter(({ group, key }) => {
return !isEqual(state.config[group][key], state.draft[group][key])
})
// Here we take all changed group-keys and get all changed subkeys
const changed = changedGroupKeys.map(({ group, key }) => {
const config = state.config[group][key]
const draft = state.draft[group][key]
// We convert group-key value into entries arrays
const eConfig = Object.entries(config)
const eDraft = Object.entries(draft)
// Then those entries array we diff so only changed subkey entries remain
// We use the diffed array to reconstruct the object and then shove it into convert()
return ({ group, key, value: convert(Object.fromEntries(differenceWith(eDraft, eConfig, isEqual))) })
})
rootState.api.backendInteractor.pushInstanceDBConfig({
payload: {
configs: changed
}
})
.then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
.then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
},
pushAdminSetting ({ rootState, dispatch }, { path, value }) {
const [group, key, ...rest] = Array.isArray(path) ? path : path.split(/\./g)
const clone = {} // not actually cloning the entire thing to avoid excessive writes
set(clone, rest, value)
// TODO cleanup paths in modifiedPaths
const convert = (value) => {
if (typeof value !== 'object') {
return value
} else if (Array.isArray(value)) {
return value.map(convert)
} else {
return Object.entries(value).map(([k, v]) => ({ tuple: [k, v] }))
}
}
rootState.api.backendInteractor.pushInstanceDBConfig({
payload: {
configs: [{
group,
key,
value: convert(clone)
}]
}
})
.then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
.then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
},
resetAdminSetting ({ rootState, state, dispatch }, { path }) {
const [group, key, subkey] = path.split(/\./g)
state.modifiedPaths.delete(path)
return rootState.api.backendInteractor.pushInstanceDBConfig({
payload: {
configs: [{
group,
key,
delete: true,
subkeys: [subkey]
}]
}
})
.then(() => rootState.api.backendInteractor.fetchInstanceDBConfig())
.then(backendDbConfig => dispatch('setInstanceAdminSettings', { backendDbConfig }))
}
}
}
export default adminSettingsStorage