diff --git a/resources/scripts/components/server/schedules/EditScheduleModal.tsx b/resources/scripts/components/server/schedules/EditScheduleModal.tsx index 003cc1d42..648dd6f51 100644 --- a/resources/scripts/components/server/schedules/EditScheduleModal.tsx +++ b/resources/scripts/components/server/schedules/EditScheduleModal.tsx @@ -6,14 +6,13 @@ import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import Switch from '@/components/elements/Switch'; import createOrUpdateSchedule from '@/api/server/schedules/createOrUpdateSchedule'; import { ServerContext } from '@/state/server'; -import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationStore } from '@/state'; import { httpErrorToHuman } from '@/api/http'; import FlashMessageRender from '@/components/FlashMessageRender'; +import useServer from '@/plugins/useServer'; +import useFlash from '@/plugins/useFlash'; type Props = { schedule?: Schedule; - onScheduleUpdated: (schedule: Schedule) => void; } & RequiredModalProps; interface Values { @@ -73,15 +72,17 @@ const EditScheduleModal = ({ schedule, ...props }: Omit { - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); - const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); +export default ({ schedule, visible, ...props }: Props) => { + const { uuid } = useServer(); + const { addError, clearFlashes } = useFlash(); const [ modalVisible, setModalVisible ] = useState(visible); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); + useEffect(() => { setModalVisible(visible); clearFlashes('schedule:edit'); - }, [visible]); + }, [ visible ]); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('schedule:edit'); @@ -98,7 +99,7 @@ export default ({ schedule, onScheduleUpdated, visible, ...props }: Props) => { }) .then(schedule => { setSubmitting(false); - onScheduleUpdated(schedule); + appendSchedule(schedule); setModalVisible(false); }) .catch(error => { diff --git a/resources/scripts/components/server/schedules/NewTaskButton.tsx b/resources/scripts/components/server/schedules/NewTaskButton.tsx index ac8787b90..c53f1b17c 100644 --- a/resources/scripts/components/server/schedules/NewTaskButton.tsx +++ b/resources/scripts/components/server/schedules/NewTaskButton.tsx @@ -1,25 +1,21 @@ import React, { useState } from 'react'; -import { Task } from '@/api/server/schedules/getServerSchedules'; +import { Schedule } from '@/api/server/schedules/getServerSchedules'; import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal'; interface Props { - scheduleId: number; - onTaskAdded: (task: Task) => void; + schedule: Schedule; } -export default ({ scheduleId, onTaskAdded }: Props) => { - const [visible, setVisible] = useState(false); +export default ({ schedule }: Props) => { + const [ visible, setVisible ] = useState(false); return ( <> {visible && - { - task && onTaskAdded(task); - setVisible(false); - }} - /> + setVisible(false)} + /> } - setSchedule(s => ({ - ...s!, tasks: [ ...s!.tasks, task ], - }))} - /> + diff --git a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx index 93bd4d326..516ffa9ae 100644 --- a/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx +++ b/resources/scripts/components/server/schedules/ScheduleTaskRow.tsx @@ -1,39 +1,41 @@ import React, { useState } from 'react'; -import { Task } from '@/api/server/schedules/getServerSchedules'; +import { Schedule, Task } from '@/api/server/schedules/getServerSchedules'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; import { faCode } from '@fortawesome/free-solid-svg-icons/faCode'; import { faToggleOn } from '@fortawesome/free-solid-svg-icons/faToggleOn'; import ConfirmTaskDeletionModal from '@/components/server/schedules/ConfirmTaskDeletionModal'; -import { ServerContext } from '@/state/server'; -import { Actions, useStoreActions } from 'easy-peasy'; -import { ApplicationStore } from '@/state'; import deleteScheduleTask from '@/api/server/schedules/deleteScheduleTask'; import { httpErrorToHuman } from '@/api/http'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import TaskDetailsModal from '@/components/server/schedules/TaskDetailsModal'; import { faPencilAlt } from '@fortawesome/free-solid-svg-icons/faPencilAlt'; import Can from '@/components/elements/Can'; +import useServer from '@/plugins/useServer'; +import useFlash from '@/plugins/useFlash'; +import { ServerContext } from '@/state/server'; interface Props { - schedule: number; + schedule: Schedule; task: Task; - onTaskUpdated: (task: Task) => void; - onTaskRemoved: () => void; } -export default ({ schedule, task, onTaskUpdated, onTaskRemoved }: Props) => { +export default ({ schedule, task }: Props) => { + const { uuid } = useServer(); + const { clearFlashes, addError } = useFlash(); const [ visible, setVisible ] = useState(false); const [ isLoading, setIsLoading ] = useState(false); const [ isEditing, setIsEditing ] = useState(false); - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); - const { clearFlashes, addError } = useStoreActions((actions: Actions) => actions.flashes); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const onConfirmDeletion = () => { setIsLoading(true); clearFlashes('schedules'); - deleteScheduleTask(uuid, schedule, task.id) - .then(() => onTaskRemoved()) + deleteScheduleTask(uuid, schedule.id, task.id) + .then(() => appendSchedule({ + ...schedule, + tasks: schedule.tasks.filter(t => t.id !== task.id), + })) .catch(error => { console.error(error); setIsLoading(false); @@ -45,12 +47,9 @@ export default ({ schedule, task, onTaskUpdated, onTaskRemoved }: Props) => {
{isEditing && { - task && onTaskUpdated(task); - setIsEditing(false); - }} + onDismissed={() => setIsEditing(false)} />} void; + onDismissed: () => void; } interface Values { @@ -29,9 +29,11 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => { const { values: { action }, setFieldValue, setFieldTouched } = useFormikContext(); useEffect(() => { - setFieldValue('payload', ''); - setFieldTouched('payload', false); - }, [action]); + return () => { + setFieldValue('payload', ''); + setFieldTouched('payload', false); + }; + }, [ action ]); return (
@@ -80,9 +82,10 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => { ); }; -export default ({ task, scheduleId, onDismissed }: Props) => { - const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); - const { clearFlashes, addError } = useStoreActions((actions: Actions) => actions.flashes); +export default ({ task, schedule, onDismissed }: Props) => { + const { uuid } = useServer(); + const { clearFlashes, addError } = useFlash(); + const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); useEffect(() => { clearFlashes('schedule:task'); @@ -90,8 +93,16 @@ export default ({ task, scheduleId, onDismissed }: Props) => { const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('schedule:task'); - createOrUpdateScheduleTask(uuid, scheduleId, task?.id, values) - .then(task => onDismissed(task)) + createOrUpdateScheduleTask(uuid, schedule.id, task?.id, values) + .then(task => { + let tasks = schedule.tasks.map(t => t.id === task.id ? task : t); + if (!schedule.tasks.find(t => t.id === task.id)) { + tasks = [ ...tasks, task ]; + } + + appendSchedule({ ...schedule, tasks }); + onDismissed(); + }) .catch(error => { console.error(error); setSubmitting(false); diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index a6c863ea0..699834d6c 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -9,6 +9,7 @@ import FlashMessageRender from '@/components/FlashMessageRender'; import getServerSubusers from '@/api/server/users/getServerSubusers'; import { httpErrorToHuman } from '@/api/http'; import Can from '@/components/elements/Can'; +import ListRefreshIndicator from '@/components/elements/ListRefreshIndicator'; export default () => { const [ loading, setLoading ] = useState(true); @@ -21,10 +22,6 @@ export default () => { const getPermissions = useStoreActions((actions: Actions) => actions.permissions.getPermissions); const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); - useEffect(() => { - getPermissions().catch(error => console.error(error)); - }, []); - useEffect(() => { clearFlashes('users'); getServerSubusers(uuid) @@ -38,12 +35,20 @@ export default () => { }); }, []); - if (loading || !Object.keys(permissions).length) { + useEffect(() => { + getPermissions().catch(error => { + addError({ key: 'users', message: httpErrorToHuman(error) }); + console.error(error); + }); + }, []); + + if (!subusers.length && (loading || !Object.keys(permissions).length)) { return ; } return (
+ {!subusers.length ?

diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index 45acb7eae..32b3a7c54 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -6,6 +6,7 @@ import files, { ServerFileStore } from '@/state/server/files'; import subusers, { ServerSubuserStore } from '@/state/server/subusers'; import { composeWithDevTools } from 'redux-devtools-extension'; import backups, { ServerBackupStore } from '@/state/server/backups'; +import schedules, { ServerScheduleStore } from '@/state/server/schedules'; export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; @@ -74,6 +75,7 @@ export interface ServerStore { subusers: ServerSubuserStore; databases: ServerDatabaseStore; files: ServerFileStore; + schedules: ServerScheduleStore; backups: ServerBackupStore; socket: SocketStore; status: ServerStatusStore; @@ -88,6 +90,7 @@ export const ServerContext = createContextStore({ files, subusers, backups, + schedules, clearServerState: action(state => { state.server.data = undefined; state.server.permissions = []; @@ -95,7 +98,8 @@ export const ServerContext = createContextStore({ state.subusers.data = []; state.files.directory = '/'; state.files.contents = []; - state.backups.backups = []; + state.backups.data = []; + state.schedules.data = []; if (state.socket.instance) { state.socket.instance.removeAllListeners(); diff --git a/resources/scripts/state/server/schedules.ts b/resources/scripts/state/server/schedules.ts new file mode 100644 index 000000000..47504ef7a --- /dev/null +++ b/resources/scripts/state/server/schedules.ts @@ -0,0 +1,31 @@ +import { action, Action } from 'easy-peasy'; +import { Schedule } from '@/api/server/schedules/getServerSchedules'; + +export interface ServerScheduleStore { + data: Schedule[]; + setSchedules: Action; + appendSchedule: Action; + removeSchedule: Action; +} + +const schedules: ServerScheduleStore = { + data: [], + + setSchedules: action((state, payload) => { + state.data = payload; + }), + + appendSchedule: action((state, payload) => { + if (state.data.find(schedule => schedule.id === payload.id)) { + state.data = state.data.map(schedule => schedule.id === payload.id ? payload : schedule); + } else { + state.data = [ ...state.data, payload ]; + } + }), + + removeSchedule: action((state, payload) => { + state.data = [ ...state.data.filter(schedule => schedule.id !== payload) ]; + }), +}; + +export default schedules;