mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-11 03:52:59 +00:00
Backport PR #3834: Admin Dashboard - Collapsible Details View
I made this PR to see if this feature would be useful for other people. Right now, you can't see all of a user or server's details in the admin page so I added a collapsible view which will let you see the entire server and user objects. I'm open to ideas about how the information is displayed. Will add more tests if this feature is accepted.  Signed-off-by: Min RK <benjaminrk@gmail.com>
This commit is contained in:
@@ -37,6 +37,15 @@ object-assign
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react.production.min.js
|
||||
*
|
||||
|
@@ -43,10 +43,11 @@
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^17.0.1",
|
||||
"react-bootstrap": "^1.4.0",
|
||||
"react-bootstrap": "^2.1.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-icons": "^4.1.0",
|
||||
"react-multi-select-component": "^3.0.7",
|
||||
"react-object-table-viewer": "^1.0.7",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
@@ -60,7 +61,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.5",
|
||||
"sinon": "^13.0.1",
|
||||
"babel-jest": "^26.6.3",
|
||||
"enzyme": "^3.11.0",
|
||||
"eslint": "^7.18.0",
|
||||
@@ -68,6 +68,7 @@
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"prettier": "^2.2.1"
|
||||
"prettier": "^2.2.1",
|
||||
"sinon": "^13.0.1"
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ export const reducers = (state = initialState, action) => {
|
||||
return Object.assign({}, state, {
|
||||
user_page: action.value.page,
|
||||
user_data: action.value.data,
|
||||
name_filter: action.value.name_filter,
|
||||
name_filter: action.value.name_filter || "",
|
||||
});
|
||||
|
||||
// Updates the client group model data and stores the page
|
||||
|
@@ -3,7 +3,17 @@ import regeneratorRuntime from "regenerator-runtime";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { Button, Col, Row, FormControl } from "react-bootstrap";
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Row,
|
||||
FormControl,
|
||||
Card,
|
||||
CardGroup,
|
||||
Collapse,
|
||||
} from "react-bootstrap";
|
||||
import ReactObjectTableViewer from "react-object-table-viewer";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
||||
|
||||
@@ -40,6 +50,7 @@ const ServerDashboard = (props) => {
|
||||
var [errorAlert, setErrorAlert] = useState(null);
|
||||
var [sortMethod, setSortMethod] = useState(null);
|
||||
var [disabledButtons, setDisabledButtons] = useState({});
|
||||
const [collapseStates, setCollapseStates] = useState({});
|
||||
|
||||
var user_data = useSelector((state) => state.user_data),
|
||||
user_page = useSelector((state) => state.user_page),
|
||||
@@ -189,6 +200,131 @@ const ServerDashboard = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const serverRow = (user, server) => {
|
||||
const { servers, ...userNoServers } = user;
|
||||
const serverNameDash = server.name ? `-${server.name}` : "";
|
||||
const userServerName = user.name + serverNameDash;
|
||||
const open = collapseStates[userServerName] || false;
|
||||
return [
|
||||
<tr key={`${userServerName}-row`} className="user-row">
|
||||
<td data-testid="user-row-name">
|
||||
<span>
|
||||
<Button
|
||||
onClick={() =>
|
||||
setCollapseStates({
|
||||
...collapseStates,
|
||||
[userServerName]: !open,
|
||||
})
|
||||
}
|
||||
aria-controls={`${userServerName}-collapse`}
|
||||
aria-expanded={open}
|
||||
data-testid={`${userServerName}-collapse-button`}
|
||||
variant={open ? "secondary" : "primary"}
|
||||
size="sm"
|
||||
>
|
||||
<span className="caret"></span>
|
||||
</Button>{" "}
|
||||
</span>
|
||||
<span data-testid={`user-name-div-${userServerName}`}>
|
||||
{user.name}
|
||||
</span>
|
||||
</td>
|
||||
<td data-testid="user-row-admin">{user.admin ? "admin" : ""}</td>
|
||||
|
||||
<td data-testid="user-row-server">
|
||||
{server.name ? (
|
||||
<p className="text-secondary">{server.name}</p>
|
||||
) : (
|
||||
<p style={{ color: "lightgrey" }}>[MAIN]</p>
|
||||
)}
|
||||
</td>
|
||||
<td data-testid="user-row-last-activity">
|
||||
{server.last_activity ? timeSince(server.last_activity) : "Never"}
|
||||
</td>
|
||||
<td data-testid="user-row-server-activity">
|
||||
{server.started ? (
|
||||
// Stop Single-user server
|
||||
<>
|
||||
<StopServerButton serverName={server.name} userName={user.name} />
|
||||
<AccessServerButton url={server.url} />
|
||||
</>
|
||||
) : (
|
||||
// Start Single-user server
|
||||
<>
|
||||
<StartServerButton
|
||||
serverName={server.name}
|
||||
userName={user.name}
|
||||
style={{ marginRight: 20 }}
|
||||
/>
|
||||
<a
|
||||
href={`${base_url}spawn/${user.name}${
|
||||
server.name && "/" + server.name
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="btn btn-secondary btn-xs"
|
||||
style={{ marginRight: 20 }}
|
||||
>
|
||||
Spawn Page
|
||||
</button>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<EditUserCell user={user} />
|
||||
</tr>,
|
||||
<tr>
|
||||
<td
|
||||
colSpan={6}
|
||||
style={{ padding: 0 }}
|
||||
data-testid={`${userServerName}-td`}
|
||||
>
|
||||
<Collapse in={open} data-testid={`${userServerName}-collapse`}>
|
||||
<CardGroup
|
||||
id={`${userServerName}-card-group`}
|
||||
style={{ width: "100%", margin: "0 auto", float: "none" }}
|
||||
>
|
||||
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
|
||||
<Card.Title>User</Card.Title>
|
||||
<ReactObjectTableViewer
|
||||
className="table-striped table-bordered admin-table-head"
|
||||
style={{
|
||||
padding: "3px 6px",
|
||||
margin: "auto",
|
||||
}}
|
||||
keyStyle={{
|
||||
padding: "4px",
|
||||
}}
|
||||
valueStyle={{
|
||||
padding: "4px",
|
||||
}}
|
||||
data={userNoServers}
|
||||
/>
|
||||
</Card>
|
||||
<Card style={{ width: "100%", padding: 3, margin: "0 auto" }}>
|
||||
<Card.Title>Server</Card.Title>
|
||||
<ReactObjectTableViewer
|
||||
className="table-striped table-bordered admin-table-head"
|
||||
style={{
|
||||
padding: "3px 6px",
|
||||
margin: "auto",
|
||||
}}
|
||||
keyStyle={{
|
||||
padding: "4px",
|
||||
}}
|
||||
valueStyle={{
|
||||
padding: "4px",
|
||||
}}
|
||||
data={server}
|
||||
/>
|
||||
</Card>
|
||||
</CardGroup>
|
||||
</Collapse>
|
||||
</td>
|
||||
</tr>,
|
||||
];
|
||||
};
|
||||
|
||||
let servers = user_data.flatMap((user) => {
|
||||
let userServers = Object.values({
|
||||
"": user.server || {},
|
||||
@@ -234,7 +370,7 @@ const ServerDashboard = (props) => {
|
||||
<Link to="/groups">{"> Manage Groups"}</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
<table className="table table-striped table-bordered table-hover">
|
||||
<table className="table table-bordered table-hover">
|
||||
<thead className="admin-table-head">
|
||||
<tr>
|
||||
<th id="user-header">
|
||||
@@ -373,63 +509,7 @@ const ServerDashboard = (props) => {
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{servers.map(([user, server], i) => {
|
||||
server.name = server.name || "";
|
||||
return (
|
||||
<tr key={i + "row"} className="user-row">
|
||||
<td data-testid="user-row-name">{user.name}</td>
|
||||
<td data-testid="user-row-admin">
|
||||
{user.admin ? "admin" : ""}
|
||||
</td>
|
||||
|
||||
<td data-testid="user-row-server">
|
||||
{server.name ? (
|
||||
<p class="text-secondary">{server.name}</p>
|
||||
) : (
|
||||
<p style={{ color: "lightgrey" }}>[MAIN]</p>
|
||||
)}
|
||||
</td>
|
||||
<td data-testid="user-row-last-activity">
|
||||
{server.last_activity
|
||||
? timeSince(server.last_activity)
|
||||
: "Never"}
|
||||
</td>
|
||||
<td data-testid="user-row-server-activity">
|
||||
{server.started ? (
|
||||
// Stop Single-user server
|
||||
<>
|
||||
<StopServerButton
|
||||
serverName={server.name}
|
||||
userName={user.name}
|
||||
/>
|
||||
<AccessServerButton url={server.url} />
|
||||
</>
|
||||
) : (
|
||||
// Start Single-user server
|
||||
<>
|
||||
<StartServerButton
|
||||
serverName={server.name}
|
||||
userName={user.name}
|
||||
/>
|
||||
<a
|
||||
href={`${base_url}spawn/${user.name}${
|
||||
server.name && "/" + server.name
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
className="btn btn-secondary btn-xs"
|
||||
style={{ marginRight: 20 }}
|
||||
>
|
||||
Spawn Page
|
||||
</button>
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<EditUserCell user={user} />
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
{servers.flatMap(([user, server]) => serverRow(user, server))}
|
||||
</tbody>
|
||||
</table>
|
||||
<PaginationFooter
|
||||
|
@@ -76,8 +76,8 @@ test("Renders users from props.user_data into table", async () => {
|
||||
render(serverDashboardJsx(callbackSpy));
|
||||
});
|
||||
|
||||
let foo = screen.getByText("foo");
|
||||
let bar = screen.getByText("bar");
|
||||
let foo = screen.getByTestId("user-name-div-foo");
|
||||
let bar = screen.getByTestId("user-name-div-bar");
|
||||
|
||||
expect(foo).toBeVisible();
|
||||
expect(bar).toBeVisible();
|
||||
@@ -156,12 +156,12 @@ test("Sorts according to username", async () => {
|
||||
fireEvent.click(handler);
|
||||
|
||||
let first = screen.getAllByTestId("user-row-name")[0];
|
||||
expect(first.textContent).toBe("bar");
|
||||
expect(first.textContent).toContain("bar");
|
||||
|
||||
fireEvent.click(handler);
|
||||
|
||||
first = screen.getAllByTestId("user-row-name")[0];
|
||||
expect(first.textContent).toBe("foo");
|
||||
expect(first.textContent).toContain("foo");
|
||||
});
|
||||
|
||||
test("Sorts according to admin", async () => {
|
||||
@@ -194,12 +194,12 @@ test("Sorts according to last activity", async () => {
|
||||
fireEvent.click(handler);
|
||||
|
||||
let first = screen.getAllByTestId("user-row-name")[0];
|
||||
expect(first.textContent).toBe("foo");
|
||||
expect(first.textContent).toContain("foo");
|
||||
|
||||
fireEvent.click(handler);
|
||||
|
||||
first = screen.getAllByTestId("user-row-name")[0];
|
||||
expect(first.textContent).toBe("bar");
|
||||
expect(first.textContent).toContain("bar");
|
||||
});
|
||||
|
||||
test("Sorts according to server status (running/not running)", async () => {
|
||||
@@ -213,12 +213,53 @@ test("Sorts according to server status (running/not running)", async () => {
|
||||
fireEvent.click(handler);
|
||||
|
||||
let first = screen.getAllByTestId("user-row-name")[0];
|
||||
expect(first.textContent).toBe("foo");
|
||||
expect(first.textContent).toContain("foo");
|
||||
|
||||
fireEvent.click(handler);
|
||||
|
||||
first = screen.getAllByTestId("user-row-name")[0];
|
||||
expect(first.textContent).toBe("bar");
|
||||
expect(first.textContent).toContain("bar");
|
||||
});
|
||||
|
||||
test("Shows server details with button click", async () => {
|
||||
let callbackSpy = mockAsync();
|
||||
|
||||
await act(async () => {
|
||||
render(serverDashboardJsx(callbackSpy));
|
||||
});
|
||||
let button = screen.getByTestId("foo-collapse-button");
|
||||
let collapse = screen.getByTestId("foo-collapse");
|
||||
let collapseBar = screen.getByTestId("bar-collapse");
|
||||
|
||||
// expect().toBeVisible does not work here with collapse.
|
||||
expect(collapse).toHaveClass("collapse");
|
||||
expect(collapse).not.toHaveClass("show");
|
||||
expect(collapseBar).not.toHaveClass("show");
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
clock.tick(400);
|
||||
|
||||
expect(collapse).toHaveClass("collapse show");
|
||||
expect(collapseBar).not.toHaveClass("show");
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
clock.tick(400);
|
||||
|
||||
expect(collapse).toHaveClass("collapse");
|
||||
expect(collapse).not.toHaveClass("show");
|
||||
expect(collapseBar).not.toHaveClass("show");
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
clock.tick(400);
|
||||
|
||||
expect(collapse).toHaveClass("collapse show");
|
||||
expect(collapseBar).not.toHaveClass("show");
|
||||
});
|
||||
|
||||
test("Renders nothing if required data is not available", async () => {
|
||||
|
@@ -4,7 +4,7 @@ import { jhapiRequest } from "./jhapiUtil";
|
||||
const withAPI = withProps(() => ({
|
||||
updateUsers: (offset, limit, name_filter) =>
|
||||
jhapiRequest(
|
||||
`/users?offset=${offset}&limit=${limit}&name_filter=${name_filter}`,
|
||||
`/users?offset=${offset}&limit=${limit}&name_filter=${name_filter || ""}`,
|
||||
"GET"
|
||||
).then((data) => data.json()),
|
||||
updateGroups: (offset, limit) =>
|
||||
|
@@ -928,7 +928,7 @@
|
||||
"@babel/plugin-transform-react-jsx-development" "^7.16.7"
|
||||
"@babel/plugin-transform-react-pure-annotations" "^7.16.7"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.16", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.17.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.7.tgz#a5f3328dc41ff39d803f311cfe17703418cf9825"
|
||||
integrity sha512-L6rvG9GDxaLgFjg41K+5Yv9OMrU98sWe+Ykmc6FDJW/+vYZMhdOMKkISgzptMaERHvS2Y2lw9MDRm2gHhlQQoA==
|
||||
@@ -1215,23 +1215,41 @@
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@popperjs/core@^2.8.6":
|
||||
"@popperjs/core@^2.10.1":
|
||||
version "2.11.4"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503"
|
||||
integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==
|
||||
|
||||
"@restart/context@^2.1.4":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@restart/context/-/context-2.1.4.tgz#a99d87c299a34c28bd85bb489cb07bfd23149c02"
|
||||
integrity sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==
|
||||
"@react-aria/ssr@^3.0.1":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.1.2.tgz#665a6fd56385068c7417922af2d0d71b0618e52d"
|
||||
integrity sha512-amXY11ImpokvkTMeKRHjsSsG7v1yzzs6yeqArCyBIk60J3Yhgxwx9Cah+Uu/804ATFwqzN22AXIo7SdtIaMP+g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.2"
|
||||
|
||||
"@restart/hooks@^0.3.26":
|
||||
version "0.3.27"
|
||||
resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.3.27.tgz#91f356d66d4699a8cd8b3d008402708b6a9dc505"
|
||||
integrity sha512-s984xV/EapUIfkjlf8wz9weP2O9TNKR96C68FfMEy2bE69+H4cNv3RD4Mf97lW7Htt7PjZrYTjSC8f3SB9VCXw==
|
||||
"@restart/hooks@^0.4.0", "@restart/hooks@^0.4.5":
|
||||
version "0.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.5.tgz#e7acbea237bfc9e479970500cf87538b41a1ed02"
|
||||
integrity sha512-tLGtY0aHeIfT7aPwUkvQuhIy3+q3w4iqmUzFLPlOAf/vNUacLaBt1j/S//jv/dQhenRh8jvswyMojCwmLvJw8A==
|
||||
dependencies:
|
||||
dequal "^2.0.2"
|
||||
|
||||
"@restart/ui@^1.0.2":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.0.2.tgz#009a06ae698d624672c5e6a776efd0e8a6017842"
|
||||
integrity sha512-vKGe0UBJLnbvNAjr8ljlDvphf2HkpjBjXsblmgKPvKdZBDn/mtAz89wmznaomIaEJ9VNoSEY0vA5T5MDi2jIcQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.16"
|
||||
"@popperjs/core" "^2.10.1"
|
||||
"@react-aria/ssr" "^3.0.1"
|
||||
"@restart/hooks" "^0.4.0"
|
||||
"@types/warning" "^3.0.0"
|
||||
dequal "^2.0.2"
|
||||
dom-helpers "^5.2.0"
|
||||
prop-types "^15.7.2"
|
||||
uncontrollable "^7.2.1"
|
||||
warning "^4.0.3"
|
||||
|
||||
"@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.3":
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
|
||||
@@ -1399,7 +1417,7 @@
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
|
||||
"@types/invariant@^2.2.33":
|
||||
"@types/invariant@^2.2.35":
|
||||
version "2.2.35"
|
||||
resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be"
|
||||
integrity sha512-DxX1V9P8zdJPYQat1gHyY0xj3efl8gnMVjiM9iCY6y27lj+PoQWkgjt8jDqmovPqULkKVpKRg8J36iQiA+EtEg==
|
||||
@@ -1456,7 +1474,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17"
|
||||
integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==
|
||||
|
||||
"@types/prop-types@*", "@types/prop-types@^15.7.3":
|
||||
"@types/prop-types@*", "@types/prop-types@^15.7.4":
|
||||
version "15.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
|
||||
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
|
||||
@@ -1478,7 +1496,7 @@
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-transition-group@^4.4.1":
|
||||
"@types/react-transition-group@^4.4.4":
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
|
||||
integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==
|
||||
@@ -6167,30 +6185,29 @@ raw-body@2.4.3:
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
react-bootstrap@^1.4.0:
|
||||
version "1.6.4"
|
||||
resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-1.6.4.tgz#94d5d2422e26bba277656d3529128e14f838b7ca"
|
||||
integrity sha512-z3BhBD4bEZuLP8VrYqAD7OT7axdcSkkyvWBWnS2U/4MhyabUihrUyucPWkan7aMI1XIHbmH4LCpEtzWGfx/yfA==
|
||||
react-bootstrap@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.2.1.tgz#2a6ad0931e9367882ec3fc88a70ed0b8ace90b26"
|
||||
integrity sha512-x8lpVQflsbevphuWbTnTNCatcbKyPJNrP2WyQ1MJYmFEcVjbTbai1yZhdlXr0QUxLQLxA8g5hQWb5TwJtaZoCA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.0"
|
||||
"@restart/context" "^2.1.4"
|
||||
"@restart/hooks" "^0.3.26"
|
||||
"@types/invariant" "^2.2.33"
|
||||
"@types/prop-types" "^15.7.3"
|
||||
"@babel/runtime" "^7.17.2"
|
||||
"@restart/hooks" "^0.4.5"
|
||||
"@restart/ui" "^1.0.2"
|
||||
"@types/invariant" "^2.2.35"
|
||||
"@types/prop-types" "^15.7.4"
|
||||
"@types/react" ">=16.14.8"
|
||||
"@types/react-transition-group" "^4.4.1"
|
||||
"@types/react-transition-group" "^4.4.4"
|
||||
"@types/warning" "^3.0.0"
|
||||
classnames "^2.3.1"
|
||||
dom-helpers "^5.2.1"
|
||||
invariant "^2.2.4"
|
||||
prop-types "^15.7.2"
|
||||
prop-types "^15.8.1"
|
||||
prop-types-extra "^1.1.0"
|
||||
react-overlays "^5.1.1"
|
||||
react-transition-group "^4.4.1"
|
||||
react-transition-group "^4.4.2"
|
||||
uncontrollable "^7.2.1"
|
||||
warning "^4.0.3"
|
||||
|
||||
react-dom@^17.0.1:
|
||||
react-dom@17.0.2, react-dom@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
||||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
||||
@@ -6226,19 +6243,13 @@ react-multi-select-component@^3.0.7:
|
||||
dependencies:
|
||||
goober "^2.0.30"
|
||||
|
||||
react-overlays@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-5.1.1.tgz#2e7cf49744b56537c7828ccb94cfc63dd778ae4f"
|
||||
integrity sha512-eCN2s2/+GVZzpnId4XVWtvDPYYBD2EtOGP74hE+8yDskPzFy9+pV1H3ZZihxuRdEbQzzacySaaDkR7xE0ydl4Q==
|
||||
react-object-table-viewer@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-object-table-viewer/-/react-object-table-viewer-1.0.7.tgz#31816021fa4526641c6b66bd9433ec9b78c2e472"
|
||||
integrity sha512-OezCet8+BmEdJJHO5WGPFPRWXxw4Ls6HsV4Uh1kRPlmRXLOTNqWt/ZHmH8NhTl1BA9HkdhEegKVqc2b61wDMLg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.8"
|
||||
"@popperjs/core" "^2.8.6"
|
||||
"@restart/hooks" "^0.3.26"
|
||||
"@types/warning" "^3.0.0"
|
||||
dom-helpers "^5.2.0"
|
||||
prop-types "^15.7.2"
|
||||
uncontrollable "^7.2.1"
|
||||
warning "^4.0.3"
|
||||
react "^17.0.2"
|
||||
react-dom "17.0.2"
|
||||
|
||||
react-redux@^7.2.2:
|
||||
version "7.2.6"
|
||||
@@ -6299,7 +6310,7 @@ react-test-renderer@^17.0.0:
|
||||
react-shallow-renderer "^16.13.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-transition-group@^4.4.1:
|
||||
react-transition-group@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
|
||||
integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==
|
||||
@@ -6309,7 +6320,7 @@ react-transition-group@^4.4.1:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react@^17.0.1:
|
||||
react@^17.0.1, react@^17.0.2:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user