mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Add React-Table library replacing the default table. Integrated a search function by name. Added sorting by Name and LastSeen Co-authored-by: Misha Bragin <bangvalo@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ed82212e77
commit
f11aff792d
19
package-lock.json
generated
19
package-lock.json
generated
@@ -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",
|
||||
|
||||
16
package.json
16
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": {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<tr key={peer.IP}>
|
||||
<td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6">
|
||||
{peer.Name}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
<CopyText
|
||||
text={peer.IP.toUpperCase()}
|
||||
idPrefix={"peers-ip-" + peer.IP}
|
||||
/>
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
{peer.Connected && (
|
||||
<span className="inline-flex rounded-full bg-green-100 px-2 text-xs leading-5 text-green-800">
|
||||
online
|
||||
</span>
|
||||
)}
|
||||
{!peer.Connected && (
|
||||
<span className="inline-flex rounded-full bg-red-100 px-2 text-xs leading-5 text-red-800">
|
||||
offline
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
{peer.Connected ? "just now" : timeAgo(peer.LastSeen)}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
{formatOS(peer.OS)}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
{peer.Version}
|
||||
</td>
|
||||
<td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
<EditButton
|
||||
items={[{name: "Delete"}]}
|
||||
handler={(action) => handleRowMenuClick(action, peer)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="py-10 bg-gray-50 overflow-hidden rounded max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<header className="sm:flex sm:items-center">
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 sm:flex-auto">
|
||||
<h1 className="text-xl font-semibold text-gray-900">Peers</h1>
|
||||
<p className="mt-2 text-sm text-gray-700">
|
||||
A list of all the machines in your account including their name, IP and status.
|
||||
</p>
|
||||
</div>
|
||||
{!empty ? (
|
||||
<span className="relative z-0 inline-flex shadow-sm rounded-md">
|
||||
<button
|
||||
id="btn-show-all"
|
||||
onClick={() => showAll()}
|
||||
type="button"
|
||||
className="relative inline-flex items-center px-4 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 z-10 outline-none ring-1 ring-indigo-500 border-indigo-500"
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="btn-show-online"
|
||||
onClick={() => showConnected()}
|
||||
className="relative inline-flex items-center px-4 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-700 outline-none hover:bg-gray-50"
|
||||
>
|
||||
Online
|
||||
</button>
|
||||
</span>
|
||||
) : (<div/>)}
|
||||
|
||||
{!empty ? (
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 sm:flex-auto mt-2 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<Link to="/add-peer">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
|
||||
>
|
||||
Add peer
|
||||
</button>
|
||||
</Link>
|
||||
</div>) : (<div/>)}
|
||||
</header>
|
||||
<main>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="px-4 sm:px-0">
|
||||
{loading && <Loading/>}
|
||||
{error != null && <span>{error.toString()}</span>}
|
||||
<main>
|
||||
{loading && <Loading/>}
|
||||
{error != null && (
|
||||
<span>{error.toString()}</span>
|
||||
)}
|
||||
|
||||
{!empty ? (
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div className="px-4 py-8 sm:px-0">
|
||||
<DeleteModal
|
||||
show={showDeleteDialog}
|
||||
confirmCallback={
|
||||
handleDeleteConfirmation
|
||||
}
|
||||
text={deleteDialogText}
|
||||
title={deleteDialogTitle}
|
||||
/>
|
||||
<PaginatedPeersList
|
||||
data={peers}
|
||||
RenderComponent={PeerRow}
|
||||
dataLimit={5}
|
||||
pageLimit={5}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 py-10">
|
||||
<EmptyPeersPanel/>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
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(
|
||||
<button
|
||||
className={pageIndex === i ? clicked_btn : default_btn}
|
||||
onClick={() => gotoPage(i)}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return <div>{menuItems}</div>;
|
||||
};
|
||||
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 (
|
||||
<div className="py-10 bg-gray-50 overflow-hidden rounded max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<header className="sm:flex sm:items-center">
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 sm:flex-auto">
|
||||
<h1 className="text-xl font-semibold text-gray-900">Peers</h1>
|
||||
<p className="mt-2 text-sm text-gray-700">
|
||||
A list of all the machines in your account including their name, IP
|
||||
and status.
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="px-4 sm:px-0">
|
||||
{loading && <Loading />}
|
||||
{error != null && <span>{error.toString()}</span>}
|
||||
<main>
|
||||
{loading && <Loading />}
|
||||
{error != null && <span>{error.toString()}</span>}
|
||||
|
||||
{!empty ? (
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div className="flex w-full items-center mt-8 justify-between">
|
||||
<div className="flex">
|
||||
<input
|
||||
className="text-sm rounded p-2 border border-gray-300 focus:border-gray-400 outline-none w-[300px]"
|
||||
placeholder="Search..."
|
||||
type="search"
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<p className="ml-6 text-sm text-gray-700">Sort by: </p>
|
||||
<select
|
||||
className="bg-gray-50 text-sm text-gray-500 rounded p-2 border border-gray-300 focus:border-gray-400 outline-none"
|
||||
onChange={(e) => sortTable(e.target.value)}
|
||||
>
|
||||
<option className="text-sm text-gray-500" value={0}>Name: Asc</option>
|
||||
<option className="text-sm text-gray-500" value={1}>Name: Desc</option>
|
||||
<option className="text-sm text-gray-500" value={2}>Last Seen: Asc</option>
|
||||
<option className="text-sm text-gray-500" value={3}>Last Seen: Desc</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="relative z-0 inline-flex shadow-sm rounded-md">
|
||||
<button
|
||||
id="btn-show-all"
|
||||
onClick={() => showAll()}
|
||||
type="button"
|
||||
className="relative inline-flex items-center px-4 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 z-10 outline-none ring-1 ring-indigo-500 border-indigo-500"
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="btn-show-online"
|
||||
onClick={() => showConnected()}
|
||||
className="relative inline-flex items-center px-4 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-700 outline-none hover:bg-gray-50"
|
||||
>
|
||||
Online
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 sm:flex-auto mt-2 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<Link to="/add-peer">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
|
||||
>
|
||||
Add peer
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 py-8 sm:px-0">
|
||||
<DeleteModal
|
||||
show={showDeleteDialog}
|
||||
confirmCallback={handleDeleteConfirmation}
|
||||
text={deleteDialogText}
|
||||
title={deleteDialogTitle}
|
||||
/>
|
||||
{/* table */}
|
||||
<div className="flex flex-col">
|
||||
<div className="-my-2 sm:-mx-6 lg:-mx-8">
|
||||
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
|
||||
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg">
|
||||
<table
|
||||
{...getTableProps()}
|
||||
className="min-w-full divide-y divide-gray-200"
|
||||
>
|
||||
<thead className="bg-gray-50">
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map((column) => (
|
||||
<th
|
||||
{...column.getHeaderProps()}
|
||||
className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
|
||||
>
|
||||
{column.render("Header")}
|
||||
</th>
|
||||
))}
|
||||
<th
|
||||
scope="col"
|
||||
className="relative px-6 py-3"
|
||||
>
|
||||
<span className="sr-only">Edit</span>
|
||||
</th>
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody
|
||||
{...getTableBodyProps()}
|
||||
className="bg-white divide-y divide-gray-200"
|
||||
>
|
||||
{page.map((row) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell) => {
|
||||
return (
|
||||
<td
|
||||
{...cell.getCellProps()}
|
||||
className={
|
||||
cell.column.id === "Name"
|
||||
? td_class_name
|
||||
: td_class_other
|
||||
}
|
||||
>
|
||||
{cell.column.id === "IP" && (
|
||||
<CopyText
|
||||
text={cell.value.toUpperCase()}
|
||||
idPrefix={
|
||||
"peers-ip-" + cell.value
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{cell.column.id === "Connected" &&
|
||||
(cell.value ? (
|
||||
<span className="inline-flex rounded-full bg-green-100 px-2 text-xs leading-5 text-green-800">
|
||||
online
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex rounded-full bg-red-100 px-2 text-xs leading-5 text-red-800">
|
||||
offline
|
||||
</span>
|
||||
))}
|
||||
{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}
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
<td className={td_class_other}>
|
||||
<EditButton
|
||||
items={[{ name: "Delete" }]}
|
||||
handler={(action) =>
|
||||
handleRowMenuClick(
|
||||
action,
|
||||
row.cells
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{/* pagenation */}
|
||||
<div className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p className=" text-gray-700">
|
||||
Showing{" "}
|
||||
<span className="font-medium">
|
||||
{pageCount === 0
|
||||
? 0
|
||||
: pageIndex * pageSize + 1}
|
||||
</span>{" "}
|
||||
to{" "}
|
||||
<span className="font-medium">
|
||||
{pageCount === 0
|
||||
? 0
|
||||
: pageIndex === pageCount - 1
|
||||
? data.length
|
||||
: pageIndex * pageSize + pageSize}
|
||||
</span>{" "}
|
||||
of{" "}
|
||||
<span className="font-medium">
|
||||
{data.length}
|
||||
</span>{" "}
|
||||
{data.length === 1 ? "peer" : "peers"}
|
||||
</p>
|
||||
</div>
|
||||
{pageCount === 1 || pageCount === 0 ? (
|
||||
<div />
|
||||
) : (
|
||||
<div>
|
||||
<nav
|
||||
className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px"
|
||||
aria-label="Pagination"
|
||||
>
|
||||
<button
|
||||
className="relative inline-flex rounded-l-md items-center px-2 py-2 border border-gray-300 bg-white text-gray-500 hover:bg-gray-50"
|
||||
onClick={() => gotoPage(0)}
|
||||
disabled={!canPreviousPage}
|
||||
>
|
||||
First
|
||||
</button>
|
||||
<button
|
||||
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-gray-500 hover:bg-gray-50"
|
||||
onClick={() => previousPage()}
|
||||
disabled={!canPreviousPage}
|
||||
>
|
||||
<span className="sr-only">
|
||||
Previous
|
||||
</span>
|
||||
<ChevronLeftIcon
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
<div>
|
||||
<InnerPageNumbers />
|
||||
</div>
|
||||
<button
|
||||
className="relative inline-flex items-center px-2 py-2 border border-gray-300 bg-white text-gray-500 hover:bg-gray-50"
|
||||
onClick={() => nextPage()}
|
||||
disabled={!canNextPage}
|
||||
>
|
||||
<span className="sr-only">Next</span>
|
||||
<ChevronRightIcon
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="relative inline-flex rounded-r-md items-center px-2 py-2 border border-gray-300 bg-white text-gray-500 hover:bg-gray-50"
|
||||
onClick={() => gotoPage(pageCount - 1)}
|
||||
disabled={!canNextPage}
|
||||
>
|
||||
Last
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 py-10">
|
||||
<EmptyPeersPanel />
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default withAuthenticationRequired(Peers, {
|
||||
onRedirecting: () => <Loading/>,
|
||||
onRedirecting: () => <Loading />,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user