diff --git a/package-lock.json b/package-lock.json index 1730e9e..9443500 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "react-dom": "^17.0.1", "react-router-dom": "^5.2.0", "react-scripts": "^5.0.0", + "react-table": "^7.7.0", "tailwindcss": "^3.0.23", "web-vitals": "^0.2.4" }, @@ -13214,6 +13215,18 @@ } } }, + "node_modules/react-table": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz", + "integrity": "sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17.0.0-0" + } + }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -25692,6 +25705,12 @@ "workbox-webpack-plugin": "^6.4.1" } }, + "react-table": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.7.0.tgz", + "integrity": "sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==", + "requires": {} + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", diff --git a/package.json b/package.json index 2670b83..85a234b 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,23 @@ "dependencies": { "@auth0/auth0-react": "^1.6.0", "@headlessui/react": "^1.5.0", + "@heroicons/react": "^1.0.4", "@testing-library/jest-dom": "^5.11.4", "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", + "autoprefixer": "^10.4.4", + "heroicons": "^1.0.6", "highlight.js": "^11.2.0", "history": "^5.0.1", + "postcss": "^8.4.12", "prop-types": "^15.7.2", "react": "^17.0.1", "react-dom": "^17.0.1", "react-router-dom": "^5.2.0", "react-scripts": "^5.0.0", - "web-vitals": "^0.2.4", - "@heroicons/react": "^1.0.4", - "autoprefixer": "^10.4.4", - "heroicons": "^1.0.6", - "postcss": "^8.4.12", - "tailwindcss": "^3.0.23" + "react-table": "^7.7.0", + "tailwindcss": "^3.0.23", + "web-vitals": "^0.2.4" }, "scripts": { "start": "react-scripts start", @@ -45,8 +46,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "devDependencies": { - } } diff --git a/src/views/Peers.js b/src/views/Peers.js index d79d107..514fd0c 100644 --- a/src/views/Peers.js +++ b/src/views/Peers.js @@ -1,248 +1,511 @@ -import React, {useEffect, useState} from "react"; -import {useAuth0, withAuthenticationRequired} from "@auth0/auth0-react"; -import Loading from "../components/Loading"; -import {deletePeer, getPeers} from "../api/ManagementAPI"; -import {timeAgo} from "../utils/common"; -import EditButton from "../components/EditButton"; +import { useAuth0, withAuthenticationRequired } from "@auth0/auth0-react"; +import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid"; +import React, { useEffect, useState } from "react"; +// import PaginatedPeersList from "../components/PaginatedPeersList" +import { Link } from "react-router-dom"; +import { usePagination, useTable } from "react-table"; +import { deletePeer, getPeers } from "../api/ManagementAPI"; import CopyText from "../components/CopyText"; import DeleteModal from "../components/DeleteDialog"; +import EditButton from "../components/EditButton"; import EmptyPeersPanel from "../components/EmptyPeers"; -import PaginatedPeersList from "../components/PaginatedPeersList" -import {Link} from "react-router-dom"; +import Loading from "../components/Loading"; +import { timeAgo } from "../utils/common"; export const Peers = () => { - const [peers, setPeers] = useState([]); - const [empty, setEmpty] = useState(true) - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [showDeleteDialog, setShowDeleteDialog] = useState(false); - const [peerToDelete, setPeerToDelete] = useState(null); - const [deleteDialogText, setDeleteDialogText] = useState(""); - const [deleteDialogTitle, setDeleteDialogTitle] = useState(""); + const [peers, setPeers] = useState([]); + const [peersBackUp, setPeersBackUp] = useState([]); + const [empty, setEmpty] = useState(true); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [peerToDelete, setPeerToDelete] = useState(null); + const [deleteDialogText, setDeleteDialogText] = useState(""); + const [deleteDialogTitle, setDeleteDialogTitle] = useState(""); - const {getAccessTokenSilently} = useAuth0(); + const { getAccessTokenSilently } = useAuth0(); - const handleError = (error) => { - console.error("Error to fetch data:", error); - setLoading(false); - setError(error); - }; + const handleError = (error) => { + console.error("Error to fetch data:", error); + setLoading(false); + setError(error); + }; + // Add React Table + const data = React.useMemo(() => peers, [peers]); - const formatOS = (os) => { - if (os.startsWith("windows 10")) { - return "Windows 10" - } + const columns = React.useMemo( + () => [ + { + Header: "Name", + accessor: "Name", + }, + { + Header: "IP", + accessor: "IP", + }, + { + Header: "Status", + accessor: "Connected", + }, + { + Header: "Last Seen", + accessor: "LastSeen", + }, + { + Header: "OS", + accessor: "OS", + }, + { + Header: "Version", + accessor: "Version", + }, + ], + [] + ); + const td_class_name = + "whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6"; + const td_class_other = "whitespace-nowrap px-3 py-4 text-sm text-gray-500"; - if (os.startsWith("Darwin")) { - return os.replace("Darwin", "MacOS") - } - - return os - } - - //called when user clicks on table row menu item - const handleRowMenuClick = (action, peer) => { - if (action === "Delete") { - setPeerToDelete(peer); - setDeleteDialogText( - "Are you sure you want to delete peer from your account?" - ); - setDeleteDialogTitle('Delete peer "' + peer.Name + '"'); - setShowDeleteDialog(true); - } - }; - - const showAll = () => { - const showAllBtn = document.getElementById("btn-show-all"); - const showOnlineBtn = document.getElementById("btn-show-online"); - - showAllBtn.classList.add('ring-1', 'ring-indigo-500', 'border-indigo-500', 'outline-none'); - showOnlineBtn.classList.remove('ring-1', 'ring-indigo-500', 'border-indigo-500', 'outline-none'); - refresh(null) - } - - - const showConnected = () => { - const showAllBtn = document.getElementById("btn-show-all"); - const showOnlineBtn = document.getElementById("btn-show-online"); - - showOnlineBtn.classList.add('ring-1', 'ring-indigo-500', 'border-indigo-500', 'outline-none'); - showAllBtn.classList.remove('ring-1', 'ring-indigo-500', 'border-indigo-500', 'outline-none'); - - refresh(function (peers) { - return peers.filter(peer => { - return peer.Connected - }) - }) - } - - const refresh = (filter) => { - getPeers(getAccessTokenSilently) - .then((responseData) => - responseData.sort((a, b) => (a.Name > b.Name ? 1 : -1)) - ) - .then(list => { - setEmpty(list.length === 0) - return list - }) - .then((sorted) => { - return filter != null ? filter(sorted) : sorted - }) - .then((filtered) => { - setPeers(filtered) - }) - .then(() => setLoading(false)) - .catch((error) => handleError(error)); - }; - - // after user confirms (or not) deletion of the peer - const handleDeleteConfirmation = (confirmed) => { - setShowDeleteDialog(false); - if (confirmed) { - deletePeer(getAccessTokenSilently, peerToDelete.IP) - .then(() => setPeerToDelete(null)) - .then(() => refresh(null)) - .catch((error) => { - setPeerToDelete(null); - console.log(error); - }); - } else { - setPeerToDelete(null); - } - }; - - useEffect(() => { - refresh(null); - }, [getAccessTokenSilently]); - - const PeerRow = (peer) => { - return ( - - - {peer.Name} - - - - - - {peer.Connected && ( - - online - - )} - {!peer.Connected && ( - - offline - - )} - - - {peer.Connected ? "just now" : timeAgo(peer.LastSeen)} - - - {formatOS(peer.OS)} - - - {peer.Version} - - - handleRowMenuClick(action, peer)} - /> - - - ); - }; - - return ( -
-
-
-

Peers

-

- A list of all the machines in your account including their name, IP and status. -

-
- {!empty ? ( - - - - - ) : (
)} - - {!empty ? ( -
- - - -
) : (
)} -
-
-
-
- {loading && } - {error != null && {error.toString()}} -
- {loading && } - {error != null && ( - {error.toString()} - )} - - {!empty ? ( -
-
- - -
-
- ) : ( -
- -
- )} -
-
-
-
-
+ const { + getTableProps, + getTableBodyProps, + headerGroups, + prepareRow, + page, + canPreviousPage, + canNextPage, + pageCount, + gotoPage, + nextPage, + previousPage, + state: { pageIndex, pageSize }, + } = useTable( + { columns, data, initialState: { pageIndex: 0, pageSize: 5 } }, + usePagination + ); + const handleSearch = (e) => { + let tempArray = peersBackUp.filter((item) => + item.Name.toUpperCase().includes(e.toUpperCase()) ); + setPeers(tempArray); + }; + + const sortTable = (e) => { + let peerCopy = [...peers]; + if (e === "0") { + peerCopy.sort((a, b) => (a.Name > b.Name ? 1 : -1)); + } else if (e === "1") { + peerCopy.sort((a, b) => (a.Name > b.Name ? -1 : 1)); + } else if (e === "2") { + peerCopy.sort((a, b) => (a.LastSeen > b.LastSeen ? 1 : -1)); + } else if (e === "3") { + peerCopy.sort((a, b) => (a.LastSeen > b.LastSeen ? -1 : 1)); + } else { + console.log(`Sorry, we are out of ${e}`, e); + } + setPeers(peerCopy); + }; + + const InnerPageNumbers = () => { + let default_btn = + "z-10 bg-white border-gray-300 text-gray-700 relative inline-flex items-center px-4 py-2 border hover:bg-gray-50"; + let clicked_btn = + "z-10 bg-gray-50 border-gray-500 text-gray-600 relative inline-flex items-center px-4 py-2 border hover:bg-gray-50"; + let menuItems = []; + for (let i = 0; i < pageCount; i++) { + menuItems.push( + + ); + } + return
{menuItems}
; + }; + const formatOS = (os) => { + if (os.startsWith("windows 10")) { + return "Windows 10"; + } + + if (os.startsWith("Darwin")) { + return os.replace("Darwin", "MacOS"); + } + + return os; + }; + + //called when user clicks on table row menu item + const handleRowMenuClick = (action, peer) => { + if (action === "Delete") { + setPeerToDelete(peer[1].value); + setDeleteDialogText( + "Are you sure you want to delete peer from your account?" + ); + setDeleteDialogTitle('Delete peer "' + peer[0].value + '"'); + setShowDeleteDialog(true); + } + }; + + const showAll = () => { + const showAllBtn = document.getElementById("btn-show-all"); + const showOnlineBtn = document.getElementById("btn-show-online"); + + showAllBtn.classList.add( + "ring-1", + "ring-indigo-500", + "border-indigo-500", + "outline-none" + ); + showOnlineBtn.classList.remove( + "ring-1", + "ring-indigo-500", + "border-indigo-500", + "outline-none" + ); + refresh(null); + }; + + const showConnected = () => { + const showAllBtn = document.getElementById("btn-show-all"); + const showOnlineBtn = document.getElementById("btn-show-online"); + + showOnlineBtn.classList.add( + "ring-1", + "ring-indigo-500", + "border-indigo-500", + "outline-none" + ); + showAllBtn.classList.remove( + "ring-1", + "ring-indigo-500", + "border-indigo-500", + "outline-none" + ); + + refresh(function (peers) { + return peers.filter((peer) => { + return peer.Connected; + }); + }); + }; + + const refresh = (filter) => { + getPeers(getAccessTokenSilently) + .then((responseData) => + responseData.sort((a, b) => (a.Name > b.Name ? 1 : -1)) + ) + .then((list) => { + setEmpty(list.length === 0); + return list; + }) + .then((sorted) => { + return filter != null ? filter(sorted) : sorted; + }) + .then((filtered) => { + setPeersBackUp(filtered); + setPeers(filtered); + }) + .then(() => setLoading(false)) + .catch((error) => handleError(error)); + }; + + // after user confirms (or not) deletion of the peer + const handleDeleteConfirmation = (confirmed) => { + setShowDeleteDialog(false); + if (confirmed) { + deletePeer(getAccessTokenSilently, peerToDelete) + .then(() => setPeerToDelete(null)) + .then(() => refresh(null)) + .catch((error) => { + setPeerToDelete(null); + console.log(error); + }); + } else { + setPeerToDelete(null); + } + }; + + useEffect(() => { + refresh(null); + }, [getAccessTokenSilently]); + useEffect(() => {}, [peers]); + + return ( +
+
+
+

Peers

+

+ A list of all the machines in your account including their name, IP + and status. +

+
+
+
+
+
+ {loading && } + {error != null && {error.toString()}} +
+ {loading && } + {error != null && {error.toString()}} + + {!empty ? ( +
+
+
+ handleSearch(e.target.value)} + /> +
+

Sort by:  

+ +
+
+
+ + + + + +
+ + + +
+
+
+
+ + {/* table */} +
+
+
+
+ + + {headerGroups.map((headerGroup) => ( + + {headerGroup.headers.map((column) => ( + + ))} + + + ))} + + + {page.map((row) => { + prepareRow(row); + return ( + + {row.cells.map((cell) => { + return ( + + ); + })} + + + ); + })} + +
+ {column.render("Header")} + + Edit +
+ {cell.column.id === "IP" && ( + + )} + {cell.column.id === "Connected" && + (cell.value ? ( + + online + + ) : ( + + offline + + ))} + {cell.column.id === "LastSeen" && + (cell.row.original.Connected + ? "just now" + : timeAgo(cell.value))} + {cell.column.id === "OS" && + formatOS(cell.value)} + {(cell.column.id === "Name" || + cell.column.id === "Version") && + cell.value} + + + handleRowMenuClick( + action, + row.cells + ) + } + /> +
+ {/* pagenation */} +
+
+
+

+ Showing{" "} + + {pageCount === 0 + ? 0 + : pageIndex * pageSize + 1} + {" "} + to{" "} + + {pageCount === 0 + ? 0 + : pageIndex === pageCount - 1 + ? data.length + : pageIndex * pageSize + pageSize} + {" "} + of{" "} + + {data.length} + {" "} + {data.length === 1 ? "peer" : "peers"} +

+
+ {pageCount === 1 || pageCount === 0 ? ( +
+ ) : ( +
+ +
+ )} +
+
+
+
+
+
+
+
+ ) : ( +
+ +
+ )} +
+
+
+
+
+ ); }; export default withAuthenticationRequired(Peers, { - onRedirecting: () => , + onRedirecting: () => , });