15/02/2025
This commit is contained in:
parent
a58c9b35a4
commit
3e9d001850
3
.gitignore
vendored
3
.gitignore
vendored
@ -6,8 +6,6 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
@ -24,3 +22,4 @@ dist-ssr
|
||||
*.sw?
|
||||
.env
|
||||
*.zip
|
||||
node_modules
|
||||
|
||||
BIN
public/img/Logo.jpg
Normal file
BIN
public/img/Logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
352
src/App.css
352
src/App.css
@ -65,12 +65,13 @@ body.dark {
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-top: 75px;
|
||||
margin-left: 0; /* Pastikan kontennya full width */
|
||||
}
|
||||
}
|
||||
|
||||
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
||||
@media (min-width: 768px) {
|
||||
@media (min-width: 769px) {
|
||||
.menu-toggle-btn, .sidebar-toggle-btn {
|
||||
display: none;
|
||||
}
|
||||
@ -84,8 +85,7 @@ body.dark {
|
||||
/* Styling modal */
|
||||
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
||||
@media (min-width: 768px) {
|
||||
.menu-toggle-btn,
|
||||
.sidebar-toggle-btn {
|
||||
.menu-toggle-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -145,6 +145,20 @@ body.dark {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Default styling for close button */
|
||||
.sidebar-modal .btn-close {
|
||||
transition: filter 0.3s;
|
||||
}
|
||||
|
||||
/* Close button untuk tema light */
|
||||
.light .sidebar-modal .btn-close {
|
||||
filter: invert(0); /* Warna default (hitam) */
|
||||
}
|
||||
|
||||
/* Close button untuk tema dark */
|
||||
.dark .sidebar-modal .btn-close {
|
||||
filter: invert(1); /* Mengubah warna ke putih */
|
||||
}
|
||||
|
||||
.navbar-app {
|
||||
display: flex;
|
||||
@ -156,6 +170,20 @@ body.dark {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* Default shadow */
|
||||
}
|
||||
|
||||
.navbar-brand img {
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
.navbar-brand span {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar-brand span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Light Mode Navbar */
|
||||
.light .navbar-app {
|
||||
background-color: #ffffff;
|
||||
@ -370,6 +398,14 @@ button:hover {
|
||||
/* METRICS GRID TABLE */
|
||||
/* ======================= */
|
||||
|
||||
/* Wrapper untuk memungkinkan scroll horizontal */
|
||||
.metrics-scroll-wrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Container utama untuk Metrics Table */
|
||||
.metrics-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -379,20 +415,39 @@ button:hover {
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.metrics-header, .metrics-row {
|
||||
/* Header Styling */
|
||||
.metrics-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: inherit;
|
||||
z-index: 10;
|
||||
font-weight: bold;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 1fr 1fr 1fr 1fr;
|
||||
grid-template-columns: repeat(6, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
width: 1300px;
|
||||
}
|
||||
|
||||
/* Body Styling */
|
||||
.metrics-body {
|
||||
display: block;
|
||||
min-width: 1300px;
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
/* Row Styling */
|
||||
.metrics-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Header Styling */
|
||||
.metrics-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* LIGHT MODE */
|
||||
/* LIGHT MODE Styling */
|
||||
.light .metrics-container {
|
||||
background-color: #ffffff;
|
||||
color: #1d1d1d;
|
||||
@ -413,7 +468,7 @@ button:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
/* DARK MODE */
|
||||
/* DARK MODE Styling */
|
||||
.dark .metrics-container {
|
||||
background-color: #161616;
|
||||
color: #ffffff;
|
||||
@ -447,8 +502,8 @@ button:hover {
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.metrics-header, .metrics-row {
|
||||
grid-template-columns: 1fr 1fr 1fr; /* Kurangi jumlah kolom agar lebih rapi di mobile */
|
||||
font-size: 14px;
|
||||
width: 1290px;
|
||||
grid-template-columns: repeat(6, minmax(200px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,6 +511,13 @@ button:hover {
|
||||
/* DATA TABLE GRID STYLING */
|
||||
/* ======================= */
|
||||
|
||||
/* Wrapper agar DataTable bisa scroll ke samping */
|
||||
.data-table-scroll-wrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.data-table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -465,20 +527,74 @@ button:hover {
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.data-table-header, .data-table-row {
|
||||
/* Header tetap di atas saat scroll */
|
||||
.data-table-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: inherit;
|
||||
z-index: 10;
|
||||
font-weight: bold;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 2fr 2fr 2fr 2fr 3fr 3fr;
|
||||
grid-template-columns: repeat(8, minmax(100px, 1fr));
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
width: 1300px;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.data-table-header {
|
||||
font-weight: bold;
|
||||
/* Pastikan body bisa scroll */
|
||||
.data-table-body {
|
||||
display: block;
|
||||
min-width: 1300px;
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
/* Row styling */
|
||||
.data-table-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
|
||||
@media (max-width: 1279px) {
|
||||
.data-table-header,
|
||||
.data-table-row {
|
||||
width: 1300px;
|
||||
grid-template-columns: repeat(8, minmax(120px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* Di atas 1280px: Tidak perlu scroll horizontal */
|
||||
@media (min-width: 1280px) {
|
||||
.data-table-scroll-wrapper {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.data-table-body {
|
||||
min-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.data-table-header,
|
||||
.data-table-row {
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.data-table-header, .data-table-row {
|
||||
width: 1290px;
|
||||
grid-template-columns: repeat(8, minmax(100px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* LIGHT MODE */
|
||||
.light .data-table-container {
|
||||
background-color: #ffffff;
|
||||
@ -521,18 +637,17 @@ button:hover {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.data-table-header, .data-table-row {
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr; /* Kurangi jumlah kolom di mobile */
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* DEVICE TABLE GRID STYLE */
|
||||
/* ======================= */
|
||||
|
||||
/* Wrapper untuk memungkinkan scroll horizontal */
|
||||
.device-table-scroll-wrapper {
|
||||
overflow-x: auto;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.device-table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -542,18 +657,37 @@ button:hover {
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.device-table-header, .device-table-row {
|
||||
/* Header tetap di atas saat scroll */
|
||||
.device-table-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: inherit;
|
||||
z-index: 10;
|
||||
font-weight: bold;
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 3fr 2fr;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
gap: 5px;
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
.device-table-header {
|
||||
font-weight: bold;
|
||||
/* Pastikan body bisa scroll */
|
||||
.device-table-body {
|
||||
display: block;
|
||||
min-width: 800px; /* Memastikan scroll horizontal muncul */
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
/* Row styling */
|
||||
.device-table-row {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 3fr 2fr;
|
||||
gap: 5px;
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* LIGHT MODE */
|
||||
@ -614,10 +748,150 @@ button:hover {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.device-table-header,
|
||||
.device-table-row {
|
||||
width: 800px; /* Memastikan tabel lebih lebar agar scroll muncul */
|
||||
grid-template-columns: repeat(3, minmax(150px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
/* Di atas 1024px: Tidak perlu scroll horizontal */
|
||||
@media (min-width: 1024px) {
|
||||
.device-table-scroll-wrapper {
|
||||
overflow-x: hidden; /* Hilangkan scroll horizontal */
|
||||
}
|
||||
|
||||
.device-table-body {
|
||||
min-width: 100%; /* Gunakan lebar penuh */
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.device-table-header,
|
||||
.device-table-row {
|
||||
width: 100%; /* Gunakan lebar penuh */
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.device-table-header, .device-table-row {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
font-size: 14px;
|
||||
.device-table-header,
|
||||
.device-table-row {
|
||||
width: 800px; /* Memastikan tabel lebih lebar dari layar agar scroll muncul */
|
||||
grid-template-columns: repeat(3, minmax(150px, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Untuk semua tabel di bawah 768px */
|
||||
@media (max-width: 768px) {
|
||||
.metrics-header,
|
||||
.metrics-row,
|
||||
.data-table-header,
|
||||
.data-table-row,
|
||||
.device-table-header,
|
||||
.device-table-row {
|
||||
font-size: 12px; /* Kurangi ukuran font */
|
||||
padding: 5px; /* Kurangi padding */
|
||||
}
|
||||
|
||||
/* Pastikan tabel tidak terlalu lebar */
|
||||
.metrics-header, .metrics-row,
|
||||
.data-table-header, .data-table-row,
|
||||
.device-table-header, .device-table-row {
|
||||
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
|
||||
}
|
||||
|
||||
/* Jika tabel terlalu besar, pastikan bisa di-scroll */
|
||||
.metrics-container,
|
||||
.data-table-container,
|
||||
.device-table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.metrics-body,
|
||||
.data-table-body,
|
||||
.device-table-body {
|
||||
max-height: 300px; /* Kurangi tinggi tabel */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Kurangi ukuran ikon agar tidak mendominasi */
|
||||
.header-item svg,
|
||||
.row-item svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================= */
|
||||
/* SCROLLBAR STYLING */
|
||||
/* ======================= */
|
||||
|
||||
/* Styling untuk scrollbar horizontal & vertical */
|
||||
.metrics-scroll-wrapper::-webkit-scrollbar,
|
||||
.data-table-scroll-wrapper::-webkit-scrollbar,
|
||||
.device-table-scroll-wrapper::-webkit-scrollbar,
|
||||
.metrics-body::-webkit-scrollbar,
|
||||
.data-table-body::-webkit-scrollbar,
|
||||
.device-table-body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.metrics-scroll-wrapper::-webkit-scrollbar-track,
|
||||
.data-table-scroll-wrapper::-webkit-scrollbar-track,
|
||||
.device-table-scroll-wrapper::-webkit-scrollbar-track,
|
||||
.metrics-body::-webkit-scrollbar-track,
|
||||
.data-table-body::-webkit-scrollbar-track,
|
||||
.device-table-body::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.metrics-scroll-wrapper::-webkit-scrollbar-thumb,
|
||||
.data-table-scroll-wrapper::-webkit-scrollbar-thumb,
|
||||
.device-table-scroll-wrapper::-webkit-scrollbar-thumb,
|
||||
.metrics-body::-webkit-scrollbar-thumb,
|
||||
.data-table-body::-webkit-scrollbar-thumb,
|
||||
.device-table-body::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.metrics-scroll-wrapper::-webkit-scrollbar-thumb:hover,
|
||||
.data-table-scroll-wrapper::-webkit-scrollbar-thumb:hover,
|
||||
.device-table-scroll-wrapper::-webkit-scrollbar-thumb:hover,
|
||||
.metrics-body::-webkit-scrollbar-thumb:hover,
|
||||
.data-table-body::-webkit-scrollbar-thumb:hover,
|
||||
.device-table-body::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Dark mode scrollbar */
|
||||
.dark .metrics-scroll-wrapper::-webkit-scrollbar-track,
|
||||
.dark .data-table-scroll-wrapper::-webkit-scrollbar-track,
|
||||
.dark .device-table-scroll-wrapper::-webkit-scrollbar-track,
|
||||
.dark .metrics-body::-webkit-scrollbar-track,
|
||||
.dark .data-table-body::-webkit-scrollbar-track,
|
||||
.dark .device-table-body::-webkit-scrollbar-track {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
.dark .metrics-scroll-wrapper::-webkit-scrollbar-thumb,
|
||||
.dark .data-table-scroll-wrapper::-webkit-scrollbar-thumb,
|
||||
.dark .device-table-scroll-wrapper::-webkit-scrollbar-thumb,
|
||||
.dark .metrics-body::-webkit-scrollbar-thumb,
|
||||
.dark .data-table-body::-webkit-scrollbar-thumb,
|
||||
.dark .device-table-body::-webkit-scrollbar-thumb {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.dark .metrics-scroll-wrapper::-webkit-scrollbar-thumb:hover,
|
||||
.dark .data-table-scroll-wrapper::-webkit-scrollbar-thumb:hover,
|
||||
.dark .device-table-scroll-wrapper::-webkit-scrollbar-thumb:hover,
|
||||
.dark .metrics-body::-webkit-scrollbar-thumb:hover,
|
||||
.dark .data-table-body::-webkit-scrollbar-thumb:hover,
|
||||
.dark .device-table-body::-webkit-scrollbar-thumb:hover {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
@ -3,39 +3,41 @@ import { FaCheckCircle, FaTimesCircle } from "react-icons/fa";
|
||||
|
||||
const AppDevice = ({ devices, theme }) => {
|
||||
return (
|
||||
<div className={`device-table-container ${theme}`}>
|
||||
{/* Header */}
|
||||
<div className="device-table-header">
|
||||
<div className="header-item">IP Address</div>
|
||||
<div className="header-item">Device Name</div>
|
||||
<div className="header-item">Status</div>
|
||||
</div>
|
||||
<div className={`device-table-container ${theme} my-3`}>
|
||||
<div className="device-table-scroll-wrapper">
|
||||
{/* Header */}
|
||||
<div className="device-table-header">
|
||||
<div>IP Address</div>
|
||||
<div>Device Name</div>
|
||||
<div>Status</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="device-table-body">
|
||||
{devices.map((device) => (
|
||||
<div className="device-table-row" key={device.id}>
|
||||
<div className="row-item">{device.ip}</div>
|
||||
<div className="row-item">{device.name}</div>
|
||||
<div
|
||||
className={`row-item status ${
|
||||
device.is_online === "Online" ? "online" : "offline"
|
||||
}`}
|
||||
>
|
||||
{device.is_online === "Online" ? (
|
||||
<>
|
||||
<FaCheckCircle className="me-2" />
|
||||
Online
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaTimesCircle className="me-2" />
|
||||
Offline
|
||||
</>
|
||||
)}
|
||||
{/* Body */}
|
||||
<div className="device-table-body">
|
||||
{devices.map((device) => (
|
||||
<div className="device-table-row" key={device.id}>
|
||||
<div>{device.ip}</div>
|
||||
<div>{device.name}</div>
|
||||
<div
|
||||
className={`status ${
|
||||
device.is_online === "Online" ? "online" : "offline"
|
||||
}`}
|
||||
>
|
||||
{device.is_online === "Online" ? (
|
||||
<>
|
||||
<FaCheckCircle className="me-2" />
|
||||
Online
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaTimesCircle className="me-2" />
|
||||
Offline
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,58 +1,58 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { FaDownload, FaUpload, FaTachometerAlt } from 'react-icons/fa';
|
||||
import { Card, Col, Row } from 'react-bootstrap';
|
||||
// /* eslint-disable react/prop-types */
|
||||
// import { FaDownload, FaUpload, FaTachometerAlt } from 'react-icons/fa';
|
||||
// import { Card, Col, Row } from 'react-bootstrap';
|
||||
|
||||
const LatestData = ({ data, theme }) => {
|
||||
const latestData = data.length > 0 ? data[data.length - 20] : null;
|
||||
// const LatestData = ({ data, theme }) => {
|
||||
// const latestData = data.length > 0 ? data[data.length - 20] : null;
|
||||
|
||||
if (!latestData) return null;
|
||||
// if (!latestData) return null;
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Card className='mb-4 shadow-sm border-light'>
|
||||
<Card.Header>
|
||||
<h3 style={{marginTop: '20px', marginBottom: '20px'}}>Latest Data</h3>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Row>
|
||||
<Col md={4}>
|
||||
<Card className={`${theme} mb-4 shadow-sm`}>
|
||||
<Card.Header as="h6">
|
||||
<FaDownload style={{ marginRight: '10px', width: '15px', color: '#28a745' }} />
|
||||
Latest Download
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Card.Title as="h4">{(latestData.data.download.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Card className={`${theme} mb-4 shadow-sm`}>
|
||||
<Card.Header as="h6">
|
||||
<FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
||||
Latest Upload
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Card.Title as="h4">{(latestData.data.upload.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Card className={`${theme} mb-4 shadow-sm`}>
|
||||
<Card.Header as="h6">
|
||||
<FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
||||
Latest Ping
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Card.Title as="h4">{(latestData.data.ping.latency).toFixed(2)} ms</Card.Title>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
// return (
|
||||
// <Row>
|
||||
// <Card className='mb-4 shadow-sm border-light'>
|
||||
// <Card.Header>
|
||||
// <h3 style={{marginTop: '20px', marginBottom: '20px'}}>Latest Data</h3>
|
||||
// </Card.Header>
|
||||
// <Card.Body>
|
||||
// <Row>
|
||||
// <Col md={4}>
|
||||
// <Card className={`${theme} mb-4 shadow-sm`}>
|
||||
// <Card.Header as="h6">
|
||||
// <FaDownload style={{ marginRight: '10px', width: '15px', color: '#28a745' }} />
|
||||
// Latest Download
|
||||
// </Card.Header>
|
||||
// <Card.Body>
|
||||
// <Card.Title as="h4">{(latestData.data.download.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
||||
// </Card.Body>
|
||||
// </Card>
|
||||
// </Col>
|
||||
// <Col md={4}>
|
||||
// <Card className={`${theme} mb-4 shadow-sm`}>
|
||||
// <Card.Header as="h6">
|
||||
// <FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
||||
// Latest Upload
|
||||
// </Card.Header>
|
||||
// <Card.Body>
|
||||
// <Card.Title as="h4">{(latestData.data.upload.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
||||
// </Card.Body>
|
||||
// </Card>
|
||||
// </Col>
|
||||
// <Col md={4}>
|
||||
// <Card className={`${theme} mb-4 shadow-sm`}>
|
||||
// <Card.Header as="h6">
|
||||
// <FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
||||
// Latest Ping
|
||||
// </Card.Header>
|
||||
// <Card.Body>
|
||||
// <Card.Title as="h4">{(latestData.data.ping.latency).toFixed(2)} ms</Card.Title>
|
||||
// </Card.Body>
|
||||
// </Card>
|
||||
// </Col>
|
||||
// </Row>
|
||||
// </Card.Body>
|
||||
// </Card>
|
||||
// </Row>
|
||||
// );
|
||||
// };
|
||||
|
||||
export default LatestData;
|
||||
// export default LatestData;
|
||||
|
||||
@ -1,32 +1,35 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
const MetricsTable = ({ metrics = [], theme }) => {
|
||||
return (
|
||||
<div className={`metrics-container ${theme}`}>
|
||||
<div className="metrics-header">
|
||||
<div>Monitor Name</div>
|
||||
<div>URL</div>
|
||||
<div>Response Time (ms)</div>
|
||||
<div>Status</div>
|
||||
<div>Certificate Valid</div>
|
||||
<div>Cert Days Remaining</div>
|
||||
</div>
|
||||
<div className="metrics-body">
|
||||
{metrics.map((item, index) => (
|
||||
<div key={index} className="metrics-row">
|
||||
<div>{item.monitor_name}</div>
|
||||
<div>
|
||||
<a href={item.monitor_url} target="_blank" rel="noopener noreferrer">
|
||||
{item.monitor_url}
|
||||
</a>
|
||||
<div className="metrics-scroll-wrapper">
|
||||
<div className="metrics-header">
|
||||
<div>Monitor Name</div>
|
||||
<div>URL</div>
|
||||
<div>Response Time (ms)</div>
|
||||
<div>Status</div>
|
||||
<div>Certificate Valid</div>
|
||||
<div>Cert Days Remaining</div>
|
||||
</div>
|
||||
<div className="metrics-body overflow-auto">
|
||||
{metrics.map((item, index) => (
|
||||
<div key={index} className="metrics-row">
|
||||
<div>{item.monitor_name}</div>
|
||||
<div>
|
||||
<a href={item.monitor_url} target="_blank" rel="noopener noreferrer">
|
||||
{item.monitor_url}
|
||||
</a>
|
||||
</div>
|
||||
<div>{item.response_time} ms</div>
|
||||
<div>
|
||||
{item.status === 1 ? "UP" : item.status === 0 ? "DOWN" : item.status === 2 ? "PENDING" : "MAINTENANCE"}
|
||||
</div>
|
||||
<div>{item.cert_valid === 1 ? "Yes" : "No"}</div>
|
||||
<div>{item.cert_days_remaining}</div>
|
||||
</div>
|
||||
<div>{item.response_time} ms</div>
|
||||
<div>
|
||||
{item.status === 1 ? "UP" : item.status === 0 ? "DOWN" : item.status === 2 ? "PENDING" : "MAINTENANCE"}
|
||||
</div>
|
||||
<div>{item.cert_valid === 1 ? "Yes" : "No"}</div>
|
||||
<div>{item.cert_days_remaining}</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,61 +1,54 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { format } from "date-fns";
|
||||
import { FaDownload, FaUpload, FaTachometerAlt } from "react-icons/fa";
|
||||
|
||||
const DataTable = ({ data = [], theme }) => {
|
||||
return (
|
||||
<div className={`data-table-container ${theme}`}>
|
||||
{/* Header */}
|
||||
<div className="data-table-header">
|
||||
<div className="header-item">No</div>
|
||||
<div className="header-item">
|
||||
<FaDownload className="me-2" /> Bytes
|
||||
</div>
|
||||
<div className="header-item">
|
||||
<FaDownload className="me-2" /> Elapsed
|
||||
</div>
|
||||
<div className="header-item">
|
||||
<FaUpload className="me-2" /> Bytes
|
||||
</div>
|
||||
<div className="header-item">
|
||||
<FaUpload className="me-2" /> Elapsed
|
||||
</div>
|
||||
<div className="header-item">
|
||||
<FaTachometerAlt className="me-2" /> Jitter
|
||||
</div>
|
||||
<div className="header-item">Time</div>
|
||||
<div className="header-item">Server</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="data-table-body">
|
||||
{data.map((item, index) => (
|
||||
<div className="data-table-row" key={item._id}>
|
||||
<div className="row-item">{index + 1}</div>
|
||||
<div className="row-item">
|
||||
{(item.data.download.bytes / 1048576).toFixed(1)} Mbps
|
||||
</div>
|
||||
<div className="row-item">
|
||||
{(item.data.download.elapsed / 1000).toFixed(2)}s
|
||||
</div>
|
||||
<div className="row-item">
|
||||
{(item.data.upload.bytes / 1048576).toFixed(1)} Mbps
|
||||
</div>
|
||||
<div className="row-item">
|
||||
{(item.data.upload.elapsed / 1000).toFixed(2)}s
|
||||
</div>
|
||||
<div className="row-item">{item.data.ping.jitter.toFixed(1)}ms</div>
|
||||
<div className="row-item">
|
||||
{format(new Date(item.datetime), "eeee, d MMMM yyyy HH:mm:ss")}
|
||||
</div>
|
||||
<div className="row-item">
|
||||
{item.data.isp} ({item.data.server.name},{" "}
|
||||
{item.data.server.location})
|
||||
</div>
|
||||
<div className="data-table-scroll-wrapper">
|
||||
{/* Header */}
|
||||
<div className="data-table-header">
|
||||
<div>No</div>
|
||||
<div>
|
||||
<FaDownload className="me-2" /> Bytes
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<FaDownload className="me-2" /> Elapsed
|
||||
</div>
|
||||
<div>
|
||||
<FaUpload className="me-2" /> Bytes
|
||||
</div>
|
||||
<div>
|
||||
<FaUpload className="me-2" /> Elapsed
|
||||
</div>
|
||||
<div>
|
||||
<FaTachometerAlt className="me-2" /> Jitter
|
||||
</div>
|
||||
<div>Time</div>
|
||||
<div>Server</div>
|
||||
</div>
|
||||
|
||||
{/* Body */}
|
||||
<div className="data-table-body overflow-auto">
|
||||
{data.map((item, index) => (
|
||||
<div className="data-table-row" key={item._id}>
|
||||
<div>{index + 1}</div>
|
||||
<div>{(item.data.download.bytes / 1048576).toFixed(1)} Mbps</div>
|
||||
<div>{(item.data.download.elapsed / 1000).toFixed(2)}s</div>
|
||||
<div>{(item.data.upload.bytes / 1048576).toFixed(1)} Mbps</div>
|
||||
<div>{(item.data.upload.elapsed / 1000).toFixed(2)}s</div>
|
||||
<div>{item.data.ping.jitter.toFixed(1)}ms</div>
|
||||
<div>{format(new Date(item.datetime), "eeee, d MMMM yyyy HH:mm:ss")}</div>
|
||||
<div>
|
||||
{item.data.isp} ({item.data.server.name}, {item.data.server.location})
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataTable;
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Navbar, Nav, Modal, Button } from 'react-bootstrap';
|
||||
/* eslint-disable react/prop-types */
|
||||
import { Navbar, Nav, Modal } from 'react-bootstrap';
|
||||
import { FaChartLine, FaSun, FaMoon, FaBars } from 'react-icons/fa';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
@ -11,7 +12,8 @@ const AppNavbar = ({ toggleTheme, theme }) => {
|
||||
<Navbar className={`${theme} navbar-app fixed-top w-100 shadow-sm d-flex justify-content-between`} style={{ zIndex: 1030, padding: '15px 20px' }}>
|
||||
<div className="d-flex align-items-center">
|
||||
<Navbar.Brand className={`${theme}`} href="#home" style={{ marginLeft: '10px' }}>
|
||||
<FaChartLine style={{ marginRight: '10px', color: '#b31e1e' }} /> Speedtest
|
||||
<FaChartLine style={{ width: '35px', height: '35px', marginRight: '10px', color: '#b31e1e' }} />
|
||||
<span>Speed Data</span>
|
||||
</Navbar.Brand>
|
||||
</div>
|
||||
<Nav className="ml-auto d-flex align-items-center" style={{ marginRight: '20px' }}>
|
||||
@ -25,22 +27,22 @@ const AppNavbar = ({ toggleTheme, theme }) => {
|
||||
</Navbar>
|
||||
|
||||
{/* Modal Sidebar */}
|
||||
<Modal className={`${theme}`} show={showModal} onHide={() => setShowModal(false)} centered>
|
||||
<Modal.Header className={`${theme}`} closeButton>
|
||||
<Modal.Title className={`${theme}`}>Menu</Modal.Title>
|
||||
<Modal className={`sidebar-modal ${theme}`} show={showModal} onHide={() => setShowModal(false)} centered>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Menu</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body className={`${theme}`}>
|
||||
<Nav className={`${theme} flex-column`}>
|
||||
<Nav.Link as={Link} to="/dashboard" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||
<Modal.Body>
|
||||
<Nav className="flex-column">
|
||||
<Nav.Link as={Link} to="/dashboard" onClick={() => setShowModal(false)}>
|
||||
Dashboard
|
||||
</Nav.Link>
|
||||
<Nav.Link as={Link} to="/devices" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||
<Nav.Link as={Link} to="/devices" onClick={() => setShowModal(false)}>
|
||||
Devices
|
||||
</Nav.Link>
|
||||
<Nav.Link as={Link} to="/reports" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||
<Nav.Link as={Link} to="/reports" onClick={() => setShowModal(false)}>
|
||||
Reports
|
||||
</Nav.Link>
|
||||
<Nav.Link as={Link} to="/settings" onClick={() => setShowModal(false)} className={`${theme}`}>
|
||||
<Nav.Link as={Link} to="/settings" onClick={() => setShowModal(false)}>
|
||||
Settings
|
||||
</Nav.Link>
|
||||
</Nav>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Navbar, Nav } from 'react-bootstrap';
|
||||
/* eslint-disable react/prop-types */
|
||||
import { Nav } from 'react-bootstrap';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { FaTachometerAlt, FaDesktop, FaChartBar, FaCog, FaBars } from "react-icons/fa";
|
||||
import { useState, useEffect } from 'react';
|
||||
import { FaTachometerAlt, FaDesktop, FaChartBar, FaCog } from "react-icons/fa";
|
||||
|
||||
const Sidebar = ({ theme }) => {
|
||||
const location = useLocation();
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Container, Spinner, Alert, Row, Col } from 'react-bootstrap';
|
||||
import { fetchSpeedTestData } from '../services/api';
|
||||
@ -27,8 +28,8 @@ const Dashboard = ({theme}) => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Container fluid className="text-center mt-5">
|
||||
<Spinner animation="border" variant="info" />
|
||||
<Container fluid className="d-flex justify-content-center align-items-center min-vh-100">
|
||||
<Spinner animation="border" variant="danger" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@ -44,7 +45,7 @@ const Dashboard = ({theme}) => {
|
||||
return (
|
||||
<Container fluid className={`flex dashboard-page ${theme}`}>
|
||||
<Col className="p-3 ms-auto">
|
||||
<h4 className='my-3'>Dashboard</h4>
|
||||
<h4 className='my-3 fw-bold'>Dashboard</h4>
|
||||
<Row>
|
||||
<Col>
|
||||
<AverageData data={data} theme={theme}/>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
// import AppDevice from '../components/dashboard/AppDevice';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Container, Spinner, Alert, Row, Col } from 'react-bootstrap';
|
||||
import { Container, Spinner, Alert, Col } from 'react-bootstrap';
|
||||
import { fetchActiveDevices, fetchMetricsData } from '../services/api';
|
||||
import MetricsTable from '../components/dashboard/MetricsTable';
|
||||
import AppDevice from '../components/dashboard/AppDevice';
|
||||
@ -35,7 +36,7 @@ function DevicesPage({theme}) {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Container fluid className="text-center mt-5">
|
||||
<Container fluid className="d-flex justify-content-center align-items-center min-vh-100">
|
||||
<Spinner animation="border" variant="danger"/>
|
||||
</Container>
|
||||
);
|
||||
@ -50,9 +51,9 @@ function DevicesPage({theme}) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Container fluid className={`device-page flex {$theme}`} style={{ fontFamily: 'Poppins, sans-serif'}}>
|
||||
<Container fluid className={`device-page flex {$theme}`}>
|
||||
<Col className="p-3 ms-auto">
|
||||
<h4 className='my-3'>Devices Page</h4>
|
||||
<h4 className='my-3 fw-bold'>Devices Page</h4>
|
||||
<AppDevice devices={devices} theme={theme}/>
|
||||
<MetricsTable metrics={metrics} theme={theme}/>
|
||||
</Col>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Container, Row, Col } from 'react-bootstrap'
|
||||
import { Container, Col } from 'react-bootstrap'
|
||||
|
||||
const ReportPage = () => {
|
||||
return (
|
||||
<Container fluid className='d-flex' style={{ fontFamily: 'Poppins, sans-serif'}}>
|
||||
<Container fluid className='d-flex'>
|
||||
<Col className="p-3 ms-auto">
|
||||
<h4 className='my-3'>Report Page</h4>
|
||||
<h4 className='my-3 fw-bold'>Report Page</h4>
|
||||
</Col>
|
||||
</Container>
|
||||
)
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import React from 'react'
|
||||
import { Container, Row, Col } from 'react-bootstrap'
|
||||
import { Container, Col } from 'react-bootstrap'
|
||||
|
||||
const SettingsPage = () => {
|
||||
return (
|
||||
<Container fluid className='d-flex' style={{ fontFamily: 'Poppins, sans-serif'}}>
|
||||
<Container fluid className='d-flex'>
|
||||
<Col className="p-3 ms-auto">
|
||||
<h4 className='my-3'>Settings</h4>
|
||||
<h4 className='my-3 fw-bold'>Settings</h4>
|
||||
</Col>
|
||||
</Container>
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user