15/02/2025

This commit is contained in:
damarrsyh 2025-02-15 14:17:52 +07:00
parent a58c9b35a4
commit 3e9d001850
13 changed files with 500 additions and 227 deletions

3
.gitignore vendored
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -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;
}

View File

@ -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>
);

View File

@ -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;

View File

@ -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>
);

View File

@ -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;

View File

@ -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>

View File

@ -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();

View File

@ -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}/>

View File

@ -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>

View File

@ -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>
)

View File

@ -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>
)