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*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
node_modules
|
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
@ -24,3 +22,4 @@ dist-ssr
|
|||||||
*.sw?
|
*.sw?
|
||||||
.env
|
.env
|
||||||
*.zip
|
*.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 |
348
src/App.css
348
src/App.css
@ -65,12 +65,13 @@ body.dark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
|
margin-top: 75px;
|
||||||
margin-left: 0; /* Pastikan kontennya full width */
|
margin-left: 0; /* Pastikan kontennya full width */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 769px) {
|
||||||
.menu-toggle-btn, .sidebar-toggle-btn {
|
.menu-toggle-btn, .sidebar-toggle-btn {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -84,8 +85,7 @@ body.dark {
|
|||||||
/* Styling modal */
|
/* Styling modal */
|
||||||
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
/* Styling untuk tombol modal hanya tampil di bawah 768px */
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.menu-toggle-btn,
|
.menu-toggle-btn {
|
||||||
.sidebar-toggle-btn {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,6 +145,20 @@ body.dark {
|
|||||||
background-color: rgba(255, 255, 255, 0.1);
|
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 {
|
.navbar-app {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -156,6 +170,20 @@ body.dark {
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* Default shadow */
|
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 Mode Navbar */
|
||||||
.light .navbar-app {
|
.light .navbar-app {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
@ -370,6 +398,14 @@ button:hover {
|
|||||||
/* METRICS GRID TABLE */
|
/* 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 {
|
.metrics-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -379,20 +415,39 @@ button:hover {
|
|||||||
transition: background-color 0.3s, color 0.3s;
|
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;
|
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;
|
gap: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header Styling */
|
/* LIGHT MODE Styling */
|
||||||
.metrics-header {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LIGHT MODE */
|
|
||||||
.light .metrics-container {
|
.light .metrics-container {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #1d1d1d;
|
color: #1d1d1d;
|
||||||
@ -413,7 +468,7 @@ button:hover {
|
|||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DARK MODE */
|
/* DARK MODE Styling */
|
||||||
.dark .metrics-container {
|
.dark .metrics-container {
|
||||||
background-color: #161616;
|
background-color: #161616;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@ -447,8 +502,8 @@ button:hover {
|
|||||||
/* Responsive Design */
|
/* Responsive Design */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.metrics-header, .metrics-row {
|
.metrics-header, .metrics-row {
|
||||||
grid-template-columns: 1fr 1fr 1fr; /* Kurangi jumlah kolom agar lebih rapi di mobile */
|
width: 1290px;
|
||||||
font-size: 14px;
|
grid-template-columns: repeat(6, minmax(200px, 1fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,6 +511,13 @@ button:hover {
|
|||||||
/* DATA TABLE GRID STYLING */
|
/* 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 {
|
.data-table-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -465,20 +527,74 @@ button:hover {
|
|||||||
transition: background-color 0.3s, color 0.3s;
|
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;
|
display: grid;
|
||||||
grid-template-columns: 1fr 2fr 2fr 2fr 2fr 2fr 3fr 3fr;
|
grid-template-columns: repeat(8, minmax(100px, 1fr));
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
width: 1300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HEADER */
|
/* Pastikan body bisa scroll */
|
||||||
.data-table-header {
|
.data-table-body {
|
||||||
font-weight: bold;
|
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 MODE */
|
||||||
.light .data-table-container {
|
.light .data-table-container {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
@ -521,18 +637,17 @@ button:hover {
|
|||||||
background-color: #2a2a2a;
|
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 */
|
/* DEVICE TABLE GRID STYLE */
|
||||||
/* ======================= */
|
/* ======================= */
|
||||||
|
|
||||||
|
/* Wrapper untuk memungkinkan scroll horizontal */
|
||||||
|
.device-table-scroll-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.device-table-container {
|
.device-table-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -542,18 +657,37 @@ button:hover {
|
|||||||
transition: background-color 0.3s, color 0.3s;
|
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;
|
display: grid;
|
||||||
grid-template-columns: 3fr 3fr 2fr;
|
grid-template-columns: 3fr 3fr 2fr;
|
||||||
gap: 10px;
|
gap: 5px;
|
||||||
padding: 12px;
|
padding: 6px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-items: center;
|
width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HEADER */
|
/* Pastikan body bisa scroll */
|
||||||
.device-table-header {
|
.device-table-body {
|
||||||
font-weight: bold;
|
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 */
|
/* LIGHT MODE */
|
||||||
@ -614,10 +748,150 @@ button:hover {
|
|||||||
color: red;
|
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 */
|
/* Responsive Design */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.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 {
|
.device-table-header, .device-table-row {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
|
||||||
font-size: 14px;
|
}
|
||||||
|
|
||||||
|
/* 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,22 +3,23 @@ import { FaCheckCircle, FaTimesCircle } from "react-icons/fa";
|
|||||||
|
|
||||||
const AppDevice = ({ devices, theme }) => {
|
const AppDevice = ({ devices, theme }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`device-table-container ${theme}`}>
|
<div className={`device-table-container ${theme} my-3`}>
|
||||||
|
<div className="device-table-scroll-wrapper">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="device-table-header">
|
<div className="device-table-header">
|
||||||
<div className="header-item">IP Address</div>
|
<div>IP Address</div>
|
||||||
<div className="header-item">Device Name</div>
|
<div>Device Name</div>
|
||||||
<div className="header-item">Status</div>
|
<div>Status</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="device-table-body">
|
<div className="device-table-body">
|
||||||
{devices.map((device) => (
|
{devices.map((device) => (
|
||||||
<div className="device-table-row" key={device.id}>
|
<div className="device-table-row" key={device.id}>
|
||||||
<div className="row-item">{device.ip}</div>
|
<div>{device.ip}</div>
|
||||||
<div className="row-item">{device.name}</div>
|
<div>{device.name}</div>
|
||||||
<div
|
<div
|
||||||
className={`row-item status ${
|
className={`status ${
|
||||||
device.is_online === "Online" ? "online" : "offline"
|
device.is_online === "Online" ? "online" : "offline"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -38,6 +39,7 @@ const AppDevice = ({ devices, theme }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,58 +1,58 @@
|
|||||||
/* eslint-disable react/prop-types */
|
// /* eslint-disable react/prop-types */
|
||||||
import { FaDownload, FaUpload, FaTachometerAlt } from 'react-icons/fa';
|
// import { FaDownload, FaUpload, FaTachometerAlt } from 'react-icons/fa';
|
||||||
import { Card, Col, Row } from 'react-bootstrap';
|
// import { Card, Col, Row } from 'react-bootstrap';
|
||||||
|
|
||||||
const LatestData = ({ data, theme }) => {
|
// const LatestData = ({ data, theme }) => {
|
||||||
const latestData = data.length > 0 ? data[data.length - 20] : null;
|
// const latestData = data.length > 0 ? data[data.length - 20] : null;
|
||||||
|
|
||||||
if (!latestData) return null;
|
// if (!latestData) return null;
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<Row>
|
// <Row>
|
||||||
<Card className='mb-4 shadow-sm border-light'>
|
// <Card className='mb-4 shadow-sm border-light'>
|
||||||
<Card.Header>
|
// <Card.Header>
|
||||||
<h3 style={{marginTop: '20px', marginBottom: '20px'}}>Latest Data</h3>
|
// <h3 style={{marginTop: '20px', marginBottom: '20px'}}>Latest Data</h3>
|
||||||
</Card.Header>
|
// </Card.Header>
|
||||||
<Card.Body>
|
// <Card.Body>
|
||||||
<Row>
|
// <Row>
|
||||||
<Col md={4}>
|
// <Col md={4}>
|
||||||
<Card className={`${theme} mb-4 shadow-sm`}>
|
// <Card className={`${theme} mb-4 shadow-sm`}>
|
||||||
<Card.Header as="h6">
|
// <Card.Header as="h6">
|
||||||
<FaDownload style={{ marginRight: '10px', width: '15px', color: '#28a745' }} />
|
// <FaDownload style={{ marginRight: '10px', width: '15px', color: '#28a745' }} />
|
||||||
Latest Download
|
// Latest Download
|
||||||
</Card.Header>
|
// </Card.Header>
|
||||||
<Card.Body>
|
// <Card.Body>
|
||||||
<Card.Title as="h4">{(latestData.data.download.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
// <Card.Title as="h4">{(latestData.data.download.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
||||||
</Card.Body>
|
// </Card.Body>
|
||||||
</Card>
|
// </Card>
|
||||||
</Col>
|
// </Col>
|
||||||
<Col md={4}>
|
// <Col md={4}>
|
||||||
<Card className={`${theme} mb-4 shadow-sm`}>
|
// <Card className={`${theme} mb-4 shadow-sm`}>
|
||||||
<Card.Header as="h6">
|
// <Card.Header as="h6">
|
||||||
<FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
// <FaUpload style={{ marginRight: '10px', width: '15px', color: '#00bcd4' }} />
|
||||||
Latest Upload
|
// Latest Upload
|
||||||
</Card.Header>
|
// </Card.Header>
|
||||||
<Card.Body>
|
// <Card.Body>
|
||||||
<Card.Title as="h4">{(latestData.data.upload.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
// <Card.Title as="h4">{(latestData.data.upload.bandwidth / 1000000).toFixed(2)} Mbps</Card.Title>
|
||||||
</Card.Body>
|
// </Card.Body>
|
||||||
</Card>
|
// </Card>
|
||||||
</Col>
|
// </Col>
|
||||||
<Col md={4}>
|
// <Col md={4}>
|
||||||
<Card className={`${theme} mb-4 shadow-sm`}>
|
// <Card className={`${theme} mb-4 shadow-sm`}>
|
||||||
<Card.Header as="h6">
|
// <Card.Header as="h6">
|
||||||
<FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
// <FaTachometerAlt style={{ marginRight: '10px', width: '15px', color: '#ffc107' }} />
|
||||||
Latest Ping
|
// Latest Ping
|
||||||
</Card.Header>
|
// </Card.Header>
|
||||||
<Card.Body>
|
// <Card.Body>
|
||||||
<Card.Title as="h4">{(latestData.data.ping.latency).toFixed(2)} ms</Card.Title>
|
// <Card.Title as="h4">{(latestData.data.ping.latency).toFixed(2)} ms</Card.Title>
|
||||||
</Card.Body>
|
// </Card.Body>
|
||||||
</Card>
|
// </Card>
|
||||||
</Col>
|
// </Col>
|
||||||
</Row>
|
// </Row>
|
||||||
</Card.Body>
|
// </Card.Body>
|
||||||
</Card>
|
// </Card>
|
||||||
</Row>
|
// </Row>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
export default LatestData;
|
// export default LatestData;
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
|
||||||
const MetricsTable = ({ metrics = [], theme }) => {
|
const MetricsTable = ({ metrics = [], theme }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`metrics-container ${theme}`}>
|
<div className={`metrics-container ${theme}`}>
|
||||||
|
<div className="metrics-scroll-wrapper">
|
||||||
<div className="metrics-header">
|
<div className="metrics-header">
|
||||||
<div>Monitor Name</div>
|
<div>Monitor Name</div>
|
||||||
<div>URL</div>
|
<div>URL</div>
|
||||||
@ -10,7 +12,7 @@ const MetricsTable = ({ metrics = [], theme }) => {
|
|||||||
<div>Certificate Valid</div>
|
<div>Certificate Valid</div>
|
||||||
<div>Cert Days Remaining</div>
|
<div>Cert Days Remaining</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="metrics-body">
|
<div className="metrics-body overflow-auto">
|
||||||
{metrics.map((item, index) => (
|
{metrics.map((item, index) => (
|
||||||
<div key={index} className="metrics-row">
|
<div key={index} className="metrics-row">
|
||||||
<div>{item.monitor_name}</div>
|
<div>{item.monitor_name}</div>
|
||||||
@ -29,6 +31,7 @@ const MetricsTable = ({ metrics = [], theme }) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,61 +1,54 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { FaDownload, FaUpload, FaTachometerAlt } from "react-icons/fa";
|
import { FaDownload, FaUpload, FaTachometerAlt } from "react-icons/fa";
|
||||||
|
|
||||||
const DataTable = ({ data = [], theme }) => {
|
const DataTable = ({ data = [], theme }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`data-table-container ${theme}`}>
|
<div className={`data-table-container ${theme}`}>
|
||||||
|
<div className="data-table-scroll-wrapper">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="data-table-header">
|
<div className="data-table-header">
|
||||||
<div className="header-item">No</div>
|
<div>No</div>
|
||||||
<div className="header-item">
|
<div>
|
||||||
<FaDownload className="me-2" /> Bytes
|
<FaDownload className="me-2" /> Bytes
|
||||||
</div>
|
</div>
|
||||||
<div className="header-item">
|
<div>
|
||||||
<FaDownload className="me-2" /> Elapsed
|
<FaDownload className="me-2" /> Elapsed
|
||||||
</div>
|
</div>
|
||||||
<div className="header-item">
|
<div>
|
||||||
<FaUpload className="me-2" /> Bytes
|
<FaUpload className="me-2" /> Bytes
|
||||||
</div>
|
</div>
|
||||||
<div className="header-item">
|
<div>
|
||||||
<FaUpload className="me-2" /> Elapsed
|
<FaUpload className="me-2" /> Elapsed
|
||||||
</div>
|
</div>
|
||||||
<div className="header-item">
|
<div>
|
||||||
<FaTachometerAlt className="me-2" /> Jitter
|
<FaTachometerAlt className="me-2" /> Jitter
|
||||||
</div>
|
</div>
|
||||||
<div className="header-item">Time</div>
|
<div>Time</div>
|
||||||
<div className="header-item">Server</div>
|
<div>Server</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="data-table-body">
|
<div className="data-table-body overflow-auto">
|
||||||
{data.map((item, index) => (
|
{data.map((item, index) => (
|
||||||
<div className="data-table-row" key={item._id}>
|
<div className="data-table-row" key={item._id}>
|
||||||
<div className="row-item">{index + 1}</div>
|
<div>{index + 1}</div>
|
||||||
<div className="row-item">
|
<div>{(item.data.download.bytes / 1048576).toFixed(1)} Mbps</div>
|
||||||
{(item.data.download.bytes / 1048576).toFixed(1)} Mbps
|
<div>{(item.data.download.elapsed / 1000).toFixed(2)}s</div>
|
||||||
</div>
|
<div>{(item.data.upload.bytes / 1048576).toFixed(1)} Mbps</div>
|
||||||
<div className="row-item">
|
<div>{(item.data.upload.elapsed / 1000).toFixed(2)}s</div>
|
||||||
{(item.data.download.elapsed / 1000).toFixed(2)}s
|
<div>{item.data.ping.jitter.toFixed(1)}ms</div>
|
||||||
</div>
|
<div>{format(new Date(item.datetime), "eeee, d MMMM yyyy HH:mm:ss")}</div>
|
||||||
<div className="row-item">
|
<div>
|
||||||
{(item.data.upload.bytes / 1048576).toFixed(1)} Mbps
|
{item.data.isp} ({item.data.server.name}, {item.data.server.location})
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DataTable;
|
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 { FaChartLine, FaSun, FaMoon, FaBars } from 'react-icons/fa';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
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' }}>
|
<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">
|
<div className="d-flex align-items-center">
|
||||||
<Navbar.Brand className={`${theme}`} href="#home" style={{ marginLeft: '10px' }}>
|
<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>
|
</Navbar.Brand>
|
||||||
</div>
|
</div>
|
||||||
<Nav className="ml-auto d-flex align-items-center" style={{ marginRight: '20px' }}>
|
<Nav className="ml-auto d-flex align-items-center" style={{ marginRight: '20px' }}>
|
||||||
@ -25,22 +27,22 @@ const AppNavbar = ({ toggleTheme, theme }) => {
|
|||||||
</Navbar>
|
</Navbar>
|
||||||
|
|
||||||
{/* Modal Sidebar */}
|
{/* Modal Sidebar */}
|
||||||
<Modal className={`${theme}`} show={showModal} onHide={() => setShowModal(false)} centered>
|
<Modal className={`sidebar-modal ${theme}`} show={showModal} onHide={() => setShowModal(false)} centered>
|
||||||
<Modal.Header className={`${theme}`} closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title className={`${theme}`}>Menu</Modal.Title>
|
<Modal.Title>Menu</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body className={`${theme}`}>
|
<Modal.Body>
|
||||||
<Nav className={`${theme} flex-column`}>
|
<Nav className="flex-column">
|
||||||
<Nav.Link as={Link} to="/dashboard" onClick={() => setShowModal(false)} className={`${theme}`}>
|
<Nav.Link as={Link} to="/dashboard" onClick={() => setShowModal(false)}>
|
||||||
Dashboard
|
Dashboard
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
<Nav.Link as={Link} to="/devices" onClick={() => setShowModal(false)} className={`${theme}`}>
|
<Nav.Link as={Link} to="/devices" onClick={() => setShowModal(false)}>
|
||||||
Devices
|
Devices
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
<Nav.Link as={Link} to="/reports" onClick={() => setShowModal(false)} className={`${theme}`}>
|
<Nav.Link as={Link} to="/reports" onClick={() => setShowModal(false)}>
|
||||||
Reports
|
Reports
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
<Nav.Link as={Link} to="/settings" onClick={() => setShowModal(false)} className={`${theme}`}>
|
<Nav.Link as={Link} to="/settings" onClick={() => setShowModal(false)}>
|
||||||
Settings
|
Settings
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
</Nav>
|
</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 { Link, useLocation } from 'react-router-dom';
|
||||||
import { FaTachometerAlt, FaDesktop, FaChartBar, FaCog, FaBars } from "react-icons/fa";
|
import { FaTachometerAlt, FaDesktop, FaChartBar, FaCog } from "react-icons/fa";
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
const Sidebar = ({ theme }) => {
|
const Sidebar = ({ theme }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Container, Spinner, Alert, Row, Col } from 'react-bootstrap';
|
import { Container, Spinner, Alert, Row, Col } from 'react-bootstrap';
|
||||||
import { fetchSpeedTestData } from '../services/api';
|
import { fetchSpeedTestData } from '../services/api';
|
||||||
@ -27,8 +28,8 @@ const Dashboard = ({theme}) => {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
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="info" />
|
<Spinner animation="border" variant="danger" />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -44,7 +45,7 @@ const Dashboard = ({theme}) => {
|
|||||||
return (
|
return (
|
||||||
<Container fluid className={`flex dashboard-page ${theme}`}>
|
<Container fluid className={`flex dashboard-page ${theme}`}>
|
||||||
<Col className="p-3 ms-auto">
|
<Col className="p-3 ms-auto">
|
||||||
<h4 className='my-3'>Dashboard</h4>
|
<h4 className='my-3 fw-bold'>Dashboard</h4>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<AverageData data={data} theme={theme}/>
|
<AverageData data={data} theme={theme}/>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
// import AppDevice from '../components/dashboard/AppDevice';
|
// import AppDevice from '../components/dashboard/AppDevice';
|
||||||
import { useEffect, useState } from 'react';
|
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 { fetchActiveDevices, fetchMetricsData } from '../services/api';
|
||||||
import MetricsTable from '../components/dashboard/MetricsTable';
|
import MetricsTable from '../components/dashboard/MetricsTable';
|
||||||
import AppDevice from '../components/dashboard/AppDevice';
|
import AppDevice from '../components/dashboard/AppDevice';
|
||||||
@ -35,7 +36,7 @@ function DevicesPage({theme}) {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
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"/>
|
<Spinner animation="border" variant="danger"/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
@ -50,9 +51,9 @@ function DevicesPage({theme}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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">
|
<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}/>
|
<AppDevice devices={devices} theme={theme}/>
|
||||||
<MetricsTable metrics={metrics} theme={theme}/>
|
<MetricsTable metrics={metrics} theme={theme}/>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import React from 'react'
|
import { Container, Col } from 'react-bootstrap'
|
||||||
import { Container, Row, Col } from 'react-bootstrap'
|
|
||||||
|
|
||||||
const ReportPage = () => {
|
const ReportPage = () => {
|
||||||
return (
|
return (
|
||||||
<Container fluid className='d-flex' style={{ fontFamily: 'Poppins, sans-serif'}}>
|
<Container fluid className='d-flex'>
|
||||||
<Col className="p-3 ms-auto">
|
<Col className="p-3 ms-auto">
|
||||||
<h4 className='my-3'>Report Page</h4>
|
<h4 className='my-3 fw-bold'>Report Page</h4>
|
||||||
</Col>
|
</Col>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import React from 'react'
|
import { Container, Col } from 'react-bootstrap'
|
||||||
import { Container, Row, Col } from 'react-bootstrap'
|
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
return (
|
return (
|
||||||
<Container fluid className='d-flex' style={{ fontFamily: 'Poppins, sans-serif'}}>
|
<Container fluid className='d-flex'>
|
||||||
<Col className="p-3 ms-auto">
|
<Col className="p-3 ms-auto">
|
||||||
<h4 className='my-3'>Settings</h4>
|
<h4 className='my-3 fw-bold'>Settings</h4>
|
||||||
</Col>
|
</Col>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user