From 1270e51248358a99054a483b100e4e6a67ee809f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 27 Mar 2020 15:32:33 -0700 Subject: [PATCH] Add support for deleting a subuser from a server --- .../Api/Client/Servers/SubuserController.php | 8 ++- .../Subusers/AbstractSubuserRequest.php | 2 +- .../scripts/api/server/users/deleteSubuser.ts | 9 +++ .../dashboard/AccountApiContainer.tsx | 2 +- .../components/elements/ConfirmationModal.tsx | 18 +++--- .../scripts/components/elements/Modal.tsx | 28 +++++---- .../server/users/PermissionEditor.tsx | 46 -------------- .../server/users/RemoveSubuserButton.tsx | 60 +++++++++++++++++++ .../components/server/users/UserRow.tsx | 36 +++++++++++ .../server/users/UsersContainer.tsx | 45 +++++--------- resources/scripts/state/server/subusers.ts | 11 ++-- 11 files changed, 158 insertions(+), 107 deletions(-) create mode 100644 resources/scripts/api/server/users/deleteSubuser.ts delete mode 100644 resources/scripts/components/server/users/PermissionEditor.tsx create mode 100644 resources/scripts/components/server/users/RemoveSubuserButton.tsx create mode 100644 resources/scripts/components/server/users/UserRow.tsx diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index ed4929c07..73a534341 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -83,12 +83,13 @@ class SubuserController extends ClientApiController * Update a given subuser in the system for the server. * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest $request - * + * @param \Pterodactyl\Models\Server $server * @return array + * * @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function update(UpdateSubuserRequest $request) + public function update(UpdateSubuserRequest $request, Server $server): array { $subuser = $request->subuser(); $this->repository->update($subuser->id, [ @@ -104,9 +105,10 @@ class SubuserController extends ClientApiController * Removes a subusers from a server's assignment. * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest $request + * @param \Pterodactyl\Models\Server $server * @return \Illuminate\Http\JsonResponse */ - public function delete(DeleteSubuserRequest $request) + public function delete(DeleteSubuserRequest $request, Server $server) { $this->repository->delete($request->subuser()->id); diff --git a/app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php b/app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php index 09d14545c..0782aad66 100644 --- a/app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php +++ b/app/Http/Requests/Api/Client/Servers/Subusers/AbstractSubuserRequest.php @@ -57,7 +57,7 @@ abstract class AbstractSubuserRequest extends ClientApiRequest } return $this->model ?: $this->model = $repository->getUserForServer( - $this->route()->parameter('subuser'), $this->route()->parameter('server')->id + $parameters['server']->id, $parameters['subuser'] ); } } diff --git a/resources/scripts/api/server/users/deleteSubuser.ts b/resources/scripts/api/server/users/deleteSubuser.ts new file mode 100644 index 000000000..dccd98e69 --- /dev/null +++ b/resources/scripts/api/server/users/deleteSubuser.ts @@ -0,0 +1,9 @@ +import http from '@/api/http'; + +export default (uuid: string, userId: string): Promise => { + return new Promise((resolve, reject) => { + http.delete(`/api/client/servers/${uuid}/users/${userId}`) + .then(() => resolve()) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/dashboard/AccountApiContainer.tsx b/resources/scripts/components/dashboard/AccountApiContainer.tsx index b65033285..38d7f757c 100644 --- a/resources/scripts/components/dashboard/AccountApiContainer.tsx +++ b/resources/scripts/components/dashboard/AccountApiContainer.tsx @@ -62,7 +62,7 @@ export default () => { doDeletion(deleteIdentifier); setDeleteIdentifier(''); }} - onCanceled={() => setDeleteIdentifier('')} + onDismissed={() => setDeleteIdentifier('')} > Are you sure you wish to delete this API key? All requests using it will immediately be invalidated and will fail. diff --git a/resources/scripts/components/elements/ConfirmationModal.tsx b/resources/scripts/components/elements/ConfirmationModal.tsx index 0796a5866..127a43faa 100644 --- a/resources/scripts/components/elements/ConfirmationModal.tsx +++ b/resources/scripts/components/elements/ConfirmationModal.tsx @@ -1,25 +1,25 @@ import React from 'react'; -import Modal from '@/components/elements/Modal'; +import Modal, { RequiredModalProps } from '@/components/elements/Modal'; -interface Props { +type Props = { title: string; buttonText: string; children: string; - visible: boolean; onConfirmed: () => void; - onCanceled: () => void; -} + showSpinnerOverlay?: boolean; +} & RequiredModalProps; -const ConfirmationModal = ({ title, children, visible, buttonText, onConfirmed, onCanceled }: Props) => ( +const ConfirmationModal = ({ title, appear, children, visible, buttonText, onConfirmed, showSpinnerOverlay, onDismissed }: Props) => ( onCanceled()} + showSpinnerOverlay={showSpinnerOverlay} + onDismissed={() => onDismissed()} >

{title}

{children}

- -
- - ); -}; diff --git a/resources/scripts/components/server/users/RemoveSubuserButton.tsx b/resources/scripts/components/server/users/RemoveSubuserButton.tsx new file mode 100644 index 000000000..28f58dc49 --- /dev/null +++ b/resources/scripts/components/server/users/RemoveSubuserButton.tsx @@ -0,0 +1,60 @@ +import React, { useState } from 'react'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import { ServerContext } from '@/state/server'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; +import { Subuser } from '@/state/server/subusers'; +import deleteSubuser from '@/api/server/users/deleteSubuser'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import { httpErrorToHuman } from '@/api/http'; + +export default ({ subuser }: { subuser: Subuser }) => { + const [ loading, setLoading ] = useState(false); + const [ showConfirmation, setShowConfirmation ] = useState(false); + + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const removeSubuser = ServerContext.useStoreActions(actions => actions.subusers.removeSubuser); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + + const doDeletion = () => { + setLoading(true); + clearFlashes('users'); + deleteSubuser(uuid, subuser.uuid) + .then(() => { + setLoading(false); + removeSubuser(subuser.uuid); + }) + .catch(error => { + console.error(error); + addError({ key: 'users', message: httpErrorToHuman(error) }); + setShowConfirmation(false); + }); + } + + return ( + <> + {showConfirmation && + doDeletion()} + onDismissed={() => setShowConfirmation(false)} + > + Are you sure you wish to remove this subuser? They will have all access to this server revoked + immediately. + + } + + + ); +}; diff --git a/resources/scripts/components/server/users/UserRow.tsx b/resources/scripts/components/server/users/UserRow.tsx new file mode 100644 index 000000000..635dfc42e --- /dev/null +++ b/resources/scripts/components/server/users/UserRow.tsx @@ -0,0 +1,36 @@ +import React, { useState } from 'react'; +import { Subuser } from '@/state/server/subusers'; +import { ServerContext } from '@/state/server'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faPencilAlt } from '@fortawesome/free-solid-svg-icons/faPencilAlt'; +import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import RemoveSubuserButton from '@/components/server/users/RemoveSubuserButton'; + +interface Props { + subuser: Subuser; +} + +export default ({ subuser }: Props) => { + const appendSubuser = ServerContext.useStoreActions(actions => actions.subusers.appendSubuser); + + return ( +
+
+ +
+
+

{subuser.email}

+
+ + +
+ ); +}; diff --git a/resources/scripts/components/server/users/UsersContainer.tsx b/resources/scripts/components/server/users/UsersContainer.tsx index ff685ec0b..082859c3d 100644 --- a/resources/scripts/components/server/users/UsersContainer.tsx +++ b/resources/scripts/components/server/users/UsersContainer.tsx @@ -4,32 +4,38 @@ import { Actions, useStoreActions, useStoreState } from 'easy-peasy'; import { ApplicationStore } from '@/state'; import Spinner from '@/components/elements/Spinner'; import AddSubuserButton from '@/components/server/users/AddSubuserButton'; +import UserRow from '@/components/server/users/UserRow'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import getServerSubusers from '@/api/server/users/getServerSubusers'; +import { httpErrorToHuman } from '@/api/http'; export default () => { const [ loading, setLoading ] = useState(true); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const subusers = ServerContext.useStoreState(state => state.subusers.data); - const getSubusers = ServerContext.useStoreActions(actions => actions.subusers.getSubusers); + const setSubusers = ServerContext.useStoreActions(actions => actions.subusers.setSubusers); const permissions = useStoreState((state: ApplicationStore) => state.permissions.data); const getPermissions = useStoreActions((actions: Actions) => actions.permissions.getPermissions); + const { addError, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); useEffect(() => { getPermissions().catch(error => console.error(error)); }, []); useEffect(() => { - getSubusers(uuid) - .then(() => setLoading(false)) + clearFlashes('users'); + getServerSubusers(uuid) + .then(subusers => { + setSubusers(subusers); + setLoading(false); + }) .catch(error => { console.error(error); + addError({ key: 'users', message: httpErrorToHuman(error) }); }); - }, [ uuid, getSubusers ]); - - useEffect(() => { - setLoading(!subusers); - }, [ subusers ]); + }, []); if (loading || !Object.keys(permissions).length) { return ; @@ -37,33 +43,14 @@ export default () => { return (
+ {!subusers.length ?

It looks like you don't have any subusers.

: subusers.map(subuser => ( -
- -
-

{subuser.email}

-
-
- - -
-
+ )) }
diff --git a/resources/scripts/state/server/subusers.ts b/resources/scripts/state/server/subusers.ts index 5cba6f3a5..46e69f470 100644 --- a/resources/scripts/state/server/subusers.ts +++ b/resources/scripts/state/server/subusers.ts @@ -1,5 +1,4 @@ -import { action, Action, thunk, Thunk } from 'easy-peasy'; -import getServerSubusers from '@/api/server/users/getServerSubusers'; +import { action, Action } from 'easy-peasy'; export type SubuserPermission = 'websocket.*' | @@ -28,7 +27,7 @@ export interface ServerSubuserStore { data: Subuser[]; setSubusers: Action; appendSubuser: Action; - getSubusers: Thunk>; + removeSubuser: Action; } const subusers: ServerSubuserStore = { @@ -42,10 +41,8 @@ const subusers: ServerSubuserStore = { state.data = [ ...state.data.filter(user => user.uuid !== payload.uuid), payload ]; }), - getSubusers: thunk(async (actions, payload) => { - const subusers = await getServerSubusers(payload); - - actions.setSubusers(subusers); + removeSubuser: action((state, payload) => { + state.data = [ ...state.data.filter(user => user.uuid !== payload) ]; }), };