diff --git a/jsx/build/admin-react.js b/jsx/build/admin-react.js
index 2ffc1903..e106fc6b 100644
--- a/jsx/build/admin-react.js
+++ b/jsx/build/admin-react.js
@@ -185,7 +185,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react-redux */ \"./node_modules/react-redux/es/index.js\");\n/* harmony import */ var recompose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! recompose */ \"./node_modules/recompose/dist/Recompose.esm.js\");\n/* harmony import */ var _util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../util/jhapiUtil */ \"./src/util/jhapiUtil.js\");\n/* harmony import */ var _GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./GroupEdit.pre */ \"./src/components/GroupEdit/GroupEdit.pre.jsx\");\n\n\n\n\nvar withGroupsAPI = (0,recompose__WEBPACK_IMPORTED_MODULE_1__.withProps)(function (props) {\n return {\n addToGroup: function addToGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"POST\", {\n users: users\n });\n },\n removeFromGroup: function removeFromGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"DELETE\", {\n users: users\n });\n },\n deleteGroup: function deleteGroup(name) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + name, \"DELETE\");\n },\n refreshGroupsData: function refreshGroupsData() {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups\", \"GET\").then(function (data) {\n return data.json();\n }).then(function (data) {\n return props.dispatch({\n type: \"GROUPS_DATA\",\n value: data\n });\n });\n }\n };\n});\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((0,recompose__WEBPACK_IMPORTED_MODULE_1__.compose)((0,react_redux__WEBPACK_IMPORTED_MODULE_0__.connect)(), withGroupsAPI)(_GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__.GroupEdit));\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react-redux */ \"./node_modules/react-redux/es/index.js\");\n/* harmony import */ var recompose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! recompose */ \"./node_modules/recompose/dist/Recompose.esm.js\");\n/* harmony import */ var _util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../util/jhapiUtil */ \"./src/util/jhapiUtil.js\");\n/* harmony import */ var _GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./GroupEdit.pre */ \"./src/components/GroupEdit/GroupEdit.pre.jsx\");\n\n\n\n\nvar withGroupsAPI = (0,recompose__WEBPACK_IMPORTED_MODULE_1__.withProps)(function (props) {\n return {\n addToGroup: function addToGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"POST\", {\n users: users\n });\n },\n removeFromGroup: function removeFromGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"DELETE\", {\n users: users\n });\n },\n deleteGroup: function deleteGroup(name) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + name, \"DELETE\");\n },\n refreshGroupsData: function refreshGroupsData() {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups\", \"GET\").then(function (data) {\n return data.json();\n }).then(function (data) {\n return props.dispatch({\n type: \"GROUPS_DATA\",\n value: data\n });\n });\n }\n };\n});\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((0,recompose__WEBPACK_IMPORTED_MODULE_1__.compose)((0,react_redux__WEBPACK_IMPORTED_MODULE_0__.connect)(), withGroupsAPI)(_GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__.default));\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.js?");
/***/ }),
@@ -194,13 +194,13 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
!*** ./src/components/GroupEdit/GroupEdit.pre.jsx ***!
\****************************************************/
/*! namespace exports */
-/*! export GroupEdit [provided] [no usage info] [missing usage info prevents renaming] */
+/*! export default [provided] [no usage info] [missing usage info prevents renaming] */
/*! other exports [not provided] [no usage info] */
-/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */
+/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"GroupEdit\": () => /* binding */ GroupEdit\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router-dom/esm/react-router-dom.js\");\n/* harmony import */ var _Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Multiselect/Multiselect */ \"./src/components/Multiselect/Multiselect.jsx\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! prop-types */ \"./node_modules/prop-types/index.js\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_2__);\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\nvar GroupEdit = /*#__PURE__*/function (_Component) {\n _inherits(GroupEdit, _Component);\n\n var _super = _createSuper(GroupEdit);\n\n _createClass(GroupEdit, null, [{\n key: \"propTypes\",\n get: function get() {\n return {\n location: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n state: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n group_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().object),\n user_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().array),\n callback: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n })\n }),\n history: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n push: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n }),\n addToGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n removeFromGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n deleteGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n };\n }\n }]);\n\n function GroupEdit(props) {\n var _this;\n\n _classCallCheck(this, GroupEdit);\n\n _this = _super.call(this, props);\n _this.state = {\n selected: [],\n changed: false,\n added: undefined,\n removed: undefined\n };\n return _this;\n }\n\n _createClass(GroupEdit, [{\n key: \"render\",\n value: function render() {\n var _this2 = this;\n\n if (!this.props.location.state) {\n this.props.history.push(\"/groups\");\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null);\n }\n\n var _this$props$location$ = this.props.location.state,\n group_data = _this$props$location$.group_data,\n user_data = _this$props$location$.user_data,\n callback = _this$props$location$.callback;\n var _this$props = this.props,\n addToGroup = _this$props.addToGroup,\n removeFromGroup = _this$props.removeFromGroup,\n deleteGroup = _this$props.deleteGroup,\n refreshGroupsData = _this$props.refreshGroupsData;\n if (!(group_data && user_data)) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", null);\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"container\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"row\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"h3\", null, \"Editing Group \", group_data.name), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"alert alert-info\"\n }, \"Select group members\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(_Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__.default, {\n options: user_data.map(function (e) {\n return e.name;\n }),\n value: group_data.users,\n onChange: function onChange(selection, options) {\n _this2.setState({\n selected: selection,\n changed: true\n });\n }\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"return\",\n className: \"btn btn-light\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react_router_dom__WEBPACK_IMPORTED_MODULE_3__.Link, {\n to: \"/groups\"\n }, \"Back\")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, \" \"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"submit\",\n className: \"btn btn-primary\",\n onClick: function onClick() {\n // check for changes\n if (!_this2.state.changed) {\n _this2.props.history.push(\"/groups\");\n\n return;\n }\n\n var new_users = _this2.state.selected.filter(function (e) {\n return !group_data.users.includes(e);\n });\n\n var removed_users = group_data.users.filter(function (e) {\n return !_this2.state.selected.includes(e);\n });\n\n _this2.setState(Object.assign({}, _this2.state, {\n added: new_users,\n removed: removed_users\n }));\n\n var promiseQueue = [];\n if (new_users.length > 0) promiseQueue.push(addToGroup(new_users, group_data.name));\n if (removed_users.length > 0) promiseQueue.push(removeFromGroup(removed_users, group_data.name));\n Promise.all(promiseQueue).then(function (e) {\n return callback();\n })[\"catch\"](function (err) {\n return console.log(err);\n });\n\n _this2.props.history.push(\"/groups\");\n }\n }, \"Apply\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n className: \"btn btn-danger\",\n style: {\n \"float\": \"right\"\n },\n onClick: function onClick() {\n var groupName = group_data.name;\n deleteGroup(groupName).then(refreshGroupsData()).then(_this2.props.history.push(\"/groups\"))[\"catch\"](function (err) {\n return console.log(err);\n });\n }\n }, \"Delete Group\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null))));\n }\n }]);\n\n return GroupEdit;\n}(react__WEBPACK_IMPORTED_MODULE_0__.Component);\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.pre.jsx?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router-dom/esm/react-router-dom.js\");\n/* harmony import */ var _Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Multiselect/Multiselect */ \"./src/components/Multiselect/Multiselect.jsx\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! prop-types */ \"./node_modules/prop-types/index.js\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_2__);\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\nvar GroupEdit = function GroupEdit(props) {\n var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]),\n _useState2 = _slicedToArray(_useState, 2),\n selected = _useState2[0],\n setSelected = _useState2[1],\n _useState3 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),\n _useState4 = _slicedToArray(_useState3, 2),\n changed = _useState4[0],\n setChanged = _useState4[1],\n _useState5 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(undefined),\n _useState6 = _slicedToArray(_useState5, 2),\n added = _useState6[0],\n setAdded = _useState6[1],\n _useState7 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(undefined),\n _useState8 = _slicedToArray(_useState7, 2),\n removed = _useState8[0],\n setRemoved = _useState8[1];\n\n var addToGroup = props.addToGroup,\n removeFromGroup = props.removeFromGroup,\n deleteGroup = props.deleteGroup,\n refreshGroupsData = props.refreshGroupsData,\n history = props.history,\n location = props.location;\n\n if (!location.state) {\n history.push(\"/groups\");\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null);\n }\n\n var _location$state = location.state,\n group_data = _location$state.group_data,\n user_data = _location$state.user_data,\n callback = _location$state.callback;\n if (!(group_data && user_data)) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", null);\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"container\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"row\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"h3\", null, \"Editing Group \", group_data.name), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"alert alert-info\"\n }, \"Select group members\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(_Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__.default, {\n options: user_data.map(function (e) {\n return e.name;\n }),\n value: group_data.users,\n onChange: function onChange(selection, options) {\n setSelected(selection);\n setChanged(true);\n }\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"return\",\n className: \"btn btn-light\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react_router_dom__WEBPACK_IMPORTED_MODULE_3__.Link, {\n to: \"/groups\"\n }, \"Back\")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, \" \"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"submit\",\n className: \"btn btn-primary\",\n onClick: function onClick() {\n // check for changes\n if (!changed) {\n history.push(\"/groups\");\n return;\n }\n\n var new_users = selected.filter(function (e) {\n return !group_data.users.includes(e);\n });\n var removed_users = group_data.users.filter(function (e) {\n return !selected.includes(e);\n });\n setAdded(new_users);\n setRemoved(removed_users);\n var promiseQueue = [];\n if (new_users.length > 0) promiseQueue.push(addToGroup(new_users, group_data.name));\n if (removed_users.length > 0) promiseQueue.push(removeFromGroup(removed_users, group_data.name));\n Promise.all(promiseQueue).then(function (e) {\n return callback();\n })[\"catch\"](function (err) {\n return console.log(err);\n });\n history.push(\"/groups\");\n }\n }, \"Apply\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n className: \"btn btn-danger\",\n style: {\n \"float\": \"right\"\n },\n onClick: function onClick() {\n var groupName = group_data.name;\n deleteGroup(groupName).then(refreshGroupsData()).then(history.push(\"/groups\"))[\"catch\"](function (err) {\n return console.log(err);\n });\n }\n }, \"Delete Group\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null))));\n};\n\nGroupEdit.propTypes = {\n location: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n state: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n group_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().object),\n user_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().array),\n callback: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n })\n }),\n history: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n push: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n }),\n addToGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n removeFromGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n deleteGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n};\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (GroupEdit);\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.pre.jsx?");
/***/ }),
@@ -3416,7 +3416,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
-/******/ __webpack_require__.h = () => "1a5ba23eaece8adaaacf"
+/******/ __webpack_require__.h = () => "f8d18512a8d9a3983d76"
/******/ })();
/******/
/******/ /* webpack/runtime/global */
diff --git a/jsx/src/components/GroupEdit/GroupEdit.js b/jsx/src/components/GroupEdit/GroupEdit.js
index e2b0c2bb..ab5fa935 100644
--- a/jsx/src/components/GroupEdit/GroupEdit.js
+++ b/jsx/src/components/GroupEdit/GroupEdit.js
@@ -1,7 +1,7 @@
import { connect } from "react-redux";
import { compose, withProps } from "recompose";
import { jhapiRequest } from "../../util/jhapiUtil";
-import { GroupEdit } from "./GroupEdit.pre";
+import GroupEdit from "./GroupEdit.pre";
const withGroupsAPI = withProps((props) => ({
addToGroup: (users, groupname) =>
diff --git a/jsx/src/components/GroupEdit/GroupEdit.pre.jsx b/jsx/src/components/GroupEdit/GroupEdit.pre.jsx
index 09001a39..3cab0034 100644
--- a/jsx/src/components/GroupEdit/GroupEdit.pre.jsx
+++ b/jsx/src/components/GroupEdit/GroupEdit.pre.jsx
@@ -1,132 +1,124 @@
-import React, { Component } from "react";
+import React, { Component, useState } from "react";
import { Link } from "react-router-dom";
import Multiselect from "../Multiselect/Multiselect";
import PropTypes from "prop-types";
-export class GroupEdit extends Component {
- static get propTypes() {
- return {
- location: PropTypes.shape({
- state: PropTypes.shape({
- group_data: PropTypes.object,
- user_data: PropTypes.array,
- callback: PropTypes.func,
- }),
- }),
- history: PropTypes.shape({
- push: PropTypes.func,
- }),
- addToGroup: PropTypes.func,
- removeFromGroup: PropTypes.func,
- deleteGroup: PropTypes.func,
- };
+const GroupEdit = (props) => {
+ var [selected, setSelected] = useState([]),
+ [changed, setChanged] = useState(false),
+ [added, setAdded] = useState(undefined),
+ [removed, setRemoved] = useState(undefined);
+
+ var {
+ addToGroup,
+ removeFromGroup,
+ deleteGroup,
+ refreshGroupsData,
+ history,
+ location,
+ } = props;
+
+ if (!location.state) {
+ history.push("/groups");
+ return <>>;
}
- constructor(props) {
- super(props);
- this.state = {
- selected: [],
- changed: false,
- added: undefined,
- removed: undefined,
- };
- }
+ var { group_data, user_data, callback } = location.state;
- render() {
- if (!this.props.location.state) {
- this.props.history.push("/groups");
- return <>>;
- }
+ if (!(group_data && user_data)) return
;
- var { group_data, user_data, callback } = this.props.location.state;
+ return (
+
+
+
+
Editing Group {group_data.name}
+
+
Select group members
+
e.name)}
+ value={group_data.users}
+ onChange={(selection, options) => {
+ setSelected(selection);
+ setChanged(true);
+ }}
+ />
+
+
+
+
+ );
+};
+
+GroupEdit.propTypes = {
+ location: PropTypes.shape({
+ state: PropTypes.shape({
+ group_data: PropTypes.object,
+ user_data: PropTypes.array,
+ callback: PropTypes.func,
+ }),
+ }),
+ history: PropTypes.shape({
+ push: PropTypes.func,
+ }),
+ addToGroup: PropTypes.func,
+ removeFromGroup: PropTypes.func,
+ deleteGroup: PropTypes.func,
+};
+
+export default GroupEdit;
diff --git a/share/jupyterhub/static/js/admin-react.js b/share/jupyterhub/static/js/admin-react.js
index 2ffc1903..e106fc6b 100644
--- a/share/jupyterhub/static/js/admin-react.js
+++ b/share/jupyterhub/static/js/admin-react.js
@@ -185,7 +185,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react-redux */ \"./node_modules/react-redux/es/index.js\");\n/* harmony import */ var recompose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! recompose */ \"./node_modules/recompose/dist/Recompose.esm.js\");\n/* harmony import */ var _util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../util/jhapiUtil */ \"./src/util/jhapiUtil.js\");\n/* harmony import */ var _GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./GroupEdit.pre */ \"./src/components/GroupEdit/GroupEdit.pre.jsx\");\n\n\n\n\nvar withGroupsAPI = (0,recompose__WEBPACK_IMPORTED_MODULE_1__.withProps)(function (props) {\n return {\n addToGroup: function addToGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"POST\", {\n users: users\n });\n },\n removeFromGroup: function removeFromGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"DELETE\", {\n users: users\n });\n },\n deleteGroup: function deleteGroup(name) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + name, \"DELETE\");\n },\n refreshGroupsData: function refreshGroupsData() {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups\", \"GET\").then(function (data) {\n return data.json();\n }).then(function (data) {\n return props.dispatch({\n type: \"GROUPS_DATA\",\n value: data\n });\n });\n }\n };\n});\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((0,recompose__WEBPACK_IMPORTED_MODULE_1__.compose)((0,react_redux__WEBPACK_IMPORTED_MODULE_0__.connect)(), withGroupsAPI)(_GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__.GroupEdit));\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var react_redux__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react-redux */ \"./node_modules/react-redux/es/index.js\");\n/* harmony import */ var recompose__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! recompose */ \"./node_modules/recompose/dist/Recompose.esm.js\");\n/* harmony import */ var _util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../util/jhapiUtil */ \"./src/util/jhapiUtil.js\");\n/* harmony import */ var _GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./GroupEdit.pre */ \"./src/components/GroupEdit/GroupEdit.pre.jsx\");\n\n\n\n\nvar withGroupsAPI = (0,recompose__WEBPACK_IMPORTED_MODULE_1__.withProps)(function (props) {\n return {\n addToGroup: function addToGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"POST\", {\n users: users\n });\n },\n removeFromGroup: function removeFromGroup(users, groupname) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + groupname + \"/users\", \"DELETE\", {\n users: users\n });\n },\n deleteGroup: function deleteGroup(name) {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups/\" + name, \"DELETE\");\n },\n refreshGroupsData: function refreshGroupsData() {\n return (0,_util_jhapiUtil__WEBPACK_IMPORTED_MODULE_2__.jhapiRequest)(\"/groups\", \"GET\").then(function (data) {\n return data.json();\n }).then(function (data) {\n return props.dispatch({\n type: \"GROUPS_DATA\",\n value: data\n });\n });\n }\n };\n});\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ((0,recompose__WEBPACK_IMPORTED_MODULE_1__.compose)((0,react_redux__WEBPACK_IMPORTED_MODULE_0__.connect)(), withGroupsAPI)(_GroupEdit_pre__WEBPACK_IMPORTED_MODULE_3__.default));\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.js?");
/***/ }),
@@ -194,13 +194,13 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
!*** ./src/components/GroupEdit/GroupEdit.pre.jsx ***!
\****************************************************/
/*! namespace exports */
-/*! export GroupEdit [provided] [no usage info] [missing usage info prevents renaming] */
+/*! export default [provided] [no usage info] [missing usage info prevents renaming] */
/*! other exports [not provided] [no usage info] */
-/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_require__.r, __webpack_exports__, __webpack_require__.d, __webpack_require__.* */
+/*! runtime requirements: __webpack_require__, __webpack_require__.n, __webpack_exports__, __webpack_require__.r, __webpack_require__.d, __webpack_require__.* */
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict";
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"GroupEdit\": () => /* binding */ GroupEdit\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router-dom/esm/react-router-dom.js\");\n/* harmony import */ var _Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Multiselect/Multiselect */ \"./src/components/Multiselect/Multiselect.jsx\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! prop-types */ \"./node_modules/prop-types/index.js\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_2__);\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\nvar GroupEdit = /*#__PURE__*/function (_Component) {\n _inherits(GroupEdit, _Component);\n\n var _super = _createSuper(GroupEdit);\n\n _createClass(GroupEdit, null, [{\n key: \"propTypes\",\n get: function get() {\n return {\n location: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n state: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n group_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().object),\n user_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().array),\n callback: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n })\n }),\n history: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n push: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n }),\n addToGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n removeFromGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n deleteGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n };\n }\n }]);\n\n function GroupEdit(props) {\n var _this;\n\n _classCallCheck(this, GroupEdit);\n\n _this = _super.call(this, props);\n _this.state = {\n selected: [],\n changed: false,\n added: undefined,\n removed: undefined\n };\n return _this;\n }\n\n _createClass(GroupEdit, [{\n key: \"render\",\n value: function render() {\n var _this2 = this;\n\n if (!this.props.location.state) {\n this.props.history.push(\"/groups\");\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null);\n }\n\n var _this$props$location$ = this.props.location.state,\n group_data = _this$props$location$.group_data,\n user_data = _this$props$location$.user_data,\n callback = _this$props$location$.callback;\n var _this$props = this.props,\n addToGroup = _this$props.addToGroup,\n removeFromGroup = _this$props.removeFromGroup,\n deleteGroup = _this$props.deleteGroup,\n refreshGroupsData = _this$props.refreshGroupsData;\n if (!(group_data && user_data)) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", null);\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"container\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"row\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"h3\", null, \"Editing Group \", group_data.name), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"alert alert-info\"\n }, \"Select group members\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(_Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__.default, {\n options: user_data.map(function (e) {\n return e.name;\n }),\n value: group_data.users,\n onChange: function onChange(selection, options) {\n _this2.setState({\n selected: selection,\n changed: true\n });\n }\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"return\",\n className: \"btn btn-light\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react_router_dom__WEBPACK_IMPORTED_MODULE_3__.Link, {\n to: \"/groups\"\n }, \"Back\")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, \" \"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"submit\",\n className: \"btn btn-primary\",\n onClick: function onClick() {\n // check for changes\n if (!_this2.state.changed) {\n _this2.props.history.push(\"/groups\");\n\n return;\n }\n\n var new_users = _this2.state.selected.filter(function (e) {\n return !group_data.users.includes(e);\n });\n\n var removed_users = group_data.users.filter(function (e) {\n return !_this2.state.selected.includes(e);\n });\n\n _this2.setState(Object.assign({}, _this2.state, {\n added: new_users,\n removed: removed_users\n }));\n\n var promiseQueue = [];\n if (new_users.length > 0) promiseQueue.push(addToGroup(new_users, group_data.name));\n if (removed_users.length > 0) promiseQueue.push(removeFromGroup(removed_users, group_data.name));\n Promise.all(promiseQueue).then(function (e) {\n return callback();\n })[\"catch\"](function (err) {\n return console.log(err);\n });\n\n _this2.props.history.push(\"/groups\");\n }\n }, \"Apply\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n className: \"btn btn-danger\",\n style: {\n \"float\": \"right\"\n },\n onClick: function onClick() {\n var groupName = group_data.name;\n deleteGroup(groupName).then(refreshGroupsData()).then(_this2.props.history.push(\"/groups\"))[\"catch\"](function (err) {\n return console.log(err);\n });\n }\n }, \"Delete Group\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null))));\n }\n }]);\n\n return GroupEdit;\n}(react__WEBPACK_IMPORTED_MODULE_0__.Component);\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.pre.jsx?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => __WEBPACK_DEFAULT_EXPORT__\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router-dom/esm/react-router-dom.js\");\n/* harmony import */ var _Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../Multiselect/Multiselect */ \"./src/components/Multiselect/Multiselect.jsx\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! prop-types */ \"./node_modules/prop-types/index.js\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_2__);\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\n\nfunction _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === \"string\") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === \"Object\" && o.constructor) n = o.constructor.name; if (n === \"Map\" || n === \"Set\") return Array.from(o); if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }\n\nfunction _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }\n\nfunction _iterableToArrayLimit(arr, i) { if (typeof Symbol === \"undefined\" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\n\n\n\n\n\nvar GroupEdit = function GroupEdit(props) {\n var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]),\n _useState2 = _slicedToArray(_useState, 2),\n selected = _useState2[0],\n setSelected = _useState2[1],\n _useState3 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),\n _useState4 = _slicedToArray(_useState3, 2),\n changed = _useState4[0],\n setChanged = _useState4[1],\n _useState5 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(undefined),\n _useState6 = _slicedToArray(_useState5, 2),\n added = _useState6[0],\n setAdded = _useState6[1],\n _useState7 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(undefined),\n _useState8 = _slicedToArray(_useState7, 2),\n removed = _useState8[0],\n setRemoved = _useState8[1];\n\n var addToGroup = props.addToGroup,\n removeFromGroup = props.removeFromGroup,\n deleteGroup = props.deleteGroup,\n refreshGroupsData = props.refreshGroupsData,\n history = props.history,\n location = props.location;\n\n if (!location.state) {\n history.push(\"/groups\");\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null);\n }\n\n var _location$state = location.state,\n group_data = _location$state.group_data,\n user_data = _location$state.user_data,\n callback = _location$state.callback;\n if (!(group_data && user_data)) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", null);\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"container\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"row\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"h3\", null, \"Editing Group \", group_data.name), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", {\n className: \"alert alert-info\"\n }, \"Select group members\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(_Multiselect_Multiselect__WEBPACK_IMPORTED_MODULE_1__.default, {\n options: user_data.map(function (e) {\n return e.name;\n }),\n value: group_data.users,\n onChange: function onChange(selection, options) {\n setSelected(selection);\n setChanged(true);\n }\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"return\",\n className: \"btn btn-light\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(react_router_dom__WEBPACK_IMPORTED_MODULE_3__.Link, {\n to: \"/groups\"\n }, \"Back\")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, \" \"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n id: \"submit\",\n className: \"btn btn-primary\",\n onClick: function onClick() {\n // check for changes\n if (!changed) {\n history.push(\"/groups\");\n return;\n }\n\n var new_users = selected.filter(function (e) {\n return !group_data.users.includes(e);\n });\n var removed_users = group_data.users.filter(function (e) {\n return !selected.includes(e);\n });\n setAdded(new_users);\n setRemoved(removed_users);\n var promiseQueue = [];\n if (new_users.length > 0) promiseQueue.push(addToGroup(new_users, group_data.name));\n if (removed_users.length > 0) promiseQueue.push(removeFromGroup(removed_users, group_data.name));\n Promise.all(promiseQueue).then(function (e) {\n return callback();\n })[\"catch\"](function (err) {\n return console.log(err);\n });\n history.push(\"/groups\");\n }\n }, \"Apply\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"button\", {\n className: \"btn btn-danger\",\n style: {\n \"float\": \"right\"\n },\n onClick: function onClick() {\n var groupName = group_data.name;\n deleteGroup(groupName).then(refreshGroupsData()).then(history.push(\"/groups\"))[\"catch\"](function (err) {\n return console.log(err);\n });\n }\n }, \"Delete Group\"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null))));\n};\n\nGroupEdit.propTypes = {\n location: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n state: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n group_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().object),\n user_data: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().array),\n callback: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n })\n }),\n history: prop_types__WEBPACK_IMPORTED_MODULE_2___default().shape({\n push: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n }),\n addToGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n removeFromGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func),\n deleteGroup: (prop_types__WEBPACK_IMPORTED_MODULE_2___default().func)\n};\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (GroupEdit);\n\n//# sourceURL=webpack://jupyterhub-admin-react/./src/components/GroupEdit/GroupEdit.pre.jsx?");
/***/ }),
@@ -3416,7 +3416,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/******/
/******/ /* webpack/runtime/getFullHash */
/******/ (() => {
-/******/ __webpack_require__.h = () => "1a5ba23eaece8adaaacf"
+/******/ __webpack_require__.h = () => "f8d18512a8d9a3983d76"
/******/ })();
/******/
/******/ /* webpack/runtime/global */