Skip to content

Commit 94fe80f

Browse files
authored
Merge pull request #1193 from mathematicalthinking/adminActionButtons
Add Admin Dashboard actions to allow Admins to provide or remove Admin privileges
2 parents 9ec9262 + e940c49 commit 94fe80f

File tree

7 files changed

+113
-77
lines changed

7 files changed

+113
-77
lines changed

client/src/Components/UI/ContentBox/DashboardContentBox.js

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -37,47 +37,9 @@ class DashboardContentBox extends PureComponent {
3737
resource,
3838
manageUser,
3939
isSelf,
40+
iconActions,
4041
} = this.props;
4142
const { expanded } = this.state;
42-
let iconActions = null;
43-
let suspendReinstateAction = {
44-
iconClass: 'fas fa-ban',
45-
title: 'Suspend User',
46-
testid: 'suspend',
47-
color: 'red',
48-
onClick: () => {
49-
manageUser(details, 'suspendUser');
50-
},
51-
};
52-
53-
if (details.isSuspended) {
54-
suspendReinstateAction = {
55-
iconClass: 'fas fa-undo',
56-
title: 'Reinstate User',
57-
testid: 'reinstate',
58-
color: 'green',
59-
onClick: () => {
60-
manageUser(details, 'reinstateUser');
61-
},
62-
};
63-
}
64-
65-
const forceLogoutAction = {
66-
iconClass: 'fas fa-power-off',
67-
title: 'Force Logout',
68-
testid: 'force-logout',
69-
onClick: () => {
70-
manageUser(details, 'logoutUser');
71-
},
72-
};
73-
74-
if (resource === 'users') {
75-
iconActions = !isSelf ? [suspendReinstateAction] : [];
76-
77-
if (details.socketId && !details.doForceLogout) {
78-
iconActions.unshift(forceLogoutAction);
79-
}
80-
}
8143

8244
const { firstName = '', lastName = '' } = details;
8345
let fullName;
@@ -260,12 +222,20 @@ DashboardContentBox.propTypes = {
260222
resource: PropTypes.string.isRequired,
261223
manageUser: PropTypes.func,
262224
isSelf: PropTypes.bool.isRequired,
225+
iconActions: PropTypes.arrayOf(
226+
PropTypes.shape({
227+
testId: PropTypes.string,
228+
onClick: PropTypes.func,
229+
title: PropTypes.string,
230+
})
231+
),
263232
};
264233

265234
DashboardContentBox.defaultProps = {
266235
image: null,
267236
roomType: null,
268237
link: null,
269238
manageUser: null,
239+
iconActions: [],
270240
};
271241
export default DashboardContentBox;

client/src/Containers/Dashboard.js

Lines changed: 85 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,15 @@ class Dashboard extends Component {
204204
this.setQueryParams(filters);
205205
};
206206

207-
logoutUser = (userId) => {
208-
return API.revokeRefreshToken(userId)
207+
_buttonAction = (apiAction, userId, update) => {
208+
return apiAction()
209209
.then((res) => {
210-
const { user } = res.data;
210+
const { user, username } = res.data;
211211

212-
if (user) {
213-
this.updateVisibleResource(userId, { socketId: null });
212+
// API actions return different values if successful,
213+
// so allow for a couple of possibilities.
214+
if (user || username) {
215+
this.updateVisibleResource(userId, update);
214216
}
215217
this.stopManageUser();
216218
})
@@ -221,39 +223,28 @@ class Dashboard extends Component {
221223
});
222224
};
223225

224-
suspendUser = (userId) => {
225-
return API.suspendUser(userId)
226-
.then((res) => {
227-
const { user } = res.data;
226+
logoutUser = (userId) =>
227+
this._buttonAction(() => API.revokeRefreshToken(userId), userId, {
228+
socketId: null,
229+
});
228230

229-
if (user) {
230-
this.updateVisibleResource(userId, { isSuspended: true });
231-
}
232-
this.stopManageUser();
233-
})
234-
.catch((err) => {
235-
this.stopManageUser();
236-
// eslint-disable-next-line no-console
237-
console.log({ err });
238-
});
239-
};
231+
suspendUser = (userId) =>
232+
this._buttonAction(() => API.suspendUser(userId), userId, {
233+
isSuspended: true,
234+
});
240235

241-
reinstateUser = (userId) => {
242-
return API.reinstateUser(userId)
243-
.then((res) => {
244-
const { user } = res.data;
236+
reinstateUser = (userId) =>
237+
this._buttonAction(() => API.reinstateUser(userId), userId, {
238+
isSuspended: false,
239+
});
245240

246-
if (user) {
247-
this.updateVisibleResource(userId, { isSuspended: false });
248-
}
249-
this.stopManageUser();
250-
})
251-
.catch((err) => {
252-
this.stopManageUser();
253-
// eslint-disable-next-line no-console
254-
console.log({ err });
255-
});
256-
};
241+
removeAsAdmin = (userId) =>
242+
this._buttonAction(() => API.removeAsAdmin(userId), userId, {
243+
isAdmin: false,
244+
});
245+
246+
makeAdmin = (userId) =>
247+
this._buttonAction(() => API.makeAdmin(userId), userId, { isAdmin: true });
257248

258249
updateVisibleResource = (itemId, update) => {
259250
const { visibleResources } = this.state;
@@ -282,6 +273,62 @@ class Dashboard extends Component {
282273
});
283274
};
284275

276+
_getIconActions = (details, resource, isSelf) => {
277+
if (resource !== 'users') return [];
278+
const suspendReinstateAction = details.isSuspended
279+
? {
280+
iconClass: 'fas fa-undo',
281+
title: 'Reinstate User',
282+
testid: 'reinstate',
283+
color: 'green',
284+
onClick: () => {
285+
this.manageUser(details, 'reinstateUser');
286+
},
287+
}
288+
: {
289+
iconClass: 'fas fa-ban',
290+
title: 'Suspend User',
291+
testid: 'suspend',
292+
color: 'red',
293+
onClick: () => {
294+
this.manageUser(details, 'suspendUser');
295+
},
296+
};
297+
298+
const forceLogoutAction = {
299+
iconClass: 'fas fa-power-off',
300+
title: 'Force Logout',
301+
testid: 'force-logout',
302+
onClick: () => {
303+
this.manageUser(details, 'logoutUser');
304+
},
305+
};
306+
307+
const makeRemoveAdmin = details.isAdmin
308+
? {
309+
iconClass: 'fas fa-minus',
310+
title: 'Remove as Admin',
311+
testid: 'remove-as-admin',
312+
onClick: () => {
313+
this.manageUser(details, 'removeAsAdmin');
314+
},
315+
}
316+
: {
317+
iconClass: 'fas fa-plus',
318+
title: 'Make into Admin',
319+
testid: 'make-as-admin',
320+
onClick: () => {
321+
this.manageUser(details, 'makeAdmin');
322+
},
323+
};
324+
325+
const iconActions = [];
326+
if (details.socketId && !details.doForceLogout)
327+
iconActions.push(forceLogoutAction);
328+
if (!isSelf) iconActions.push(suspendReinstateAction, makeRemoveAdmin);
329+
return iconActions;
330+
};
331+
285332
render() {
286333
const { match, user } = this.props;
287334
const {
@@ -315,6 +362,8 @@ class Dashboard extends Component {
315362
logoutUser: `Are you sure you want to manually logout ${username}?`,
316363
reinstateUser: `Are you sure you want to reinstate ${username}?`,
317364
suspendUser: `Are you sure you want to suspend ${username}. They will not be able to use VMT until they are reinstated.`,
365+
removeAsAdmin: `Are you sure you want to remove ${username} as an Admin?`,
366+
makeAdmin: `Are you sure you want to give ${username} Admin privileges?`,
318367
};
319368
manageUserPrompt = actionMessageHash[manageUserAction];
320369
}
@@ -340,6 +389,7 @@ class Dashboard extends Component {
340389
manageUser={this.manageUser}
341390
ownUserId={user._id}
342391
isLoading={isLoading}
392+
getIconActions={this._getIconActions}
343393
/>
344394
<Modal show={userToManage !== null} closeModal={this.stopManageUser}>
345395
{manageUserPrompt}

client/src/Layout/AdminDashboard/AdminDashboard.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class AdminDashboard extends Component {
4646
manageUser,
4747
ownUserId,
4848
isLoading,
49+
getIconActions,
4950
} = this.props;
5051

5152
const totalCount = totalCounts ? totalCounts.totalCount || 0 : 0;
@@ -184,6 +185,7 @@ class AdminDashboard extends Component {
184185
resultsMessage={resultsMessage}
185186
manageUser={manageUser}
186187
ownUserId={ownUserId}
188+
getIconActions={getIconActions}
187189
/>
188190
)}
189191
</div>
@@ -222,6 +224,7 @@ AdminDashboard.propTypes = {
222224
setToDate: PropTypes.func.isRequired,
223225
manageUser: PropTypes.func.isRequired,
224226
ownUserId: PropTypes.string.isRequired,
227+
getIconActions: PropTypes.func.isRequired,
225228
};
226229

227230
export default AdminDashboard;

client/src/Layout/DashboardBoxList/DashboardBoxList.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const dashboardBoxList = (props) => {
1515
resultsMessage,
1616
manageUser,
1717
ownUserId,
18+
getIconActions,
1819
} = props;
1920
let listElems = 'No activity within specified time period';
2021
if (list.length > 0) {
@@ -56,6 +57,11 @@ const dashboardBoxList = (props) => {
5657
resource={resource}
5758
manageUser={manageUser}
5859
isSelf={item._id === ownUserId}
60+
iconActions={getIconActions(
61+
item,
62+
resource,
63+
item._id === ownUserId
64+
)}
5965
/>
6066
</div>
6167
);
@@ -97,6 +103,7 @@ dashboardBoxList.propTypes = {
97103
resultsMessage: PropTypes.string,
98104
manageUser: PropTypes.func.isRequired,
99105
ownUserId: PropTypes.string.isRequired,
106+
getIconActions: PropTypes.func.isRequired,
100107
};
101108

102109
dashboardBoxList.defaultProps = {

client/src/utils/apiRequests.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ export default {
210210
reinstateUser: (userId) => {
211211
return api.post(`/admin/reinstateUser/${userId}`);
212212
},
213+
removeAsAdmin: (userId) => {
214+
return api.put(`/api/user/${userId}`, { isAdmin: false });
215+
},
216+
makeAdmin: (userId) => {
217+
return api.put(`/api/user/${userId}`, { isAdmin: true });
218+
},
213219

214220
archiveRooms: (ids) => {
215221
return api.put(`/api/archiveRooms`, { ids });

server/controllers/UserController.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ module.exports = {
328328
$project: {
329329
username: 1,
330330
latestIpAddress: 1,
331+
isAdmin: 1,
331332
updatedAt: 1,
332333
isSuspended: 1,
333334
socketId: 1,

server/sockets.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,10 @@ module.exports = function() {
212212
socket.rooms
213213
);
214214

215-
let areAllInactive = false;
216215
if (reason === 'transport close') {
217216
disconnectFromRoom(socket);
218217
markSocketAsInactive(socket.user_id, socket.id);
219-
areAllInactive = await areAllSocketsInactive(socket.user_id);
218+
const areAllInactive = await areAllSocketsInactive(socket.user_id);
220219
if (areAllInactive) {
221220
forceLogout(socket.user_id);
222221
}
@@ -225,7 +224,7 @@ module.exports = function() {
225224
socket.timer = setTimeout(async () => {
226225
disconnectFromRoom(socket);
227226
markSocketAsInactive(socket.user_id, socket.id);
228-
areAllInactive = await areAllSocketsInactive(socket.user_id);
227+
const areAllInactive = await areAllSocketsInactive(socket.user_id);
229228
if (areAllInactive) forceLogout(socket.user_id);
230229
}, 15000);
231230
}

0 commit comments

Comments
 (0)