diff --git a/admin/src/components/ActionButtons/index.js b/admin/src/components/ActionButtons/index.js index d08194c..f88f286 100644 --- a/admin/src/components/ActionButtons/index.js +++ b/admin/src/components/ActionButtons/index.js @@ -1,16 +1,18 @@ import React, { useState } from 'react'; import styled from 'styled-components'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { isEmpty } from 'lodash'; import { Button } from '@strapi/design-system/Button'; +import { Map } from 'immutable'; import ConfirmModal from '../ConfirmModal'; import { exportAllConfig, importAllConfig } from '../../state/actions/Config'; -const ActionButtons = ({ diff }) => { +const ActionButtons = () => { const dispatch = useDispatch(); const [modalIsOpen, setModalIsOpen] = useState(false); const [actionType, setActionType] = useState(''); + const partialDiff = useSelector((state) => state.getIn(['config', 'partialDiff'], Map({}))).toJS(); const closeModal = () => { setActionType(''); @@ -24,16 +26,16 @@ const ActionButtons = ({ diff }) => { return ( - - - {!isEmpty(diff.diff) && ( -

{Object.keys(diff.diff).length} {Object.keys(diff.diff).length === 1 ? "config change" : "config changes"}

+ + + {!isEmpty(partialDiff) && ( +

{Object.keys(partialDiff).length} {Object.keys(partialDiff).length === 1 ? "config change" : "config changes"}

)} actionType === 'import' ? dispatch(importAllConfig()) : dispatch(exportAllConfig())} + onSubmit={() => actionType === 'import' ? dispatch(importAllConfig(partialDiff)) : dispatch(exportAllConfig(partialDiff))} />
); diff --git a/admin/src/components/ConfigList/ConfigListRow/index.js b/admin/src/components/ConfigList/ConfigListRow/index.js index 598bd63..417aaca 100644 --- a/admin/src/components/ConfigList/ConfigListRow/index.js +++ b/admin/src/components/ConfigList/ConfigListRow/index.js @@ -1,7 +1,8 @@ import React from 'react'; import { Tr, Td } from '@strapi/design-system/Table'; +import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox'; -const CustomRow = ({ row }) => { +const CustomRow = ({ row, checked, updateValue }) => { const { configName, configType, state, onClick } = row; const stateStyle = (stateStr) => { @@ -34,9 +35,20 @@ const CustomRow = ({ row }) => { return ( onClick(configType, configName)} + onClick={(e) => { + if (e.target.type !== 'checkbox') { + onClick(configType, configName); + } + }} style={{ cursor: 'pointer' }} > + + +

{configName}

diff --git a/admin/src/components/ConfigList/index.js b/admin/src/components/ConfigList/index.js index 40a8bc6..29d9b6a 100644 --- a/admin/src/components/ConfigList/index.js +++ b/admin/src/components/ConfigList/index.js @@ -1,13 +1,16 @@ import React, { useState, useEffect } from 'react'; import { isEmpty } from 'lodash'; +import { useDispatch } from 'react-redux'; import { Table, Thead, Tbody, Tr, Th } from '@strapi/design-system/Table'; import { TableLabel } from '@strapi/design-system/Text'; +import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox'; import ConfigDiff from '../ConfigDiff'; import FirstExport from '../FirstExport'; import NoChanges from '../NoChanges'; import ConfigListRow from './ConfigListRow'; +import { setConfigPartialDiffInState } from '../../state/actions/Config'; const ConfigList = ({ diff, isLoading }) => { const [openModal, setOpenModal] = useState(false); @@ -15,6 +18,8 @@ const ConfigList = ({ diff, isLoading }) => { const [newConfig, setNewConfig] = useState({}); const [cName, setCname] = useState(''); const [rows, setRows] = useState([]); + const [checkedItems, setCheckedItems] = useState([]); + const dispatch = useDispatch(); const getConfigState = (configName) => { if ( @@ -46,6 +51,8 @@ const ConfigList = ({ diff, isLoading }) => { const type = name.split('.')[0]; // Grab the first part of the filename. const formattedName = name.split(/\.(.+)/)[1]; // Grab the rest of the filename minus the file extension. + setCheckedItems(checkedItems.concat(true)); + formattedRows.push({ configName: formattedName, configType: type, @@ -62,6 +69,14 @@ const ConfigList = ({ diff, isLoading }) => { setRows(formattedRows); }, [diff]); + useEffect(() => { + const newPartialDiff = []; + checkedItems.map((item, index) => { + if (item && rows[index]) newPartialDiff.push(`${rows[index].configType}.${rows[index].configName}`); + }); + dispatch(setConfigPartialDiffInState(newPartialDiff)); + }, [checkedItems]); + const closeModal = () => { setOriginalConfig({}); setNewConfig({}); @@ -77,6 +92,9 @@ const ConfigList = ({ diff, isLoading }) => { return ; } + const allChecked = checkedItems && checkedItems.every(Boolean); + const isIndeterminate = checkedItems.some(Boolean) && !allChecked; + return (
{ + @@ -101,8 +127,16 @@ const ConfigList = ({ diff, isLoading }) => { - {rows.map((row) => ( - + {rows.map((row, index) => ( + { + checkedItems[index] = !checkedItems[index]; + setCheckedItems([...checkedItems]); + }} + /> ))}
+ setCheckedItems(checkedItems.map(() => value))} + value={allChecked} + /> + Config name
diff --git a/admin/src/containers/ConfigPage/index.js b/admin/src/containers/ConfigPage/index.js index ea7067a..4092b98 100644 --- a/admin/src/containers/ConfigPage/index.js +++ b/admin/src/containers/ConfigPage/index.js @@ -18,7 +18,7 @@ const ConfigPage = () => { return ( - + ); diff --git a/admin/src/state/actions/Config.js b/admin/src/state/actions/Config.js index 23a29f4..e080fed 100644 --- a/admin/src/state/actions/Config.js +++ b/admin/src/state/actions/Config.js @@ -29,13 +29,22 @@ export function setConfigDiffInState(config) { }; } -export function exportAllConfig() { +export const SET_CONFIG_PARTIAL_DIFF_IN_STATE = 'SET_CONFIG_PARTIAL_DIFF_IN_STATE'; +export function setConfigPartialDiffInState(config) { + return { + type: SET_CONFIG_PARTIAL_DIFF_IN_STATE, + config, + }; +} + +export function exportAllConfig(partialDiff) { return async function(dispatch, getState, toggleNotification) { dispatch(setLoadingState(true)); try { - const { message } = await request('/config-sync/export', { method: 'GET' }); - dispatch(setConfigDiffInState(Map({}))); - + const { message } = await request('/config-sync/export', { + method: 'POST', + body: partialDiff, + }); toggleNotification({ type: 'success', message }); dispatch(setLoadingState(false)); } catch (err) { @@ -45,13 +54,14 @@ export function exportAllConfig() { }; } -export function importAllConfig() { +export function importAllConfig(partialDiff) { return async function(dispatch, getState, toggleNotification) { dispatch(setLoadingState(true)); try { - const { message } = await request('/config-sync/import', { method: 'GET' }); - dispatch(setConfigDiffInState(Map({}))); - + const { message } = await request('/config-sync/import', { + method: 'POST', + body: partialDiff, + }); toggleNotification({ type: 'success', message }); dispatch(setLoadingState(false)); } catch (err) { diff --git a/admin/src/state/reducers/Config/index.js b/admin/src/state/reducers/Config/index.js index 9989aff..6b54558 100644 --- a/admin/src/state/reducers/Config/index.js +++ b/admin/src/state/reducers/Config/index.js @@ -4,11 +4,12 @@ * */ -import { fromJS, Map } from 'immutable'; -import { SET_CONFIG_DIFF_IN_STATE, SET_LOADING_STATE } from '../../actions/Config'; +import { fromJS, Map, List } from 'immutable'; +import { SET_CONFIG_DIFF_IN_STATE, SET_CONFIG_PARTIAL_DIFF_IN_STATE, SET_LOADING_STATE } from '../../actions/Config'; const initialState = fromJS({ configDiff: Map({}), + partialDiff: List([]), isLoading: false, }); @@ -16,11 +17,14 @@ export default function configReducer(state = initialState, action) { switch (action.type) { case SET_CONFIG_DIFF_IN_STATE: return state - .update('configDiff', () => fromJS(action.config)) + .update('configDiff', () => fromJS(action.config)); + case SET_CONFIG_PARTIAL_DIFF_IN_STATE: + return state + .update('partialDiff', () => fromJS(action.config)); case SET_LOADING_STATE: return state - .update('isLoading', () => fromJS(action.value)) + .update('isLoading', () => fromJS(action.value)); default: return state; } -} \ No newline at end of file +} diff --git a/server/controllers/config.js b/server/controllers/config.js index 460b9c9..4412fe8 100644 --- a/server/controllers/config.js +++ b/server/controllers/config.js @@ -14,7 +14,17 @@ module.exports = { * @returns {void} */ exportAll: async (ctx) => { - await strapi.plugin('config-sync').service('main').exportAllConfig(); + if (!ctx.request.body) { + ctx.send({ + message: 'No config was specified for the export endpoint.', + }); + + return; + } + + await Promise.all(ctx.request.body.map(async (configName) => { + await strapi.plugin('config-sync').service('main').exportSingleConfig(configName); + })); ctx.send({ message: `Config was successfully exported to ${strapi.config.get('plugin.config-sync.destination')}.`, @@ -37,7 +47,17 @@ module.exports = { return; } - await strapi.plugin('config-sync').service('main').importAllConfig(); + if (!ctx.request.body) { + ctx.send({ + message: 'No config was specified for the export endpoint.', + }); + + return; + } + + await Promise.all(ctx.request.body.map(async (configName) => { + await strapi.plugin('config-sync').service('main').importSingleConfig(configName); + })); ctx.send({ message: 'Config was successfully imported.', diff --git a/server/routes/admin.js b/server/routes/admin.js index aae8fe3..68fa6ae 100644 --- a/server/routes/admin.js +++ b/server/routes/admin.js @@ -4,7 +4,7 @@ module.exports = { type: 'admin', routes: [ { - method: "GET", + method: "POST", path: "/export", handler: "config.exportAll", config: { @@ -12,7 +12,7 @@ module.exports = { }, }, { - method: "GET", + method: "POST", path: "/import", handler: "config.importAll", config: {