import React, { useEffect, useState, Fragment } from "react"; import { useSelector, useDispatch } from "react-redux"; import { debounce } from "lodash"; import PropTypes from "prop-types"; import { Button, Col, Row, FormControl, Card, CardGroup, Collapse, } from "react-bootstrap"; import ReactObjectTableViewer from "../ReactObjectTableViewer/ReactObjectTableViewer"; import { Link } from "react-router-dom"; import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa"; import "./server-dashboard.css"; import { timeSince } from "../../util/timeSince"; import PaginationFooter from "../PaginationFooter/PaginationFooter"; const AccessServerButton = ({ url }) => ( ); const RowListItem = ({ text }) => ( {text} ); RowListItem.propTypes = { text: PropTypes.string, }; const ServerDashboard = (props) => { let base_url = window.base_url || "/"; // sort methods var usernameDesc = (e) => e.sort((a, b) => (a.name > b.name ? 1 : -1)), usernameAsc = (e) => e.sort((a, b) => (a.name < b.name ? 1 : -1)), adminDesc = (e) => e.sort((a) => (a.admin ? -1 : 1)), adminAsc = (e) => e.sort((a) => (a.admin ? 1 : -1)), dateDesc = (e) => e.sort((a, b) => new Date(a.last_activity) - new Date(b.last_activity) > 0 ? -1 : 1, ), dateAsc = (e) => e.sort((a, b) => new Date(a.last_activity) - new Date(b.last_activity) > 0 ? 1 : -1, ), runningAsc = (e) => e.sort((a) => (a.server == null ? -1 : 1)), runningDesc = (e) => e.sort((a) => (a.server == null ? 1 : -1)); var [errorAlert, setErrorAlert] = useState(null); var [sortMethod, setSortMethod] = useState(null); var [disabledButtons, setDisabledButtons] = useState({}); var [collapseStates, setCollapseStates] = useState({}); var user_data = useSelector((state) => state.user_data), user_page = useSelector((state) => state.user_page), name_filter = useSelector((state) => state.name_filter); var offset = user_page ? user_page.offset : 0; var limit = user_page ? user_page.limit : window.api_page_limit; var total = user_page ? user_page.total : undefined; const dispatch = useDispatch(); var { updateUsers, shutdownHub, startServer, stopServer, deleteServer, startAll, stopAll, history, } = props; const dispatchPageUpdate = (data, page) => { dispatch({ type: "USER_PAGE", value: { data: data, page: page, }, }); }; const setOffset = (newOffset) => { dispatch({ type: "USER_OFFSET", value: { offset: newOffset, }, }); }; const setNameFilter = (name_filter) => { dispatch({ type: "USER_NAME_FILTER", value: { name_filter: name_filter, }, }); }; useEffect(() => { updateUsers(offset, limit, name_filter) .then((data) => dispatchPageUpdate(data.items, data._pagination)) .catch((err) => setErrorAlert("Failed to update user list.")); }, [offset, limit, name_filter]); if (!user_data || !user_page) { return
; } var slice = [offset, limit, name_filter]; const handleSearch = debounce(async (event) => { setNameFilter(event.target.value); }, 300); if (sortMethod != null) { user_data = sortMethod(user_data); } const StopServerButton = ({ serverName, userName }) => { var [isDisabled, setIsDisabled] = useState(false); return ( ); }; const DeleteServerButton = ({ serverName, userName }) => { if (serverName === "") { return null; } var [isDisabled, setIsDisabled] = useState(false); return ( ); }; const StartServerButton = ({ serverName, userName }) => { var [isDisabled, setIsDisabled] = useState(false); return ( ); }; const EditUserCell = ({ user }) => { return ( ); }; const ServerRowTable = ({ data }) => { const sortedData = Object.keys(data) .sort() .reduce(function (result, key) { let value = data[key]; switch (key) { case "last_activity": case "created": case "started": // format timestamps value = value ? timeSince(value) : value; break; } if (Array.isArray(value)) { value = ( {value.sort().flatMap((v) => ( ))} ); } result[key] = value; return result; }, {}); return ( ); }; 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 [ {" "} {user.name} {user.admin ? "admin" : ""}

{server.name}

{server.last_activity ? timeSince(server.last_activity) : "Never"} {server.ready ? ( // Stop Single-user server <> ) : ( // Start Single-user server <> )} , User Server , ]; }; let servers = user_data.flatMap((user) => { let userServers = Object.values({ "": user.server || {}, ...(user.servers || {}), }); return userServers.map((server) => [user, server]); }); return (
{errorAlert != null ? (
{errorAlert}
) : ( <> )}
{"> Manage Groups"} {servers.flatMap(([user, server]) => serverRow(user, server))}
User{" "} setSortMethod(() => method)} testid="user-sort" /> Admin{" "} setSortMethod(() => method)} testid="admin-sort" /> Server{" "} setSortMethod(() => method)} testid="server-sort" /> Last Activity{" "} setSortMethod(() => method)} testid="last-activity-sort" /> Running{" "} setSortMethod(() => method)} testid="running-status-sort" /> Actions
{/* Start all servers */} {/* Stop all servers */} {/* Shutdown Jupyterhub */}
setOffset(offset + limit)} prev={() => setOffset(offset - limit)} />

); }; ServerDashboard.propTypes = { user_data: PropTypes.array, updateUsers: PropTypes.func, shutdownHub: PropTypes.func, startServer: PropTypes.func, stopServer: PropTypes.func, deleteServer: PropTypes.func, startAll: PropTypes.func, stopAll: PropTypes.func, dispatch: PropTypes.func, history: PropTypes.shape({ push: PropTypes.func, }), location: PropTypes.shape({ search: PropTypes.string, }), }; const SortHandler = (props) => { var { sorts, callback, testid } = props; var [direction, setDirection] = useState(undefined); return (
{ if (!direction) { callback(sorts.desc); setDirection("desc"); } else if (direction == "asc") { callback(sorts.desc); setDirection("desc"); } else { callback(sorts.asc); setDirection("asc"); } }} > {!direction ? ( ) : direction == "asc" ? ( ) : ( )}
); }; SortHandler.propTypes = { sorts: PropTypes.object, callback: PropTypes.func, testid: PropTypes.string, }; export default ServerDashboard;