├── .dockerIgnore ├── .env ├── .gitignore ├── .gitmodules ├── .prettierrc.json ├── Dockerfile ├── README.md ├── client ├── App.tsx ├── assets │ └── faros.png ├── components │ ├── AppContent │ │ ├── AppContent.tsx │ │ └── index.ts │ ├── CollapsiblePanel │ │ ├── CollapsiblePanel.tsx │ │ └── index.ts │ ├── DataGridWithHeader │ │ ├── DataGridWithHeader.tsx │ │ └── index.ts │ ├── FlexBetween │ │ ├── FlexBetween.tsx │ │ └── index.ts │ ├── Graph │ │ ├── Graph.module.css │ │ ├── Graph.module.scss │ │ ├── Graph.tsx │ │ ├── GraphNetwork.tsx │ │ ├── graph.scss │ │ ├── index.ts │ │ └── styles.module.css │ ├── Header │ │ ├── Header.tsx │ │ └── index.ts │ ├── LineChart │ │ ├── LineChart.tsx │ │ ├── index.ts │ │ └── linechart.scss │ ├── List-View │ │ ├── List-View.tsx │ │ ├── ListView.tsx │ │ ├── ListViewTable.tsx │ │ └── index.ts │ ├── ListViewHeader │ │ ├── ListViewHeader.tsx │ │ └── index.ts │ ├── NameSpaceTable │ │ ├── NameSpaceTable.tsx │ │ └── index.ts │ ├── NavBar │ │ ├── NavBar.module.css │ │ ├── NavBar.tsx │ │ ├── NavBarLink.tsx │ │ ├── index.ts │ │ └── navbar.scss │ ├── StatBox │ │ ├── StatBox.tsx │ │ └── index.ts │ ├── SwitchButton │ │ ├── SwitchButton.module.css │ │ ├── SwitchButton.scss │ │ ├── SwitchButton.tsx │ │ └── index.ts │ ├── ThemeContainer │ │ ├── ThemeContainer.tsx │ │ └── index.ts │ └── index.ts ├── css │ ├── ColoredText.scss │ ├── Landing.scss │ ├── NodeModal.scss │ ├── NotifDisplay.scss │ ├── Pod.scss │ ├── Reactdnd.scss │ ├── base.scss │ ├── graph.scss │ ├── home.scss │ ├── index.css │ ├── navbar.scss │ ├── node.scss │ ├── settings.scss │ ├── switchbutton.scss │ └── variables.scss ├── index.css ├── index.html ├── index.tsx ├── layout │ ├── DefaultLayout.tsx │ └── index.ts ├── pages │ ├── ListViewPage │ │ ├── ListViewPage.scss │ │ └── ListViewPage.tsx │ └── NodeView │ │ ├── NodeView.tsx │ │ └── index.ts ├── redux │ ├── bobbySocketService.ts │ ├── metricsApi.ts │ ├── metricsSlice.ts │ ├── socketService.ts │ └── store.ts ├── routes.tsx ├── services │ ├── api.ts │ └── bobbySocketService.ts ├── store │ ├── index.ts │ ├── slice.ts │ └── store.ts ├── theme.scss ├── theme.ts ├── util │ ├── formatters │ │ ├── formatContainerUsage.tsx │ │ ├── formatMetricsMap.tsx │ │ ├── formatNotifications.tsx │ │ └── index.ts │ ├── index.ts │ └── timeUtils.ts └── variables.scss ├── dist ├── bundle.js ├── bundle.js.map ├── client_components_LineChart_LineChart_tsx.bundle.js ├── client_components_LineChart_LineChart_tsx.bundle.js.map ├── client_layout_DefaultLayout_tsx.bundle.js ├── client_layout_DefaultLayout_tsx.bundle.js.map ├── client_pages_ListViewPage_ListViewPage_tsx.bundle.js ├── client_pages_ListViewPage_ListViewPage_tsx.bundle.js.map ├── client_pages_NodeView_NodeView_tsx.bundle.js ├── client_pages_NodeView_NodeView_tsx.bundle.js.map ├── client_views_dashboard_Dashboard_js.bundle.js ├── client_views_dashboard_Dashboard_js.bundle.js.map ├── index.html ├── vendors-node_modules_coreui_icons-react_dist_index_es_js-node_modules_coreui_icons_dist_esm_f-eb146e.bundle.js ├── vendors-node_modules_coreui_icons-react_dist_index_es_js-node_modules_coreui_icons_dist_esm_f-eb146e.bundle.js.map ├── vendors-node_modules_coreui_icons_dist_esm_brand_cib-facebook_js-node_modules_coreui_icons_di-5939ac.bundle.js ├── vendors-node_modules_coreui_icons_dist_esm_brand_cib-facebook_js-node_modules_coreui_icons_di-5939ac.bundle.js.map ├── vendors-node_modules_nivo_line_dist_nivo-line_es_js.bundle.js └── vendors-node_modules_nivo_line_dist_nivo-line_es_js.bundle.js.map ├── installation.yaml ├── kubeConfigs ├── config.yaml └── deployment.yaml ├── nodemon.json ├── package-lock.json ├── package.json ├── postcss.config.ts ├── server ├── controllers │ └── metricsController.ts ├── index.ts ├── models │ └── userModel.ts └── routers │ └── router.ts ├── tsconfig.json ├── types ├── css.d.ts ├── index.d.ts └── types.ts └── webpack.config.ts /.dockerIgnore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=3000 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | build 4 | /build -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "faros-scope"] 2 | path = faros-scope 3 | url = https://github.com/oslabs-beta/faros-scope.git 4 | [submodule "DEMO"] 5 | path = DEMO 6 | url = https://github.com/eddy8mame/DEMO.git 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "arrowParens": "always", 6 | "printWidth": 80, 7 | "tabWidth": 2 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build the client and server 2 | FROM node:alpine AS builder 3 | 4 | # Set the working directory 5 | WORKDIR /app 6 | 7 | # Copy package.json and package-lock.json 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy the source code of the application 14 | COPY . . 15 | 16 | # Run the TypeScript compiler and the build process for client and server 17 | RUN npm run build 18 | 19 | # Stage 2: Setup the serve environment 20 | FROM node:alpine 21 | 22 | # Set the working directory 23 | WORKDIR /app 24 | 25 | # Copy the built client and server directories from the builder stage 26 | COPY --from=builder /app/build/client ./client 27 | COPY --from=builder /app/build/server ./server 28 | 29 | # If your server requires any runtime dependencies, install them here 30 | COPY package*.json ./ 31 | RUN npm install 32 | 33 | # Expose the port the server is running on 34 | EXPOSE 3000 35 | 36 | # Run the server.js from the build folder 37 | CMD ["node", "server/index.js"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Step 1. Install Prometheus 2 | 3 | ```shell 4 | 5 | helm repo add prometheus-community https://prometheus-community.github.io/helm-charts 6 | 7 | helm search repo prometheus-community 8 | 9 | ``` 10 | 11 | Step 2. Create Service Account 12 | 13 | ```yaml 14 | apiVersion: v1 15 | kind: ServiceAccount 16 | metadata: 17 | name: kubby-meteor-service-account 18 | ``` 19 | 20 | Step 3. Create ClusterRole 21 | 22 | ```yaml 23 | apiVersion: rbac.authorization.k8s.io/v1 24 | kind: ClusterRole 25 | metadata: 26 | name: kubby-meteor-clusterRole 27 | rules: 28 | - apiGroups: 29 | - "" 30 | resources: 31 | - nodes 32 | - pods 33 | - services 34 | verbs: 35 | - get 36 | - list 37 | - watch 38 | - apiGroups: 39 | - metrics.k8s.io 40 | resources: 41 | - pods 42 | - nodes 43 | verbs: 44 | - get 45 | - list 46 | - watch 47 | ``` 48 | 49 | Step 4. Create ClusterRoleBinding 50 | 51 | ```yaml 52 | apiVersion: rbac.authorization.k8s.io/v1 53 | kind: ClusterRoleBinding 54 | metadata: 55 | name: kubby-meteor-clusterrolebinding 56 | roleRef: 57 | apiGroup: rbac.authorization.k8s.io 58 | kind: ClusterRole 59 | name: kubby-meteor-clusterrole 60 | subjects: 61 | - kind: ServiceAccount 62 | name: kubby-meteor-service-account 63 | namespace: default 64 | ``` 65 | 66 | Step 5. Create KubbyMeteor deployment 67 | 68 | ```yaml 69 | apiVersion: apps/v1 70 | kind: Deployment 71 | metadata: 72 | labels: 73 | app: kubby-meteor 74 | name: kubby-meteor 75 | spec: 76 | replicas: 1 77 | selector: 78 | matchLabels: 79 | app: kubby-meteor 80 | strategy: {} 81 | template: 82 | metadata: 83 | labels: 84 | app: kubby-meteor 85 | spec: 86 | serviceAccountName: kubby-meteor-service-account 87 | containers: 88 | - name: kubby-meteor 89 | image: ndoolan/kubby-meteor:2.4 90 | ports: 91 | - containerPort: 8000 92 | resources: {} 93 | ``` 94 | 95 | Step 6. Expose KubbyMeteor deployment 96 | 97 | ```yaml 98 | apiVersion: v1 99 | kind: Service 100 | metadata: 101 | labels: 102 | app: kubby-meteor 103 | name: kubby-meteor-service 104 | spec: 105 | ports: 106 | - port: 80 107 | protocol: TCP 108 | targetPort: 8000 109 | selector: 110 | app: kubby-meteor 111 | status: 112 | loadBalancer: {} 113 | ``` 114 | 115 | Step 7. Create Faros-Scope pod 116 | 117 | ```yaml 118 | apiVersion: v1 119 | kind: Pod 120 | metadata: 121 | labels: 122 | run: faros-scope 123 | name: faros-scope 124 | spec: 125 | containers: 126 | - env: 127 | - name: NODE_ENV 128 | value: prod 129 | image: eddywins/faros:v0.8 130 | name: faros-scope 131 | ports: 132 | - containerPort: 3000 133 | resources: {} 134 | dnsPolicy: ClusterFirst 135 | restartPolicy: Always 136 | status: {} 137 | ``` 138 | 139 | Step 8. Expose Faros-Scope pod 140 | 141 | ```yaml 142 | apiVersion: v1 143 | kind: Service 144 | metadata: 145 | labels: 146 | run: faros-scope 147 | name: faros-scope 148 | spec: 149 | ports: 150 | - port: 80 151 | protocol: TCP 152 | targetPort: 3000 153 | selector: 154 | run: faros-scope 155 | type: LoadBalancer 156 | status: 157 | loadBalancer: {} 158 | ``` 159 | -------------------------------------------------------------------------------- /client/App.tsx: -------------------------------------------------------------------------------- 1 | import { Routes, Route, BrowserRouter } from 'react-router-dom'; 2 | import { lazy, Suspense } from 'react'; 3 | import { ColorModeContext, useMode } from './theme'; 4 | import { ThemeProvider } from '@mui/system'; 5 | import { CssBaseline } from '@mui/material'; 6 | import { CSpinner } from '@coreui/react'; 7 | 8 | // Containers 9 | const DefaultLayout = lazy(() => import('./layout/DefaultLayout')); 10 | 11 | const Loader = () => { 12 | return ( 13 |
21 | 22 |
23 | ); 24 | }; 25 | 26 | const App = () => { 27 | const { theme, colorMode } = useMode(); 28 | 29 | return ( 30 | 31 | }> 32 | 33 | 34 | 35 | 36 | } /> 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default App; 46 | -------------------------------------------------------------------------------- /client/assets/faros.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/faros-scope/aefb07ebb5b579eec7b12c22e1d3908723d4557c/client/assets/faros.png -------------------------------------------------------------------------------- /client/components/AppContent/AppContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import { Navigate, Route, Routes } from 'react-router-dom'; 3 | import { CContainer, CSpinner } from '@coreui/react'; 4 | 5 | // routes config 6 | import routes from '../../routes'; 7 | 8 | const Loader = () => { 9 | return ( 10 |
17 | 18 |
19 | ); 20 | }; 21 | 22 | const AppContent = () => { 23 | return ( 24 | 25 | }> 26 | 27 | {routes.map((route, idx) => { 28 | return ( 29 | route.element && ( 30 | } 34 | /> 35 | ) 36 | ); 37 | })} 38 | } /> 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default React.memo(AppContent); 46 | -------------------------------------------------------------------------------- /client/components/AppContent/index.ts: -------------------------------------------------------------------------------- 1 | import AppContent from './AppContent'; 2 | 3 | export {AppContent} -------------------------------------------------------------------------------- /client/components/CollapsiblePanel/CollapsiblePanel.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from "@mui/material"; 2 | import Accordion from "@mui/material/Accordion"; 3 | import AccordionDetails from "@mui/material/AccordionDetails"; 4 | import React from "react"; 5 | import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; 6 | import ArrowForwardIosSharpIcon from "@mui/icons-material/ArrowForwardIosSharp"; 7 | import MuiAccordionSummary, { 8 | AccordionSummaryProps, 9 | } from "@mui/material/AccordionSummary"; 10 | import { styled } from "@mui/material/styles"; 11 | import { useTheme } from "@mui/material"; 12 | 13 | const AccordionSummary = styled((props: AccordionSummaryProps) => ( 14 | } 16 | {...props} 17 | /> 18 | ))(({ theme }) => ({ 19 | flexDirection: "row-reverse", 20 | "& .MuiAccordionSummary-expandIconWrapper.Mui-expanded": { 21 | transform: "rotate(90deg)", 22 | }, 23 | "& .MuiAccordionSummary-content": { 24 | marginLeft: theme.spacing(1), 25 | }, 26 | })); 27 | 28 | interface Props { 29 | title: string; 30 | children: React.ReactNode; // TODO: Verify this is correct 31 | } 32 | 33 | export const CollapsiblePanel = ({ title, children }: Props) => { 34 | const theme = useTheme(); 35 | return ( 36 | 43 | } 45 | aria-controls={title.replace(/\s+/g, "") + "-content"} 46 | id={title.replace(/\s+/g, "") + "-header"} 47 | sx={{ 48 | // backgroundColor: theme.palette.background.alt, 49 | "& .MuiAccordionSummary.Mui-expanded": { 50 | margin: "20px 0 0px 20px", 51 | }, 52 | }} 53 | > 54 | {title} 55 | 56 | 65 | {children} 66 | 67 | 68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /client/components/CollapsiblePanel/index.ts: -------------------------------------------------------------------------------- 1 | export {CollapsiblePanel} from './CollapsiblePanel'; -------------------------------------------------------------------------------- /client/components/DataGridWithHeader/DataGridWithHeader.tsx: -------------------------------------------------------------------------------- 1 | import { DataGrid, GridToolbar, GridColDef } from '@mui/x-data-grid'; 2 | import { Typography, Box } from '@mui/material'; 3 | 4 | interface Props { 5 | title: string; 6 | columns: GridColDef[]; 7 | isLoading: boolean; 8 | data: [string: any][]; 9 | } 10 | 11 | const DataGridWithHeader = ({ title, columns, data, isLoading }: Props) => { 12 | return ( 13 | 40 | 52 | {title} 53 | 54 | 76 | 77 | ); 78 | }; 79 | 80 | export default DataGridWithHeader; 81 | -------------------------------------------------------------------------------- /client/components/DataGridWithHeader/index.ts: -------------------------------------------------------------------------------- 1 | import DataGridWithHeader from './DataGridWithHeader'; 2 | 3 | export {DataGridWithHeader}; -------------------------------------------------------------------------------- /client/components/FlexBetween/FlexBetween.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@mui/material"; 2 | import { styled } from "@mui/system"; 3 | 4 | export const FlexBetween = styled(Box)({ 5 | display: "flex", 6 | justifyContent: "space-between", 7 | alignItems: "center", 8 | }) -------------------------------------------------------------------------------- /client/components/FlexBetween/index.ts: -------------------------------------------------------------------------------- 1 | export {FlexBetween} from './FlexBetween'; -------------------------------------------------------------------------------- /client/components/Graph/Graph.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/faros-scope/aefb07ebb5b579eec7b12c22e1d3908723d4557c/client/components/Graph/Graph.module.css -------------------------------------------------------------------------------- /client/components/Graph/Graph.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../variables.scss'; 2 | 3 | .draggableContainer { 4 | position: relative; 5 | width: 100vw; 6 | height: 100vh; 7 | overflow: hidden; 8 | border-radius: 0px; 9 | transition: all 0.5s ease-in-out; 10 | background-size: 1.6em 1.6em; 11 | background-attachment: local; 12 | background-image: linear-gradient(rgba(0, 0, 0, 0.5) 0.1em, transparent 0.1em), 13 | linear-gradient(90deg, rgba(0, 0, 0, 0.5) 0.1em, transparent 0.1em); 14 | } 15 | 16 | .graph { 17 | position: relative; 18 | width: fit-content; 19 | height: fit-content; 20 | transition: all 0.5s ease-in-out; 21 | backdrop-filter: blur(0.2rem); 22 | border-radius: 0 0 20px 20px; 23 | animation: opacity 1s ease-in-out; 24 | padding: 1rem; 25 | background-size: 1.6em 1.6em; 26 | background-attachment: local; 27 | &.dark { 28 | // background-color: rgba(0, 0, 0, 0.2); 29 | border: 1px solid $dark-border; 30 | border-top: none; 31 | background-image: linear-gradient(rgba(255, 255, 255, 0.5) 0.1em, transparent 0.1em), 32 | linear-gradient(90deg, rgba(255, 255, 255, 0.5) 0.1em, transparent 0.1em); 33 | } 34 | 35 | &.light { 36 | // background-color: rgba(255, 255, 255, 0); 37 | border: 1px solid $light-border; 38 | border-top: none; 39 | } 40 | } 41 | 42 | .row { 43 | perspective: 1000px; 44 | } 45 | 46 | .draggableInner { 47 | transform-origin: center center; 48 | position: relative; 49 | height: fit-content; 50 | width: fit-content; 51 | transition: all 0.5s ease-in-out; 52 | scale: 1; 53 | } 54 | 55 | .draggableContent { 56 | @include flex-vertical; 57 | position: relative; 58 | min-height: 100vh; 59 | transition: all 0.5s ease-in-out; 60 | transform-origin: 0% 0%; 61 | overflow: hidden; 62 | scale: 1; 63 | } 64 | 65 | @keyframes title-hover { 66 | 0% { 67 | transform: translate(-50%, 0px); 68 | } 69 | 70 | 50% { 71 | transform: translate(-50%, 8px); 72 | } 73 | 74 | 100% { 75 | transform: translate(-50%, 0px); 76 | } 77 | } 78 | 79 | .node-graph-title-bar { 80 | @include flex-horizontal; 81 | position: relative; 82 | width: 100%; 83 | height: 4rem; 84 | border-radius: 20px 20px 0 0; 85 | color: #ededed; 86 | padding: 1rem; 87 | transition: all 0.2s ease-in-out; 88 | animation: opacity 1s ease-in-out; 89 | backdrop-filter: blur(0.2rem); 90 | font-size: 3rem; 91 | text-decoration: underline; 92 | 93 | & .node-graph-title { 94 | position: absolute; 95 | left: 50%; 96 | transform: translate(-50%, -50%); 97 | background-color: transparent; 98 | animation: title-hover 4s infinite ease-in-out; 99 | } 100 | 101 | &.dark { 102 | background-color: rgba(0, 0, 0, 0.2); 103 | color: white; 104 | border: 1px solid $dark-border; 105 | } 106 | 107 | &.light { 108 | background-color: rgba(255, 255, 255, 0); 109 | color: black; 110 | border: 1px solid $light-border; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /client/components/Graph/Graph.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@mui/material'; 2 | import { useRef } from 'react'; 3 | // import './graph.scss'; 4 | import { metricsApi } from '../../services/api'; 5 | import GraphResponsiveNetwork from './GraphNetwork'; 6 | import './Graph.module.scss'; 7 | import './graph.scss'; 8 | 9 | /** 10 | * Graph component 11 | * @returns ReactNode 12 | */ 13 | 14 | /* export */ const Graph = () => { 15 | const muiTheme = useTheme(); 16 | const { data, isSuccess } = metricsApi.endpoints.getClusterInfo.useQueryState( 17 | undefined, 18 | {}, 19 | ); 20 | 21 | console.log('Returned Data', data); 22 | const draggableContainer = useRef(null); 23 | 24 | const graphData: { 25 | nodes: { 26 | id: string; 27 | label?: string; 28 | size: number; 29 | color: string; 30 | nodeId?: string; 31 | height?: number; 32 | type: string; 33 | image?: string; 34 | }[]; 35 | links: { 36 | source: string; 37 | target?: string; 38 | distance: number; 39 | }[]; 40 | } = {} as any; 41 | 42 | if (isSuccess && data && data.nodes) { 43 | console.log('data', data); 44 | //! TEMPORARY, PASS IN REAL NAMESPACE WHEN AVAILABLE 45 | graphData.nodes = [ 46 | { 47 | id: 'Cluster', 48 | label: 'Cluster', 49 | height: 1.5, 50 | size: 40, 51 | color: 'green', 52 | type: 'cluster', 53 | }, 54 | ]; 55 | 56 | graphData.links = []; 57 | for (const namespace in data.namespaces) { 58 | graphData.nodes.push({ 59 | id: namespace, 60 | label: namespace, 61 | height: 0, 62 | size: 30, 63 | color: 'orange', 64 | type: 'namespace', 65 | }); 66 | 67 | graphData.links.push({ 68 | source: namespace, 69 | target: 'Cluster', 70 | distance: 50, 71 | }); 72 | } 73 | 74 | for (const service in data.serviceToPodsMapping) { 75 | graphData.nodes.push({ 76 | id: service, 77 | label: service, 78 | height: 0, 79 | size: 20, 80 | color: 'blue', 81 | type: 'service', 82 | }); 83 | 84 | if (service !== 'kubernetes') { 85 | data.serviceToPodsMapping[service].forEach((pod: string) => { 86 | graphData.links.push({ 87 | source: service, 88 | target: pod, 89 | distance: 125, 90 | }); 91 | }); 92 | } 93 | } 94 | 95 | data.pods.forEach((pod) => { 96 | graphData.nodes.push({ 97 | id: pod.id, 98 | nodeId: pod.nodeId, 99 | height: 0, 100 | size: 15, 101 | color: 'cyan', 102 | type: 'pod', 103 | }); 104 | 105 | graphData.links.push({ 106 | source: pod.id, 107 | target: pod.namespace, 108 | distance: 100, 109 | }); 110 | }); 111 | 112 | data.containers.forEach((container, index) => { 113 | graphData.nodes.push({ 114 | id: `${container.name}_${index}`, 115 | label: container.name, 116 | height: 0, 117 | size: 10, 118 | color: 'yellow', 119 | image: container.image, 120 | type: 'container', 121 | }); 122 | 123 | graphData.links.push({ 124 | source: `${container.name}_${index}`, 125 | target: container.podId, 126 | distance: 100, 127 | }); 128 | }); 129 | } 130 | 131 | console.log('GraphData', graphData); 132 | 133 | return ( 134 |
135 | {data && isSuccess && ( 136 | 143 | )} 144 |
145 | ); 146 | }; 147 | 148 | export default Graph; -------------------------------------------------------------------------------- /client/components/Graph/GraphNetwork.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { createPortal } from 'react-dom'; 3 | import { ResponsiveNetworkCanvas } from '@nivo/network'; 4 | import { useTheme, Snackbar, Alert } from '@mui/material'; 5 | import { 6 | TransformWrapper, 7 | TransformComponent, 8 | ReactZoomPanPinchContentRef, 9 | } from 'react-zoom-pan-pinch'; 10 | 11 | import styles from './styles.module.css'; 12 | export const Controls: React.FC = ({ 13 | zoomIn, 14 | zoomOut, 15 | resetTransform, 16 | centerView, 17 | }: ReactZoomPanPinchContentRef) => ( 18 |
19 | 26 | 33 | 40 | 47 |
48 | ); 49 | declare module '@nivo/network' { 50 | interface InputNode { 51 | id: string; 52 | label: string; 53 | size: number; 54 | color: string; 55 | height: number; 56 | type: string; 57 | } 58 | interface InputLink { 59 | source: string; 60 | target: string; 61 | distance: number; 62 | } 63 | } 64 | 65 | const GraphResponsiveNetwork = ({ 66 | data, 67 | lengthOfData, 68 | draggableContainer, 69 | }: any) => { 70 | const muiTheme = useTheme(); 71 | const containerRef = draggableContainer; 72 | const [open, setOpen] = useState(false); 73 | 74 | // Calculate the height dynamically based on the length of data 75 | const calculatedHeight = lengthOfData * 10; 76 | const calculatedWidth = lengthOfData * 10; 77 | 78 | useEffect(() => { 79 | // Scroll to the center of the container after rendering 80 | if (containerRef.current) { 81 | const containerHeight = containerRef.current.clientHeight; 82 | const scrollOffset = (calculatedHeight - containerHeight) / 2; 83 | containerRef.current.scrollTop = scrollOffset; 84 | 85 | const containerWidth = containerRef.current.clientWidth; 86 | const scrollOffsetWidth = (calculatedHeight - containerWidth) / 2; 87 | containerRef.current.scrollLeft = scrollOffsetWidth; 88 | } 89 | }, [calculatedHeight]); 90 | 91 | return ( 92 | 100 | {() => ( 101 | 107 |
115 | e.distance} 119 | centeringStrength={1} 120 | repulsivity={100} 121 | nodeSize={(n) => n.size} 122 | activeNodeSize={(n) => n.size * 2} 123 | nodeColor={(e) => e.color} 124 | nodeBorderWidth={5} 125 | nodeBorderColor={{ 126 | from: 'color', 127 | modifiers: [['darker', 0.8]], 128 | }} 129 | distanceMin={20} 130 | linkThickness={(n) => 2 + 2 * n.target.data.height} 131 | pixelRatio={2} 132 | linkColor={ 133 | (n) => 134 | n.source.data.type === 'namespace' 135 | ? '#9AFF99' // pastel green 136 | : n.source.data.type === 'service' 137 | ? '#99C2FF' // pastel blue 138 | : n.source.data.type === 'pod' 139 | ? '#FFD699' // pastel orange 140 | : n.source.data.type === 'container' 141 | ? '#99FFFF' // pastel cyan 142 | : muiTheme.palette.mode === 'dark' 143 | ? '#EEEEEE' // light gray 144 | : '#CCCCCC' // pastel gray 145 | } 146 | motionConfig="wobbly" 147 | nodeTooltip={(e) => ( 148 |
166 | ID: {e.node.data.label || e.node.id}
167 | Type: {e.node.data.type.toUpperCase()}
168 | 173 | CLICK TO COPY ID 174 | 175 |
176 | )} 177 | onClick={(n) => { 178 | console.log(n); 179 | navigator.clipboard.writeText( 180 | n.id || n.data.id || n.data.label || '', 181 | ); 182 | setOpen(true); 183 | }} 184 | /> 185 | {createPortal( 186 | setOpen(false)} 189 | autoHideDuration={5000} 190 | > 191 | 196 | Copied to clipboard! 197 | 198 | , 199 | document.body, 200 | )} 201 |
202 |
203 | )} 204 |
205 | ); 206 | }; 207 | 208 | export default GraphResponsiveNetwork; 209 | -------------------------------------------------------------------------------- /client/components/Graph/graph.scss: -------------------------------------------------------------------------------- 1 | @import '../../variables.scss'; 2 | 3 | .graph { 4 | position: relative; 5 | display: flex; 6 | width: 100%; 7 | height: 100%; 8 | transition: all 0.5s ease-in-out; 9 | backdrop-filter: blur(0.2rem); 10 | border-radius: 0 0 20px 20px; 11 | animation: opacity 1s ease-in-out; 12 | background-size: 1.5em 1.5em; 13 | background-attachment: local; 14 | overflow: hidden; 15 | padding: 0; 16 | cursor: grab; 17 | &:active { 18 | cursor: grabbing; 19 | } 20 | &.dark { 21 | background-color: rgba(0, 0, 0, 0.2); 22 | border: 1px solid $dark-border; 23 | border-top: none; 24 | background-image: linear-gradient( 25 | rgba(255, 255, 255, 0.1) 0.1em, 26 | transparent 0.1em 27 | ), 28 | linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0.1em, transparent 0.1em); 29 | } 30 | 31 | &.light { 32 | background-color: rgba(255, 255, 255, 0); 33 | border: 1px solid $light-border; 34 | border-top: none; 35 | background-image: linear-gradient( 36 | rgb(0, 0, 0, 0.4) 0.1em, 37 | transparent 0.1em 38 | ), 39 | linear-gradient(90deg, rgba(0, 0, 0, 0.4) 0.1em, transparent 0.1em); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/components/Graph/index.ts: -------------------------------------------------------------------------------- 1 | import Graph from './Graph'; 2 | 3 | export { Graph }; 4 | -------------------------------------------------------------------------------- /client/components/Graph/styles.module.css: -------------------------------------------------------------------------------- 1 | .controlPanel { 2 | position: absolute; 3 | z-index: 2; 4 | transform: translate(10px, 10px); 5 | max-width: calc(100% - 20px); 6 | } 7 | .controlBtn { 8 | padding: 6px 12px; 9 | background: white; 10 | border: 1px solid grey; 11 | border-radius: 5px; 12 | margin-right: 10px; 13 | font-size: 12px; 14 | text-transform: uppercase; 15 | font-weight: 600; 16 | cursor: pointer; 17 | } 18 | 19 | .controlBtn:focus { 20 | filter: brightness(90%); 21 | } 22 | 23 | .controlBtn:hover { 24 | filter: brightness(120%); 25 | } 26 | 27 | .controlBtn:active { 28 | opacity: 0.9; 29 | } 30 | 31 | .grid { 32 | display: grid; 33 | grid-template-columns: repeat(100, 1fr); 34 | } -------------------------------------------------------------------------------- /client/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Typography, Box, useTheme } from '@mui/material'; 2 | 3 | interface Props { 4 | title: string; 5 | subtitle: string; 6 | } 7 | 8 | export const Header = ({ title, subtitle }: Props) => { 9 | const theme = useTheme(); 10 | return ( 11 | 12 | 18 | {title} 19 | 20 | 21 | {subtitle} 22 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /client/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | export { Header} from './Header'; -------------------------------------------------------------------------------- /client/components/LineChart/LineChart.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography, useTheme } from "@mui/material"; 2 | import CircularProgress from "@mui/material/CircularProgress"; 3 | import Paper from "@mui/material/Paper"; 4 | import { ResponsiveLineCanvas } from "@nivo/line"; 5 | import { useEffect, useState } from "react"; 6 | import "./linechart.scss"; 7 | 8 | import Tooltip from "@mui/material/Tooltip"; 9 | import IconButton from "@mui/material/IconButton"; 10 | import InfoTwoToneIcon from "@mui/icons-material/InfoTwoTone"; 11 | import MoreVertTwoToneIcon from "@mui/icons-material/MoreVertTwoTone"; 12 | 13 | interface RecievedData { 14 | metric: { [key: string]: string }; 15 | values: number[][]; 16 | } 17 | 18 | interface URLObject { 19 | clusterUsage: string; 20 | nodeUsage: string; 21 | podNetwork: string; 22 | packetsTransmitted: string; 23 | packetsReceived: string; 24 | nodeUsageURL: string; 25 | receivedBandwidth: string; 26 | [key: string]: string; 27 | } 28 | 29 | const InfoTooltip = () => { 30 | return ( 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | const MoreInfoTooltip = () => { 40 | return ( 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | }; 48 | 49 | // Get the current time in seconds (Unix timestamp) 50 | const now = Math.floor(Date.now() / 1000); 51 | 52 | // Calculate the start time (10 minutes ago) 53 | const tenMinutesAgo = now - 50000 * 2; 54 | const URLObject: URLObject = { 55 | clusterUsage: `/prom-service/api/v1/query_range?query=sum by (cluster_ip) (rate(container_cpu_user_seconds_total[5m]))&start=${tenMinutesAgo}&end=${now}&step=300`, 56 | // ! by changing query from 5 to 10 minutes increase range of time of sample 57 | nodeUsage: `http://35.227.104.153:31374/api/v1/query_range?query= sum by (node) (rate(node_cpu_seconds_total{mode!="idle"}[10m])) / sum by (node) (kube_pod_container_resource_requests{resource="cpu"})&start=${tenMinutesAgo}&end=${now}&step=120`, 58 | podNetwork: `http://35.227.104.153:31374/api/v1/query_range?query= sum by (kubernetes_io_hostname) (rate(container_network_receive_bytes_total[15m]))&start=${tenMinutesAgo}&end=${now}&step=200`, 59 | packetsTransmitted: `http://35.227.104.153:31374/api/v1/query_range?query= sum by (kubernetes_io_hostname) (rate(container_network_transmit_packets_total[5m]))&start=${tenMinutesAgo}&end=${now}&step=350`, 60 | packetsReceived: `http://35.227.104.153:31374/api/v1/query_range?query= topk(5, sum by (pod) (rate(container_network_receive_packets_total[5m])))&start=${tenMinutesAgo}&end=${now}&step=350`, 61 | nodeUsageURL: `http://35.227.104.153:31374/api/v1/query_range?query= sum by (kubernetes_io_hostname) (container_memory_usage_bytes)&start=${tenMinutesAgo}&end=${now}&step=150`, 62 | receivedBandwidth: `http://35.227.104.153:31374/api/v1/query_range?query=sum by (node) (rate(node_network_receive_bytes_total[5m]))&start=${tenMinutesAgo}&end=${now}&step=150`, 63 | }; 64 | 65 | interface Props { 66 | title: string; 67 | URL: string; 68 | } 69 | 70 | const commonProperties = { 71 | // width: 900, 72 | // height: 400, 73 | margin: { top: 20, right: 20, bottom: 40, left: 60 }, 74 | pointSize: 8, 75 | pointColor: { theme: "background" }, 76 | pointBorderWidth: 2, 77 | pointBorderColor: { theme: "background" }, 78 | }; 79 | 80 | const LineChart = ({ title, URL }: Props) => { 81 | const theme = useTheme(); 82 | const [data, setData] = useState(null); 83 | 84 | useEffect(() => { 85 | (async function () { 86 | await fetch(URLObject[URL]) 87 | .then((res) => { 88 | return res.json(); 89 | }) 90 | .then(({ data }) => { 91 | const XY = data.result.map((result: RecievedData) => { 92 | const temp = result.values.map((point: number[]) => { 93 | console.log( 94 | new Intl.DateTimeFormat("en-US", { 95 | hour: "2-digit", 96 | minute: "2-digit", 97 | second: "2-digit", 98 | hour12: false, 99 | timeZone: "UTC", 100 | }).format(new Date(point[0] * 1000)) 101 | ); 102 | // console.log(new Date(point[0] * 1000))) 103 | return { 104 | x: new Date(point[0] * 1000).toISOString(), 105 | // x: new Intl.DateTimeFormat('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, timeZone:'UTC'}).format(new Date(point[0] * 1000)), 106 | y: Number(point[1]), 107 | }; 108 | }); 109 | return { 110 | data: temp, 111 | id: Object.values(result.metric)[0] || "placeholder", 112 | }; 113 | }); 114 | setData(XY); 115 | ``; 116 | }); 117 | })(); 118 | 4; 119 | }, []); 120 | 121 | console.log("The DATA after modification", data); 122 | return ( 123 | 138 | 145 | 154 | {title} 155 | 156 | 157 | 158 | 159 | 160 | {!data && } 161 | {data && ( 162 |
171 | 223 |
224 | )} 225 |
226 | ); 227 | }; 228 | 229 | // Exporting as default for React lazy loading; React.lazy() only supports default exports 230 | export default LineChart; 231 | 232 | /* 233 | 234 | 270 | 271 | 272 | ( 313 |
314 | {point.serieId}: {point.data.yFormatted} 315 |
316 | )} 317 | enableArea={false} // Disabled for performance, enable if it adds value 318 | useMesh={true} // Improve interaction performance 319 | legends={[ 320 | // Optional: add legends to improve readability 321 | { 322 | anchor: 'bottom-right', 323 | direction: 'column', 324 | justify: false, 325 | translateX: 120, 326 | translateY: 0, 327 | itemsSpacing: 2, 328 | itemDirection: 'left-to-right', 329 | itemWidth: 80, 330 | itemHeight: 20, 331 | itemOpacity: 0.75, 332 | symbolSize: 12, 333 | symbolShape: 'circle', 334 | symbolBorderColor: 'rgba(0, 0, 0, .5)', 335 | effects: [ 336 | { 337 | on: 'hover', 338 | style: { 339 | itemBackground: 'rgba(0, 0, 0, .03)', 340 | itemOpacity: 1, 341 | }, 342 | }, 343 | ], 344 | }, 345 | ]} 346 | /> */ 347 | -------------------------------------------------------------------------------- /client/components/LineChart/index.ts: -------------------------------------------------------------------------------- 1 | // export {LineChart} from './LineChart'; -------------------------------------------------------------------------------- /client/components/LineChart/linechart.scss: -------------------------------------------------------------------------------- 1 | @import '../../variables.scss'; 2 | 3 | .page { 4 | @include flex-horizontal; 5 | color: white; 6 | min-height: 30vh; 7 | min-width: 25vw; 8 | border-radius: 20px; 9 | &.dark { 10 | border: 1px solid $light-border; 11 | } 12 | &.light { 13 | border: 1px solid $dark-border; 14 | } 15 | h1 { 16 | color: white; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/components/List-View/List-View.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/faros-scope/aefb07ebb5b579eec7b12c22e1d3908723d4557c/client/components/List-View/List-View.tsx -------------------------------------------------------------------------------- /client/components/List-View/ListView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useTheme } from '@mui/material' 3 | import Box from '@mui/material/Box'; 4 | import Table from '@mui/material/Table'; 5 | import TableBody from '@mui/material/TableBody'; 6 | import TableCell from '@mui/material/TableCell'; 7 | import TableContainer from '@mui/material/TableContainer'; 8 | import TableFooter from '@mui/material/TableFooter'; 9 | import TablePagination from '@mui/material/TablePagination'; 10 | import TableRow from '@mui/material/TableRow'; 11 | import TableHead from '@mui/material/TableHead'; 12 | import IconButton from '@mui/material/IconButton'; 13 | import FirstPageIcon from '@mui/icons-material/FirstPage'; 14 | import KeyboardArrowLeft from '@mui/icons-material/KeyboardArrowLeft'; 15 | import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight'; 16 | import LastPageIcon from '@mui/icons-material/LastPage'; 17 | 18 | interface Column { 19 | id: 'name' | 'CPU Usage (%)' | 'CPU Usage' | 'Mem Usage' | 'Mem Usage (%)'; 20 | label: string; 21 | minWidth?: number; 22 | align?: 'right'; 23 | format?: (value: number) => string; 24 | } 25 | 26 | const columns: readonly Column[] = [ 27 | { id: 'name', label: 'Name', minWidth: 170 }, 28 | { 29 | id: 'CPU Usage (%)', 30 | label: 'CPU Usage (%)', 31 | align: 'right', 32 | minWidth: 100, 33 | }, 34 | { 35 | id: 'CPU Usage', 36 | label: 'CPU Usage', 37 | minWidth: 170, 38 | align: 'right', 39 | format: (value: number) => value.toLocaleString('en-US'), 40 | }, 41 | { 42 | id: 'Mem Usage (%)', 43 | label: 'Mem Usage (%)', 44 | minWidth: 170, 45 | align: 'right', 46 | format: (value: number) => value.toFixed(2), 47 | }, 48 | { 49 | id: 'Mem Usage', 50 | label: 'Mem Usage', 51 | minWidth: 170, 52 | align: 'right', 53 | format: (value: number) => value.toLocaleString('en-US'), 54 | }, 55 | ]; 56 | interface TablePaginationActionsProps { 57 | count: number; 58 | page: number; 59 | rowsPerPage: number; 60 | onPageChange: ( 61 | event: React.MouseEvent, 62 | newPage: number, 63 | ) => void; 64 | } 65 | 66 | function TablePaginationActions(props: TablePaginationActionsProps) { 67 | const theme = useTheme(); 68 | 69 | const { count, page, rowsPerPage, onPageChange } = props; 70 | 71 | const handleFirstPageButtonClick = ( 72 | event: React.MouseEvent, 73 | ) => { 74 | onPageChange(event, 0); 75 | }; 76 | 77 | const handleBackButtonClick = ( 78 | event: React.MouseEvent, 79 | ) => { 80 | onPageChange(event, page - 1); 81 | }; 82 | 83 | const handleNextButtonClick = ( 84 | event: React.MouseEvent, 85 | ) => { 86 | onPageChange(event, page + 1); 87 | }; 88 | 89 | const handleLastPageButtonClick = ( 90 | event: React.MouseEvent, 91 | ) => { 92 | onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); 93 | }; 94 | 95 | return ( 96 | 97 | 102 | {theme.direction === 'rtl' ? : } 103 | 104 | 109 | {theme.direction === 'rtl' ? ( 110 | 111 | ) : ( 112 | 113 | )} 114 | 115 | = Math.ceil(count / rowsPerPage) - 1} 118 | aria-label="next page" 119 | > 120 | {theme.direction === 'rtl' ? ( 121 | 122 | ) : ( 123 | 124 | )} 125 | 126 | = Math.ceil(count / rowsPerPage) - 1} 129 | aria-label="last page" 130 | > 131 | {theme.direction === 'rtl' ? : } 132 | 133 | 134 | ); 135 | } 136 | 137 | //*** Data Types ***/ 138 | type MetricsItem = { 139 | name: string; 140 | cpuUsage: number; 141 | cpuUsagePct: number; 142 | memUsage: number; 143 | memUsagePct: number; 144 | type: string; 145 | }; 146 | 147 | type ListViewProps = { 148 | metricsObject: MetricsItem[]; 149 | }; 150 | 151 | const ListView = ({ metricsObject }: ListViewProps) => { 152 | const [page, setPage] = React.useState(0); 153 | const [rowsPerPage, setRowsPerPage] = React.useState(5); 154 | const dataObj = metricsObject; 155 | 156 | // Avoid a layout jump when reaching the last page with empty rows. 157 | const emptyRows = 158 | page > 0 ? Math.max(0, (1 + page) * rowsPerPage - dataObj.length) : 0; 159 | 160 | const handleChangePage = ( 161 | _event: React.MouseEvent | null, 162 | newPage: number, 163 | ) => { 164 | setPage(newPage); 165 | }; 166 | 167 | const handleChangeRowsPerPage = ( 168 | event: React.ChangeEvent, 169 | ) => { 170 | setRowsPerPage(parseInt(event.target.value, 10)); 171 | setPage(0); 172 | }; 173 | 174 | return ( 175 |
176 | {dataObj ? ( 177 | 178 | 182 | 183 | 184 | 185 | {dataObj[0]?.type[0] 186 | .toUpperCase() 187 | .concat(dataObj[0].type.slice(1))} 188 | 189 | 190 | 191 | {columns.map((column) => ( 192 | 197 | {column.label} 198 | 199 | ))} 200 | 201 | 202 | 203 | {(rowsPerPage > 0 204 | ? dataObj.slice( 205 | page * rowsPerPage, 206 | page * rowsPerPage + rowsPerPage, 207 | ) 208 | : dataObj 209 | )?.map((row) => ( 210 | 211 | 212 | {row.name} 213 | 214 | 215 | {`${row.cpuUsagePct?.toFixed(2)}%`} 216 | 217 | 218 | {`${row.cpuUsage}`} 219 | 220 | 221 | {`${row.memUsagePct?.toFixed(2)}%`} 222 | 223 | 224 | {`${row.memUsage}`} 225 | 226 | 227 | ))} 228 | {emptyRows > 0 && ( 229 | 230 | 231 | 232 | )} 233 | 234 | 235 | 236 | 252 | 253 | 254 |
255 |
256 | ) : ( 257 |

Loading...

258 | )} 259 |
260 | ); 261 | }; 262 | 263 | export default ListView; -------------------------------------------------------------------------------- /client/components/List-View/ListViewTable.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import { DataGrid, GridColDef } from '@mui/x-data-grid'; 3 | import { ListViewHeader } from '../ListViewHeader'; 4 | import Paper from '@mui/material/Paper'; 5 | 6 | // Types 7 | type MetricsItem = { 8 | name: string; 9 | cpuUsage: number; 10 | cpuUsagePct: number; 11 | memUsage: number; 12 | memUsagePct: number; 13 | type: string; 14 | id?: number; 15 | }; 16 | 17 | type ListViewProps = { 18 | metricsObject: MetricsItem[]; 19 | }; 20 | 21 | const columns: GridColDef[] = [ 22 | { field: 'id', headerName: 'ID', width: 90 }, 23 | { 24 | field: 'name', 25 | headerName: 'Name', 26 | width: 160, 27 | editable: true, 28 | }, 29 | { 30 | field: 'cpuUsage', 31 | headerName: 'Cpu Usage', 32 | width: 130, 33 | editable: true, 34 | }, 35 | { 36 | field: 'cpuUsagePct', 37 | headerName: 'CPU Usage (%)', 38 | type: 'number', 39 | width: 130, 40 | editable: true, 41 | valueGetter: (params) => { 42 | return params.value.toFixed(2); 43 | }, 44 | }, 45 | { 46 | field: 'memUsage', 47 | headerName: 'memUsage', 48 | type: 'number', 49 | width: 130, 50 | editable: true, 51 | }, 52 | { 53 | field: 'memUsagePct', 54 | headerName: 'Mem Usage (%)', 55 | type: 'number', 56 | width: 130, 57 | editable: true, 58 | valueGetter: (params) => { 59 | return params.value.toFixed(2); 60 | }, 61 | }, 62 | ]; 63 | 64 | export const ListViewTable = ({ metricsObject }: ListViewProps) => { 65 | const metricsData = metricsObject; 66 | 67 | if (metricsData) { 68 | metricsData.map((elm, idx) => { 69 | return (elm.id = idx); 70 | }); 71 | } 72 | 73 | 74 | if (!metricsData) return

Loading...

; 75 | 76 | return ( 77 | 78 | 79 | 80 | {/* */} 90 | 91 | 117 | {/* */} 118 | 119 | 120 | ); 121 | }; 122 | -------------------------------------------------------------------------------- /client/components/List-View/index.ts: -------------------------------------------------------------------------------- 1 | import ListView from './ListView'; 2 | 3 | export { ListView }; -------------------------------------------------------------------------------- /client/components/ListViewHeader/ListViewHeader.tsx: -------------------------------------------------------------------------------- 1 | import { Typography, Box, useTheme } from '@mui/material'; 2 | 3 | interface Props { 4 | title: string; 5 | } 6 | 7 | export const ListViewHeader = ({ title }: Props) => { 8 | const theme = useTheme(); 9 | const muiTheme = useTheme(); 10 | return ( 11 | 12 | 22 | {title} 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /client/components/ListViewHeader/index.ts: -------------------------------------------------------------------------------- 1 | export { ListViewHeader } from './ListViewHeader'; 2 | -------------------------------------------------------------------------------- /client/components/NameSpaceTable/NameSpaceTable.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import Paper from '@mui/material/Paper'; 3 | import { DataGrid, GridColDef } from '@mui/x-data-grid'; 4 | import { ListViewHeader } from '../ListViewHeader'; 5 | 6 | 7 | 8 | const columns: GridColDef[] = [ 9 | { field: 'id', headerName: 'ID', width: 50 }, 10 | { 11 | field: 'name', 12 | headerName: 'Name', 13 | width: 160, 14 | flex: 1, 15 | editable: true, 16 | }, 17 | { 18 | field: 'CPU', 19 | headerName: 'CPU (%)', 20 | width: 130, 21 | flex: 1, 22 | editable: true, 23 | valueGetter: (params) => { 24 | return params.value.toFixed(5); 25 | }, 26 | }, 27 | { 28 | field: 'MEM', 29 | headerName: 'MEM (bytes)', 30 | type: 'number', 31 | width: 130, 32 | flex: 1, 33 | editable: true, 34 | // valueGetter: (params) => { 35 | // return params.value.toFixed(2); 36 | // }, 37 | }, 38 | ]; 39 | //export const ListViewTable = ({ metricsObject }: ListViewProps) => { 40 | export const NameSpaceTable = ({ cUsageMetrics }: any) => { 41 | // console.log('INSIDE NAMESPACE', cUsageMetrics); 42 | if (!cUsageMetrics) return

Loading...

; 43 | 44 | return ( 45 | 46 | 47 | 48 | {/* */} 58 | 59 | 85 | {/* */} 86 | 87 | 88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /client/components/NameSpaceTable/index.ts: -------------------------------------------------------------------------------- 1 | export { NameSpaceTable } from './NameSpaceTable'; 2 | -------------------------------------------------------------------------------- /client/components/NavBar/NavBar.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/faros-scope/aefb07ebb5b579eec7b12c22e1d3908723d4557c/client/components/NavBar/NavBar.module.css -------------------------------------------------------------------------------- /client/components/NavBar/NavBar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AppBar, 3 | Box, 4 | Toolbar, 5 | Typography, 6 | IconButton, 7 | Drawer, 8 | Container, 9 | } from '@mui/material'; 10 | import MenuIcon from '@mui/icons-material/Menu'; 11 | import { SwitchButton } from '../SwitchButton'; 12 | import { useTheme } from '@mui/material'; 13 | import { useContext, useState } from 'react'; 14 | import { useLocation } from 'react-router-dom'; 15 | import { ColorModeContext } from '../../theme'; 16 | import NavBarLink from './NavBarLink'; 17 | import logo from '../../assets/faros.png'; 18 | import './navbar.scss'; 19 | 20 | export const NavBar = () => { 21 | const colorMode = useContext(ColorModeContext); 22 | const changeTheme = () => colorMode.toggleColorMode(); 23 | const [sidebarShow, setSidebarShow] = useState(false); 24 | const location = useLocation(); 25 | const currLocation = location.pathname; 26 | const theme = useTheme(); 27 | 28 | const toggleSidebar = () => { 29 | console.log('toggleSidebar'); 30 | setSidebarShow(!sidebarShow); 31 | }; 32 | 33 | return ( 34 | <> 35 | 36 | 45 | 58 | 59 | 60 | 61 | logo 62 | Faros-Scope 63 | 64 | 65 | 66 | 67 | 86 | 103 | 104 | 105 | 126 | 134 | logo 142 | 143 | 148 | Dashboard 149 | 150 | 155 | List-View 156 | 157 | 162 | Node-View 163 | 164 | 165 | 166 | 167 | ); 168 | }; 169 | -------------------------------------------------------------------------------- /client/components/NavBar/NavBarLink.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from '@mui/material'; 2 | import { Link } from 'react-router-dom'; 3 | import { useTheme } from '@mui/material'; 4 | 5 | interface NavBarLink { 6 | to: string; 7 | currLocation: string; 8 | toggleSidebar: () => void; 9 | children: React.ReactNode; 10 | } 11 | 12 | const NavBarLink = ({ to, currLocation, toggleSidebar, children }: NavBarLink) => { 13 | const theme = useTheme(); 14 | console.log('NavBarLink', currLocation, to); 15 | return ( 16 | 27 | 39 | {children} 40 | 41 | 42 | ); 43 | } 44 | 45 | export default NavBarLink; 46 | -------------------------------------------------------------------------------- /client/components/NavBar/index.ts: -------------------------------------------------------------------------------- 1 | export { NavBar } from './NavBar' -------------------------------------------------------------------------------- /client/components/NavBar/navbar.scss: -------------------------------------------------------------------------------- 1 | #navbar { 2 | height: fit-content; 3 | background-color: transparent; 4 | border-bottom: 1px solid #e9ecef; 5 | backdrop-filter: blur(10px); 6 | z-index: 999; 7 | width: 100vw; 8 | } 9 | 10 | #toolbar { 11 | background-color: #20232a6f; 12 | height: 4rem; 13 | width: 100vw; 14 | } 15 | 16 | .navbar-link { 17 | color: white; 18 | font-size: 1.2rem; 19 | margin: 0 1rem; 20 | padding: 0.5rem 1rem; 21 | border-radius: 0.5rem; 22 | transition: all 0.3s; 23 | text-decoration: none; 24 | margin: 0.2rem 0; 25 | // &:hover { 26 | // background-color: #343a40; 27 | // } 28 | } 29 | -------------------------------------------------------------------------------- /client/components/StatBox/StatBox.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Typography, useTheme } from '@mui/material'; 2 | import { FlexBetween } from '../index'; 3 | 4 | interface Props { 5 | title: string; 6 | value?: string; 7 | } 8 | 9 | export const StatBox = ({ title, value = '-' }: Props) => { 10 | const theme = useTheme(); 11 | console.log(theme); 12 | return ( 13 | 27 | 28 | 29 | {title} 30 | 31 | 32 | 37 | {value} 38 | 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /client/components/StatBox/index.ts: -------------------------------------------------------------------------------- 1 | export {StatBox} from './StatBox'; -------------------------------------------------------------------------------- /client/components/SwitchButton/SwitchButton.module.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/faros-scope/aefb07ebb5b579eec7b12c22e1d3908723d4557c/client/components/SwitchButton/SwitchButton.module.css -------------------------------------------------------------------------------- /client/components/SwitchButton/SwitchButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../variables.scss'; 2 | 3 | .switchContainer { 4 | position: relative; 5 | margin: 0.1em; 6 | border-radius: 20px; 7 | background-color: rgba(0, 0, 0, 0.2); 8 | padding: 0.2em; 9 | } 10 | 11 | .switchContainer::after { 12 | content: ''; 13 | position: absolute; 14 | width: 1px; 15 | height: 100%; 16 | background-color: rgba(255, 255, 255, 0.05); 17 | left: 50%; 18 | top: 0; 19 | } 20 | 21 | .switchButton { 22 | position: relative; 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | } 27 | 28 | .check-box { 29 | position: relative; 30 | display: flex; 31 | align-items: center; 32 | justify-content: center; 33 | } 34 | 35 | .brightnessMode { 36 | position: absolute; 37 | appearance: none; 38 | width: 100%; 39 | height: 100%; 40 | border-radius: 50%; 41 | cursor: pointer; 42 | transition: 0.4s; 43 | &:checked + .sunIcon, &:checked + .moonIcon { 44 | background-color: lighten($base-blue, 20%); 45 | border-radius: 50%; 46 | padding: 0.1em; 47 | margin: 0.05em; 48 | } 49 | } 50 | 51 | .sunIcon { 52 | width: 1.5em; 53 | height: 1.5em; 54 | border-radius: 50%; 55 | transition: 0.2s ease-in-out; 56 | pointer-events: none; 57 | } 58 | 59 | .moonIcon { 60 | width: 1.5em; 61 | height: 1.5em; 62 | border-radius: 50%; 63 | transition: 0.2s ease-in-out; 64 | pointer-events: none; 65 | } 66 | 67 | .modeLabel { 68 | @include flex-vertical; 69 | position: relative; 70 | margin: 0 0.5em; 71 | } 72 | 73 | .slider { 74 | position: absolute; 75 | width: 2em; 76 | height: 2em; 77 | top: -0.3em; 78 | left: -0.5em; 79 | background: transparent; 80 | transition: 0.4s ease-in-out; 81 | pointer-events: none; 82 | } 83 | 84 | .switchBar[type='checkbox']:checked + .slider { 85 | left: 50%; 86 | width: 2em; 87 | height: 2em; 88 | top: -0.3em; 89 | } 90 | 91 | .themeIcon { 92 | position: absolute; 93 | width: 100%; 94 | height: 100%; 95 | transition: 0.2s ease-in-out; 96 | pointer-events: none; 97 | } 98 | -------------------------------------------------------------------------------- /client/components/SwitchButton/SwitchButton.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@mui/material'; 2 | import { FaSun, FaMoon } from 'react-icons/fa'; 3 | import './SwitchButton.scss'; 4 | 5 | interface SwitchButtonProps { 6 | onChange: React.ChangeEventHandler; 7 | } 8 | 9 | /** 10 | * A basic switch button that uses the current theme for styling, and toggles the current global theme between light and dark modes 11 | * @param checked 12 | * @param onChange 13 | * @returns ReactNode 14 | */ 15 | export const SwitchButton = ({ onChange }: SwitchButtonProps) => { 16 | const muiTheme = useTheme(); 17 | return ( 18 |
19 |
20 | 31 | 32 | 43 |
44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /client/components/SwitchButton/index.ts: -------------------------------------------------------------------------------- 1 | export { SwitchButton } from './SwitchButton'; 2 | -------------------------------------------------------------------------------- /client/components/ThemeContainer/ThemeContainer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { useTheme } from '@mui/material'; 3 | 4 | /** 5 | * A basic container that uses the current theme for styling, and wraps its children in a centered div 6 | * @param children 7 | * @returns ReactNode 8 | */ 9 | 10 | export const ThemeContainer = ({ 11 | children, 12 | IDOverride, 13 | style, 14 | }: { 15 | children: ReactNode; 16 | IDOverride?: string; 17 | style?: any; 18 | }) => { 19 | const muiTheme = useTheme(); 20 | return ( 21 |
26 | {children} 27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /client/components/ThemeContainer/index.ts: -------------------------------------------------------------------------------- 1 | export { ThemeContainer } from './ThemeContainer'; 2 | -------------------------------------------------------------------------------- /client/components/index.ts: -------------------------------------------------------------------------------- 1 | export { ThemeContainer } from "./ThemeContainer"; 2 | export { SwitchButton } from "./SwitchButton"; 3 | export { Header } from "./Header"; 4 | export { FlexBetween } from "./FlexBetween"; 5 | export { StatBox } from "./StatBox"; 6 | export { CollapsiblePanel } from "./CollapsiblePanel"; 7 | export { Graph } from "./Graph"; 8 | export { AppContent } from "./AppContent"; 9 | export { DataGridWithHeader } from "./DataGridWithHeader"; 10 | export { NavBar } from "./NavBar"; -------------------------------------------------------------------------------- /client/css/ColoredText.scss: -------------------------------------------------------------------------------- 1 | @import 'variables.scss'; 2 | @import 'base.scss'; -------------------------------------------------------------------------------- /client/css/Landing.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'base'; 3 | 4 | .landingMain { 5 | position: relative; 6 | display: grid; 7 | margin: 0; 8 | padding: 0; 9 | width: 100%; 10 | height: 100%; 11 | background-size: 2.2em 2.2em; 12 | background-image: 13 | // horizontal lines 14 | linear-gradient(rgba($graph-blue, 1) 0.1em, transparent 0.2em), 15 | // vertical lines 16 | linear-gradient(90deg, rgba($graph-blue, 1) 0.1em, transparent 0.2em), 17 | // horizontal glow bottom side 18 | linear-gradient(rgba($graph-blue, 0.5) 0.1em, transparent 0.5em), 19 | // horizontal glow top side 20 | linear-gradient(360deg, rgba($graph-blue, 0.5) 0.1em, transparent 0.5em), 21 | // vertical glow left side 22 | linear-gradient(270deg, rgba($graph-blue, 0.5) 0.1em, transparent 0.5em), 23 | // vertical glow right side 24 | linear-gradient(90deg, rgba($graph-blue, 0.5) 0.1em, transparent 0.5em); 25 | background-repeat: repeat; 26 | animation: move-gradient-landing 25s linear infinite; 27 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); 28 | } 29 | 30 | .landingContent { 31 | @include flex-vertical; 32 | position: relative; 33 | width: 100%; 34 | height: 100%; 35 | } 36 | 37 | .landingNav { 38 | position: absolute; 39 | top: 2em; 40 | left: 0; 41 | right: 0; 42 | margin: auto; 43 | width: 45%; 44 | padding: 1em; 45 | height: 5.5em; 46 | display: flex; 47 | justify-content: space-between; 48 | align-items: center; 49 | z-index: 1; 50 | background-color: $base-blue; 51 | border-radius: 8px; 52 | border: 1px solid white; 53 | box-shadow: $pronounced-shadow; 54 | transition: all 0.5s ease-in-out; 55 | animation: opacity 1s ease-in-out; 56 | animation-fill-mode: backwards; 57 | animation-delay: 0.5s; 58 | &:hover { 59 | box-shadow: 15px 15px 1px 0 rgb(0, 0, 0); 60 | scale: 1.05; 61 | } 62 | } 63 | 64 | .landingBody { 65 | display: flex; 66 | flex-direction: column; 67 | justify-content: center; 68 | align-items: center; 69 | text-align: center; 70 | border-radius: 5px; 71 | padding: 2em; 72 | width: 100%; 73 | height: 100%; 74 | } 75 | 76 | .landingBodyContent { 77 | @include flex-vertical; 78 | background-image: linear-gradient( 79 | 25deg, 80 | rgba(0, 0, 0, 0.05), 81 | rgba(0, 0, 0, 0) 82 | ); 83 | backdrop-filter: blur(4px); 84 | color: white; 85 | position: relative; 86 | border: 1px solid rgb(255, 255, 255); 87 | border-radius: 5px; 88 | box-shadow: $pronounced-shadow; 89 | width: 100%; 90 | height: 100%; 91 | } 92 | 93 | .break-full-width { 94 | position: absolute; 95 | top: 15em; 96 | width: 100%; 97 | height: 0; 98 | border-top: 1px solid white; 99 | margin: 1em 0; 100 | } 101 | 102 | .landingButton { 103 | position: relative; 104 | display: flex; 105 | justify-content: center; 106 | align-items: center; 107 | width: 10em; 108 | height: 3em; 109 | padding: 0.5em; 110 | border-radius: 5px; 111 | border: 1px solid white; 112 | background-color: lighten($base-blue, 15%); 113 | color: white; 114 | font-size: 1.2em; 115 | font-weight: bold; 116 | transition: all 0.5s ease-in-out; 117 | text-decoration: none; 118 | animation: opacity 1s ease-in-out; 119 | animation-fill-mode: backwards; 120 | animation-delay: 0.5s; 121 | box-shadow: $pronounced-shadow; 122 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 1); 123 | &:hover { 124 | background-color: $accent-blue; 125 | box-shadow: 10px 10px 1px 0 rgb(0, 0, 0); 126 | scale: 1.05; 127 | cursor: pointer; 128 | } 129 | } 130 | 131 | .landingHeader { 132 | background-color: rgba(15, 15, 15, 0.5); 133 | border-radius: 5px; 134 | padding: 0.5em; 135 | border: 1px solid rgba(255, 255, 255, 0.55); 136 | box-shadow: $pronounced-shadow; 137 | transition: all 0.5s ease-in-out; 138 | margin: 2em; 139 | &:hover { 140 | scale: 1.05; 141 | box-shadow: 15px 15px 1px 0 rgb(0, 0, 0); 142 | } 143 | } 144 | 145 | @keyframes move-gradient-landing { 146 | 0% { 147 | background-position: 5% 5%; 148 | } 149 | 100% { 150 | background-position: 100% 100%; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /client/css/NodeModal.scss: -------------------------------------------------------------------------------- 1 | @import 'Variables'; 2 | @import 'Base'; 3 | 4 | $border-width: 25px; 5 | $border-height: 25px; 6 | 7 | .nodeModalOverlay { 8 | position: absolute; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | top: 0; 13 | left: 0; 14 | width: 100%; 15 | height: 100%; 16 | background-color: rgba(0, 0, 0, 0.5); 17 | z-index: 999; 18 | pointer-events: all; 19 | animation-fill-mode: forwards; 20 | animation-duration: 1s; 21 | } 22 | 23 | .nodeClickableOverlay { 24 | position: absolute; 25 | width: 100%; 26 | height: 100%; 27 | border-radius: 0; 28 | pointer-events: all; 29 | animation-fill-mode: forwards; 30 | } 31 | 32 | #nodeModalThemedContainer { 33 | border-radius: 8px; 34 | } 35 | 36 | .nodeModalContainer { 37 | position: relative; 38 | height: 100%; 39 | width: 100%; 40 | } 41 | 42 | .nodeModalPodsViewOuter { 43 | position: relative; 44 | width: 100%; 45 | height: 100%; 46 | border-radius: 2px; 47 | animation: expand-window 1.5s ease-in-out; 48 | box-shadow: -20px -20px 1px 0px black; 49 | overflow: visible; 50 | &.dark { 51 | border: 25px solid #2d2f30; 52 | } 53 | &.light { 54 | border: 25px solid #cfd8dc; 55 | } 56 | } 57 | 58 | .CloseModalAnimation { 59 | animation: close-window 1.5s ease-in-out; 60 | } 61 | 62 | .nodeModalPodsViewInner { 63 | position: relative; 64 | width: 100%; 65 | height: 100%; 66 | border-radius: 8px; 67 | &.dark { 68 | box-shadow: 0px 0px 15px 1px rgb(255, 255, 255); 69 | } 70 | &.light { 71 | box-shadow: 0px 0px 8px 1px rgb(0, 0, 0); 72 | } 73 | } 74 | 75 | .close { 76 | position: absolute; 77 | right: -23px; 78 | top: -23.5px; 79 | width: fit-content; 80 | height: fit-content; 81 | } 82 | 83 | .closeButton { 84 | display: grid; 85 | align-items: center; 86 | justify-items: center; 87 | border-radius: 50%; 88 | width: 25px; 89 | height: 25px; 90 | background-color: red; 91 | border: none; 92 | pointer-events: all; 93 | color: white; 94 | } 95 | 96 | .closeButton:hover { 97 | width: $border-width; 98 | height: $border-width; 99 | background-color: #eb4034; 100 | border: none; 101 | } 102 | 103 | // maybe scale the window instead of width and height 104 | @keyframes expand-window { 105 | 0% { 106 | width: 0%; 107 | height: 0%; 108 | opacity: 0; 109 | box-shadow: 0px 0px 0px 0px black; 110 | } 111 | 112 | 50% { 113 | opacity: 0.75; 114 | } 115 | 116 | 100% { 117 | opacity: 1; 118 | width: 100%; 119 | height: 100%; 120 | box-shadow: -20px -20px 1px 0px black; 121 | } 122 | } 123 | 124 | @keyframes close-window { 125 | 0% { 126 | width: 100%; 127 | height: 100%; 128 | box-shadow: -15px -15px 1px 0px black; 129 | } 130 | 131 | 50% { 132 | opacity: 0.75; 133 | } 134 | 135 | 100% { 136 | opacity: 0; 137 | width: 15%; 138 | height: 10%; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /client/css/NotifDisplay.scss: -------------------------------------------------------------------------------- 1 | @import 'Variables.scss'; 2 | @import 'Base.scss'; 3 | 4 | .notificationContainer { 5 | display: flex; 6 | flex-direction: column-reverse; 7 | align-items: center; 8 | position: absolute; 9 | padding: 1em; 10 | top: 2ch; 11 | left: 6ch; 12 | gap: 1em; 13 | background-color: $background-color-dark; 14 | background-image: linear-gradient( 15 | #1a1a1a, 16 | rgba(255, 255, 255, 0.165) 0em, 17 | transparent 8em 18 | ); 19 | border: 1px solid $diamond-blue; 20 | box-shadow: 0px 0px 5px 1.5px #000000; 21 | border-radius: 8px; 22 | min-width: 15em; 23 | max-width: 25em; 24 | min-height: fit-content; 25 | max-height: 15em; 26 | z-index: 30; 27 | overflow: scroll; 28 | } 29 | 30 | .notificationMessage { 31 | display: flex; 32 | flex-direction: row; 33 | align-items: center; 34 | justify-content: center; 35 | position: relative; 36 | min-width: 18em; 37 | min-height: 3em; 38 | gap: 2em; 39 | z-index: 31; 40 | background-color: black; 41 | border: 1px solid $diamond-blue; 42 | border-radius: 8px; 43 | color: white; 44 | } 45 | 46 | .notificationTitle{ 47 | align-self: center; 48 | color: white; 49 | text-shadow: 0px 0px 5px #000000; 50 | } 51 | 52 | .logText{ 53 | color: $base-orange; 54 | } -------------------------------------------------------------------------------- /client/css/Pod.scss: -------------------------------------------------------------------------------- 1 | @import 'Variables'; 2 | @import 'Base'; 3 | @mixin podContainer { 4 | color: rgb(255, 255, 255); 5 | text-shadow: 2px 2px 1px rgba(20, 20, 20, 0.5); 6 | border-bottom: 1px solid rgb(255, 255, 255); 7 | } 8 | 9 | .podsView { 10 | position: relative; 11 | width: 100%; 12 | height: 100%; 13 | border-radius: 8px; 14 | animation-fill-mode: forwards; 15 | animation-duration: 1s; 16 | background-image: radial-gradient(rgba($graph-blue, 1) 2px, transparent 3px), 17 | radial-gradient(rgba($graph-blue, 0.5) 15%, transparent 60%); 18 | background-size: 1em 1em; 19 | padding: 1rem; 20 | &.light { 21 | background-image: radial-gradient( 22 | rgba($graph-blue, 1) 2px, 23 | transparent 3px 24 | ); 25 | } 26 | } 27 | 28 | .podsViewInner { 29 | display: flex; 30 | flex-direction: column; 31 | position: relative; 32 | width: 100%; 33 | height: 100%; 34 | animation: opacity 1s ease-in-out; 35 | animation-fill-mode: backwards; 36 | animation-delay: 1.5s; 37 | opacity: 1; 38 | backdrop-filter: blur(5px); 39 | border-radius: 8px; 40 | overflow: auto; 41 | padding: 1.5rem 0; 42 | &.dark { 43 | background-color: rgba(0, 0, 0, 0.03); 44 | border: 1px solid rgb(255, 255, 255); 45 | box-shadow: 0px 0px 15px 0px $diamond-blue; 46 | } 47 | &.light { 48 | background-color: rgba(255, 255, 255, 0); 49 | border: 1px solid rgb(255, 255, 255); 50 | box-shadow: none; 51 | } 52 | } 53 | 54 | .containerBuckets { 55 | @include podContainer; 56 | display: flex; 57 | flex-direction: column; 58 | width: 100%; 59 | height: fit-content; 60 | border: none; 61 | box-shadow: 0px 4px 7.5px -3px rgba(20, 20, 20, 0.5); 62 | } 63 | 64 | .containerBucketOuter { 65 | padding: 0.5rem; 66 | overflow-x: auto; 67 | border-top: none; 68 | border-bottom: 1px solid rgb(255, 255, 255); 69 | } 70 | 71 | .containerImage { 72 | white-space: nowrap; 73 | } 74 | 75 | .containersLabel { 76 | font-size: 1.4em; 77 | font-weight: 200; 78 | } 79 | 80 | .containerBox { 81 | // padding: .7em; 82 | width: 100%; 83 | height: fit-content; 84 | border: 0.4em solid rgba(255, 255, 255, 0.162); 85 | border-radius: 2px; 86 | margin-bottom: 1em; 87 | word-break: break-all; 88 | .containerTitle { 89 | padding-bottom: 1.6em; 90 | margin-bottom: 1em; 91 | font-size: 1.5em; 92 | } 93 | .infoDiv { 94 | margin-top: 0.5em; 95 | } 96 | .imageBox { 97 | height: 4em; 98 | overflow: auto; 99 | } 100 | } 101 | 102 | .podContainer { 103 | display: flex; 104 | flex-direction: column; 105 | position: relative; 106 | width: min-content; 107 | height: min-content; 108 | transition: all 0.5s ease-in-out; 109 | background: $healthyGradient; 110 | background-size: 400% 400%; 111 | animation: expand-card-deck 1s ease-in-out, wave 2s ease-in-out, 112 | move-gradient 4s ease-in-out infinite; 113 | 114 | background-repeat: repeat; 115 | animation-fill-mode: backwards; 116 | border-radius: 10px; 117 | margin: 0.5rem; 118 | background-color: rgba(31, 31, 31, 0.55); 119 | &.healthyPod { 120 | background: $healthyGradient; 121 | background-size: 400% 400%; 122 | animation: expand-card-deck 1s ease-in-out, wave 2s ease-in-out, 123 | move-gradient 4s ease-in-out infinite; 124 | 125 | background-repeat: repeat; 126 | animation-fill-mode: backwards; 127 | } 128 | 129 | &.warningPod { 130 | background: $warningGradient; 131 | background-size: 400% 400%; 132 | animation: expand-card-deck 1s ease-in-out, wave 2s ease-in-out, 133 | move-gradient 4s ease-in-out infinite; 134 | 135 | background-repeat: repeat; 136 | animation-fill-mode: backwards; 137 | } 138 | 139 | &.unhealthyPod { 140 | background: $unhealthyGradient; 141 | background-size: 400% 400%; 142 | animation: expand-card-deck 1s ease-in-out, wave 2s ease-in-out, 143 | move-gradient 4s ease-in-out infinite; 144 | 145 | background-repeat: repeat; 146 | animation-fill-mode: backwards; 147 | } 148 | } 149 | 150 | .podsContainer { 151 | text-align: center; 152 | width: fit-content; 153 | height: fit-content; 154 | border: none; 155 | padding: 0 1.5rem; 156 | hr { 157 | height: 100%; 158 | width: 100%; 159 | } 160 | } 161 | 162 | .pod { 163 | display: flex; 164 | flex-direction: column; 165 | position: relative; 166 | width: 18rem; 167 | height: 20rem; 168 | margin: 0.5rem; 169 | gap: 0.1em; 170 | border: 1.5px solid rgb(255, 255, 255); 171 | border-radius: 4px; 172 | background-color: $background-color-dark; 173 | background-image: linear-gradient(#1f1f1f, #ffffff14); 174 | transition: all 0.5s ease-in-out; 175 | overflow: auto; 176 | &:hover { 177 | transform: scale(1.1); 178 | z-index: 999; 179 | } 180 | } 181 | 182 | .podName { 183 | display: flex; 184 | align-items: center; 185 | overflow-x: auto; 186 | overflow-y: hidden; 187 | flex-shrink: 0; 188 | width: 100%; 189 | height: fit-content; 190 | padding: 0.5rem; 191 | font-size: 1.5rem; 192 | font-weight: 600; 193 | box-shadow: 0px 1px 7.5px 0px rgba(20, 20, 20, 0.5); 194 | text-shadow: 2px 2px 1px rgba(0, 0, 0, 0.5); 195 | white-space: nowrap; 196 | border-bottom: 1px solid rgb(255, 255, 255); 197 | } 198 | 199 | .podMetrics { 200 | @include podContainer; 201 | width: 100%; 202 | height: fit-content; 203 | padding: 0.5rem; 204 | font-size: 1rem; 205 | font-weight: 600; 206 | color: rgb(255, 255, 255); 207 | border-bottom: 1px solid rgb(255, 255, 255); 208 | box-shadow: 0px 4px 7.5px -3px rgba(20, 20, 20, 0.5); 209 | } 210 | 211 | .podRow { 212 | display: flex; 213 | width: fit-content; 214 | height: fit-content; 215 | // margin: 0 1.5rem; 216 | } 217 | 218 | .podsViewPolkaOverlay { 219 | position: absolute; 220 | width: 100%; 221 | height: 100%; 222 | // background-image: radial-gradient(rgba($graph-blue, 0.5) 15%, transparent 60%); 223 | background-size: 1em 1em; 224 | border-radius: 0; 225 | } 226 | 227 | .podsViewOverlay { 228 | position: absolute; 229 | width: 100%; 230 | height: 100%; 231 | background-color: rgba(0, 0, 0, 0.5); 232 | z-index: 1; 233 | border-radius: 0; 234 | pointer-events: all; 235 | } 236 | 237 | @keyframes wave { 238 | 0% { 239 | transform: translateY(0); 240 | } 241 | 242 | 50% { 243 | transform: translateY(1rem); 244 | } 245 | 246 | 100% { 247 | transform: translateY(0); 248 | } 249 | } 250 | 251 | @keyframes expand-card-deck { 252 | 0% { 253 | margin: -9.5rem; 254 | } 255 | 256 | 100% { 257 | margin: 0.5rem; 258 | } 259 | } 260 | 261 | @keyframes move-gradient { 262 | 0% { 263 | background-position: 0% 50%; 264 | } 265 | 50% { 266 | background-position: 100% 50%; 267 | } 268 | 100% { 269 | background-position: 0% 50%; 270 | } 271 | } 272 | 273 | // @import 'Variables'; 274 | // @import 'Base'; 275 | // @mixin podContainer { 276 | // color: rgb(255, 255, 255); 277 | // text-shadow: 2px 2px 1px rgba(20, 20, 20, 0.5); 278 | // border-bottom: 1px solid rgb(255, 255, 255); 279 | // } 280 | 281 | // .podsView { 282 | // position: relative; 283 | // width: 100%; 284 | // height: 100%; 285 | // border-radius: 0; 286 | // animation-fill-mode: forwards; 287 | // animation-duration: 1s; 288 | // background-image: radial-gradient(rgba($graph-blue, 1) 2px, transparent 3px), 289 | // radial-gradient(rgba($graph-blue, 0.5) 15%, transparent 60%); 290 | // background-size: 1em 1em; 291 | // padding: 1rem; 292 | // &.light { 293 | // background-image: radial-gradient( 294 | // rgba($graph-blue, 1) 2px, 295 | // transparent 3px 296 | // ); 297 | // } 298 | // } 299 | 300 | // .podsViewInner { 301 | // position: relative; 302 | // width: 100%; 303 | // height: 100%; 304 | // animation: opacity 1s ease-in-out; 305 | // animation-fill-mode: backwards; 306 | // animation-delay: 1.5s; 307 | // opacity: 1; 308 | // backdrop-filter: blur(5px); 309 | // border-radius: 8px; 310 | // overflow: auto; 311 | // padding: 1.5rem 0; 312 | // &.dark { 313 | // background-color: rgba(0, 0, 0, 0.03); 314 | // border: 1px solid rgb(255, 255, 255); 315 | // box-shadow: 0px 0px 15px 0px $diamond-blue; 316 | // } 317 | // &.light { 318 | // background-color: rgba(255, 255, 255, 0); 319 | // border: 1px solid rgb(255, 255, 255); 320 | // box-shadow: none; 321 | // } 322 | // } 323 | 324 | // .containerBuckets { 325 | // @include podContainer; 326 | // display: flex; 327 | // flex-direction: column; 328 | // width: 100%; 329 | // height: fit-content; 330 | // border: none; 331 | // box-shadow: 0px 4px 7.5px -3px rgba(20, 20, 20, 0.5); 332 | // } 333 | 334 | // .containerBucketOuter { 335 | // padding: 0.5rem; 336 | // overflow-x: auto; 337 | // border-top: none; 338 | // border-bottom: 1px solid rgb(255, 255, 255); 339 | // } 340 | 341 | // .containerImage { 342 | // white-space: nowrap; 343 | // } 344 | 345 | // .podContainer { 346 | // position: relative; 347 | // width: min-content; 348 | // height: min-content; 349 | // transition: all 0.5s ease-in-out; 350 | // background-image: linear-gradient( 351 | // 45deg, 352 | // lighten($base-red, 20%), 353 | // $base-orange 354 | // ); 355 | // // animation: expand-card-deck 1s ease-in-out, wave 1s ease-in-out, 356 | // // move-gradient 1.5s ease-in-out infinite; 357 | // background-size: 600% 600%; 358 | // animation-fill-mode: backwards; 359 | // border-radius: 10px; 360 | // margin: 0.5rem; 361 | // background-color: rgba(31, 31, 31, 0.55); 362 | // } 363 | 364 | // .podsContainer { 365 | // width: fit-content; 366 | // height: fit-content; 367 | // border: none; 368 | // padding: 0 1.5rem; 369 | // } 370 | 371 | // .pod { 372 | // display: flex; 373 | // flex-direction: column; 374 | // position: relative; 375 | // width: 18rem; 376 | // height: 20rem; 377 | // margin: 0.5rem; 378 | // border: 1.5px solid rgb(255, 255, 255); 379 | // border-radius: 4px; 380 | // background-color: $base-yellow; 381 | // transition: all 0.5s ease-in-out; 382 | // overflow: auto; 383 | // &:hover { 384 | // transform: scale(1.1); 385 | // z-index: 999; 386 | // } 387 | // } 388 | 389 | // .podName { 390 | // display: flex; 391 | // align-items: center; 392 | // overflow-x: auto; 393 | // overflow-y: hidden; 394 | // flex-shrink: 0; 395 | // width: 100%; 396 | // height: fit-content; 397 | // padding: 0.5rem; 398 | // font-size: 1.5rem; 399 | // font-weight: 600; 400 | // box-shadow: 0px 1px 7.5px 0px rgba(20, 20, 20, 0.5); 401 | // text-shadow: 2px 2px 1px rgba(0, 0, 0, 0.5); 402 | // white-space: nowrap; 403 | // border-bottom: 1px solid rgb(255, 255, 255); 404 | // } 405 | 406 | // .podMetrics { 407 | // @include podContainer; 408 | // width: 100%; 409 | // height: fit-content; 410 | // padding: 0.5rem; 411 | // font-size: 1rem; 412 | // font-weight: 600; 413 | // color: rgb(255, 255, 255); 414 | // border-bottom: 1px solid rgb(255, 255, 255); 415 | // box-shadow: 0px 4px 7.5px -3px rgba(20, 20, 20, 0.5); 416 | // } 417 | 418 | // .podRow { 419 | // display: flex; 420 | // width: fit-content; 421 | // height: fit-content; 422 | // // margin: 0 1.5rem; 423 | // } 424 | 425 | // .podsViewPolkaOverlay { 426 | // position: absolute; 427 | // width: 100%; 428 | // height: 100%; 429 | // // background-image: radial-gradient(rgba($graph-blue, 0.5) 15%, transparent 60%); 430 | // background-size: 1em 1em; 431 | // border-radius: 0; 432 | // } 433 | 434 | // .podsViewOverlay { 435 | // position: absolute; 436 | // width: 100%; 437 | // height: 100%; 438 | // background-color: rgba(0, 0, 0, 0.5); 439 | // z-index: 1; 440 | // border-radius: 0; 441 | // pointer-events: all; 442 | // } 443 | 444 | // @keyframes wave { 445 | // 0% { 446 | // transform: translateY(0); 447 | // } 448 | 449 | // 50% { 450 | // transform: translateY(-1rem); 451 | // } 452 | 453 | // 100% { 454 | // transform: translateY(0); 455 | // } 456 | // } 457 | 458 | // @keyframes expand-card-deck { 459 | // 0% { 460 | // margin: -9.5rem; 461 | // } 462 | 463 | // 100% { 464 | // margin: 0.5rem; 465 | // } 466 | // } 467 | 468 | // @keyframes move-gradient { 469 | // 0% { 470 | // background-position: 0% 50%; 471 | // } 472 | // 50% { 473 | // background-position: 100% 50%; 474 | // } 475 | // 100% { 476 | // background-position: 0% 50%; 477 | // } 478 | // } 479 | -------------------------------------------------------------------------------- /client/css/Reactdnd.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | @import 'variables'; 3 | 4 | .navPositions { 5 | position: absolute; 6 | width: 100vw; 7 | height: 100vh; 8 | z-index: 100; 9 | pointer-events: none; 10 | } 11 | 12 | .outerDroppable { 13 | @include flex-horizontal; 14 | position: relative; 15 | pointer-events: all; 16 | // height: fit-content; 17 | width: 100%; 18 | border-radius: 8px; 19 | transition: all 0.2s ease-in-out; 20 | pointer-events: none; 21 | } 22 | 23 | #topNavPosition { 24 | @include flex-horizontal; 25 | position: relative; 26 | width: 100%; 27 | top: 0; 28 | } 29 | 30 | #bottomNavPosition { 31 | @include flex-horizontal; 32 | position: relative; 33 | width: 100%; 34 | bottom: 0; 35 | } 36 | 37 | #leftNavPosition { 38 | @include flex-horizontal; 39 | position: relative; 40 | height: 100%; 41 | width: 50px; 42 | top: 0; 43 | } 44 | 45 | #rightNavPosition { 46 | @include flex-horizontal; 47 | position: relative; 48 | height: 100%; 49 | width: 50px; 50 | top: 0; 51 | } 52 | 53 | .styleTop { 54 | position: absolute; 55 | display: flex; 56 | left: 0%; 57 | top: 1.5rem; 58 | height: 50px; 59 | width: 100%; 60 | } 61 | 62 | .styleLeft { 63 | position: absolute; 64 | display: flex; 65 | left: 5%; 66 | top: 0%; 67 | height: 100%; 68 | width: 50px; 69 | .outerDroppable { 70 | height: 100%; 71 | } 72 | } 73 | 74 | .styleBottom { 75 | position: absolute; 76 | display: flex; 77 | left: 0%; 78 | bottom: 1.5rem; 79 | height: 50px; 80 | width: 100%; 81 | } 82 | 83 | .styleRight { 84 | position: absolute; 85 | display: flex; 86 | right: 5%; 87 | top: 0%; 88 | height: 100%; 89 | width: 50px; 90 | .outerDroppable { 91 | height: 100%; 92 | } 93 | } 94 | 95 | .draggableContainer { 96 | pointer-events: all; 97 | } 98 | 99 | .outlineTop { 100 | position: absolute; 101 | background-color: $base-yellow; 102 | margin-left: auto; 103 | margin-right: auto; 104 | left: 0; 105 | right: 0; 106 | text-align: center; 107 | height: 4.5em; 108 | width: 27em; 109 | border-radius: 8px; 110 | transition: all 0.2s ease-in-out; 111 | } 112 | 113 | .outlineLeft { 114 | position: absolute; 115 | background-color: $base-yellow; 116 | position: absolute; 117 | top: 50%; 118 | transform: translateY(-50%); 119 | height: 18em; 120 | width: 7.5em; 121 | border-radius: 8px; 122 | transition: all 0.2s ease-in-out; 123 | } 124 | 125 | .outlineBottom { 126 | position: absolute; 127 | background-color: $base-yellow; 128 | margin-left: auto; 129 | margin-right: auto; 130 | left: 0; 131 | right: 0; 132 | text-align: center; 133 | height: 4.5em; 134 | width: 27em; 135 | border-radius: 8px; 136 | transition: all 0.2s ease-in-out; 137 | } 138 | 139 | .outlineRight { 140 | position: absolute; 141 | background-color: $base-yellow; 142 | top: 50%; 143 | transform: translateY(-50%); 144 | height: 18em; 145 | width: 7.5em; 146 | border-radius: 8px; 147 | transition: all 0.2s ease-in-out; 148 | } 149 | 150 | .dragButton { 151 | background-color: transparent; 152 | border: none; 153 | } 154 | -------------------------------------------------------------------------------- /client/css/base.scss: -------------------------------------------------------------------------------- 1 | @import './Variables.scss'; 2 | 3 | @mixin flex-horizontal { 4 | display: flex; 5 | flex-direction: row; 6 | align-items: center; 7 | justify-content: center; 8 | } 9 | 10 | @mixin flex-vertical { 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: center; 15 | } 16 | 17 | @mixin flex-center { 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | 23 | @mixin grid-base { 24 | display: grid; 25 | grid-template-columns: repeat(12, 1fr); 26 | grid-gap: 1rem; 27 | } 28 | 29 | .outerContainer { 30 | position: relative; 31 | height: 100%; 32 | width: 100%; 33 | background-color: #222222; 34 | color: #ededed; 35 | font-family: 'Roboto', sans-serif; 36 | font-size: 1.2rem; 37 | } 38 | 39 | .innerContainer { 40 | position: relative; 41 | height: 100%; 42 | width: 100%; 43 | } 44 | 45 | .dark { 46 | background-color: $background-color-dark; 47 | } 48 | 49 | .light { 50 | background-color: $background-color-light; 51 | } 52 | 53 | .textShadowLight { 54 | text-shadow: 2px 2px 2px black; 55 | } 56 | 57 | .textShadowLighter { 58 | text-shadow: 1px 1px 1px black; 59 | } 60 | 61 | .Opacity-In { 62 | animation: opacity 1s ease-in-out; 63 | animation-fill-mode: forwards; 64 | } 65 | 66 | .Opacity-Out { 67 | animation: opacity-reverse 2s ease-in-out; 68 | animation-fill-mode: backwards; 69 | } 70 | 71 | @keyframes hover-up-down { 72 | 0% { 73 | transform: translateY(0px); 74 | } 75 | 50% { 76 | transform: translateY(-3px); 77 | } 78 | 100% { 79 | transform: translateY(0px); 80 | } 81 | } 82 | 83 | @keyframes opacity { 84 | 0% { 85 | opacity: 0; 86 | } 87 | 100% { 88 | opacity: 1; 89 | } 90 | } 91 | 92 | @keyframes opacity-reverse { 93 | 0% { 94 | opacity: 1; 95 | } 96 | 100% { 97 | opacity: 0; 98 | } 99 | } 100 | 101 | @keyframes hover-up-down-3D { 102 | 0% { 103 | transform: translateY(0px); 104 | } 105 | 50% { 106 | transform: translateY(-3.5px); 107 | } 108 | 100% { 109 | transform: translateY(0px); 110 | } 111 | } 112 | 113 | @keyframes navLinkBlueShadow { 114 | 0% { 115 | box-shadow: 0 0 0px #287aff; 116 | } 117 | 50% { 118 | box-shadow: 0 0 8px #287aff; 119 | text-shadow: 0px 0px 10px #287aff; 120 | } 121 | 100% { 122 | box-shadow: 0 0 0px #287aff; 123 | } 124 | } 125 | 126 | @keyframes scale-up-down { 127 | 0% { 128 | transform: scale(1); 129 | } 130 | 50% { 131 | transform: scale(0.8); 132 | } 133 | 100% { 134 | transform: scale(1); 135 | } 136 | } 137 | 138 | .Main { 139 | @include flex-vertical; 140 | height: 100%; 141 | width: 100%; 142 | overflow: hidden; 143 | position: relative; 144 | } 145 | 146 | $inline-blue: #24b0df; 147 | $inline-red: #db3523; 148 | $inline-orange: #f4a227; 149 | 150 | .inlineOrangeText { 151 | color: $inline-orange; 152 | text-shadow: 2px 2px 2px black; 153 | display: inline; 154 | } 155 | 156 | .inlineBlueText { 157 | color: $inline-blue; 158 | text-shadow: 2px 2px 2px black; 159 | display: inline; 160 | } 161 | 162 | .inlineRedText { 163 | color: $inline-red; 164 | text-shadow: 2px 2px 2px black; 165 | display: inline; 166 | } 167 | -------------------------------------------------------------------------------- /client/css/graph.scss: -------------------------------------------------------------------------------- 1 | @import './Base.scss'; 2 | @import './Variables.scss'; 3 | 4 | .draggableContainer { 5 | position: relative; 6 | width: 100vw; 7 | height: 100vh; 8 | overflow: hidden; 9 | border-radius: 0px; 10 | transition: all 0.5s ease-in-out; 11 | // resolution of the background image 12 | background-size: 2.2em 2.2em; 13 | // facilitates the movement of the background image when the user drags the graph 14 | background-attachment: local; 15 | // animation: grid-glow-flicker 1s ease-in-out infinite; 16 | background-image: 17 | // horizontal lines 18 | linear-gradient(rgba($graph-blue, 1) 0.1em, transparent 0.2em), 19 | // vertical lines 20 | linear-gradient(90deg, rgba($graph-blue, 1) 0.1em, transparent 0.2em), 21 | // horizontal glow bottom side 22 | linear-gradient(rgba($graph-blue, 0.5) 0.1em, transparent 0.5em), 23 | // horizontal glow top side 24 | linear-gradient(360deg, rgba($graph-blue, 0.5) 0.1em, transparent 0.5em), 25 | // vertical glow left side 26 | linear-gradient(270deg, rgba($graph-blue, 0.5) 0.1em, transparent 0.5em), 27 | // vertical glow right side 28 | linear-gradient(90deg, rgba($graph-blue, 0.5) 0.1em, transparent 0.5em); 29 | &.light { 30 | background-image: 31 | // horizontal lines 32 | linear-gradient(rgba($graph-blue, 1) 0.1em, transparent 0.2em), 33 | // vertical lines 34 | linear-gradient(90deg, rgba($graph-blue, 1) 0.1em, transparent 0.2em); 35 | } 36 | } 37 | 38 | .graph { 39 | position: relative; 40 | width: fit-content; 41 | height: fit-content; 42 | transition: all 0.5s ease-in-out; 43 | backdrop-filter: blur(8px); 44 | border-radius: 8px; 45 | animation: opacity 1s ease-in-out; 46 | padding: 1rem; 47 | &.dark { 48 | background-color: rgba(0, 0, 0, 0.03); 49 | border: 1px solid rgb(255, 255, 255); 50 | box-shadow: $pronounced-shadow; 51 | } 52 | 53 | &.light { 54 | background-color: rgba(255, 255, 255, 0); 55 | border: 1px solid rgb(0, 0, 0); 56 | box-shadow: $pronounced-shadow; 57 | } 58 | 59 | &:hover { 60 | box-shadow: 15px 15px 1px 0 rgb(0, 0, 0); 61 | scale: 1.05; 62 | } 63 | } 64 | 65 | .row { 66 | perspective: 1000px; 67 | } 68 | 69 | .draggableInner { 70 | transform-origin: center center; 71 | position: relative; 72 | height: fit-content; 73 | width: fit-content; 74 | transition: all 0.5s ease-in-out; 75 | scale: 1; 76 | } 77 | 78 | .draggableContent { 79 | @include flex-vertical; 80 | position: relative; 81 | min-height: 100vh; 82 | transition: all 0.5s ease-in-out; 83 | transform-origin: 0% 0%; 84 | overflow: hidden; 85 | scale: 1; 86 | } 87 | 88 | @keyframes grid-glow-flicker { 89 | 0% { 90 | opacity: 1; 91 | } 92 | 50% { 93 | opacity: 0.5; 94 | } 95 | 100% { 96 | opacity: 1; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /client/css/home.scss: -------------------------------------------------------------------------------- 1 | @import './Base.scss'; 2 | 3 | @mixin outerView { 4 | @include flex-vertical; 5 | background: transparent; 6 | background-size: 3em 3em; 7 | border: 1.5px solid #000000; 8 | box-shadow: 0px 1px 10px 0px #000000; 9 | border-radius: 8px; 10 | height: 80%; 11 | width: 80%; 12 | } 13 | 14 | .outerViewdark { 15 | @include outerView; 16 | background-image: linear-gradient( 17 | rgba(255, 255, 255, 0.7) 0.1em, 18 | transparent 0.1em 19 | ), 20 | linear-gradient(90deg, rgba(255, 255, 255, 0.7) 0.1em, transparent 0.1em); 21 | } 22 | 23 | .outerViewlight { 24 | @include outerView; 25 | background-image: linear-gradient(rgba(0, 0, 0, 0.7) 0.1em, transparent 0.1em), 26 | linear-gradient(90deg, rgba(0, 0, 0, 0.7) 0.1em, transparent 0.1em); 27 | } 28 | -------------------------------------------------------------------------------- /client/css/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Baumans&display=swap'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | transition: background-color 0.3s ease-in-out; 6 | font-family: 'Baumans'; 7 | scrollbar-width: thin; 8 | scrollbar-color: rgba(255, 255, 255) rgba(0, 0, 0, 0.5); 9 | } 10 | 11 | body { 12 | position: relative; 13 | } 14 | 15 | html, 16 | body { 17 | height: 100%; 18 | width: 100%; 19 | margin: 0; 20 | padding: 0; 21 | /* overflow: hidden; */ 22 | } 23 | 24 | #root { 25 | height: 100%; 26 | width: 100%; 27 | margin: 0; 28 | padding: 0; 29 | /* overflow: hidden; */ 30 | } 31 | 32 | a { 33 | text-decoration: none; 34 | width: fit-content; 35 | height: fit-content; 36 | color: inherit; 37 | } 38 | -------------------------------------------------------------------------------- /client/css/navbar.scss: -------------------------------------------------------------------------------- 1 | @import './Base.scss'; 2 | 3 | .navDragButton { 4 | max-width: 40px; 5 | max-height: 40px; 6 | } 7 | 8 | #navDragImg { 9 | max-width: 40px; 10 | max-height: 40px; 11 | margin: 0.2rem; 12 | transition: all 0.2s ease-in-out; 13 | border-radius: 5px; 14 | &:hover { 15 | animation: hover-up-down-3D 1s infinite ease-in-out; 16 | transform: translateY(-2px); 17 | box-shadow: 0 0 15px #287aff; 18 | background-color: #16428961; 19 | cursor: grab; 20 | } 21 | } 22 | 23 | .navContainer { 24 | overflow: visible; 25 | position: relative; 26 | top: 0%; 27 | z-index: 1; 28 | border: 1px solid rgb(255, 255, 255); 29 | box-shadow: 5px 5px #000000; 30 | border-radius: 8px; 31 | transition: all 0.2s ease-in-out; 32 | width: fit-content; 33 | height: fit-content; 34 | &:hover { 35 | #navDragContainer { 36 | opacity: 0.5; 37 | &:hover { 38 | opacity: 1; 39 | background-color: #0000005d; 40 | } 41 | } 42 | } 43 | } 44 | 45 | .navContainer.vertical { 46 | overflow: visible; 47 | position: relative; 48 | top: 0%; 49 | z-index: 1; 50 | border: 1px solid rgb(255, 255, 255); 51 | box-shadow: 5px 5px #000000; 52 | border-radius: 8px; 53 | transition: all 0.2s ease-in-out; 54 | &:hover { 55 | #navDragContainer { 56 | opacity: 0.5; 57 | &:hover { 58 | opacity: 1; 59 | background-color: #0000005d; 60 | } 61 | } 62 | } 63 | } 64 | 65 | .navDragContainer { 66 | @include flex-horizontal; 67 | position: absolute; 68 | border-radius: 8px; 69 | transition: all 0.2s ease-in-out; 70 | pointer-events: all; 71 | z-index: 1000; 72 | &.vertical { 73 | @include flex-vertical; 74 | position: absolute; 75 | opacity: 1; 76 | border-radius: 8px; 77 | transition: all 0.2s ease-in-out; 78 | pointer-events: all; 79 | } 80 | } 81 | 82 | .navContainer:hover { 83 | box-shadow: 0px 1px 15px 0px #9d2719; 84 | transform: translateY(-2px); 85 | box-shadow: 10px 10px #000000; 86 | } 87 | 88 | .NavBar { 89 | @include flex-horizontal; 90 | height: 4rem; 91 | width: fit-content; 92 | background-color: #154084; 93 | border-radius: 8px; 94 | color: #ededed; 95 | font-family: 'Roboto', sans-serif; 96 | font-size: 1.2rem; 97 | overflow: visible; 98 | padding: 1rem 1rem; 99 | position: relative; 100 | pointer-events: visible; 101 | opacity: 1; 102 | transition: all 0.2s ease-in-out; 103 | box-shadow: 0px 0px 5px 1.5px #277aff; 104 | } 105 | 106 | .NavBar.vertical { 107 | @include flex-vertical; 108 | height: fit-content; 109 | width: min-content; 110 | background-color: #154084; 111 | border-radius: 8px; 112 | color: #ededed; 113 | font-family: 'Roboto', sans-serif; 114 | font-size: 1.2rem; 115 | position: relative; 116 | pointer-events: visible; 117 | opacity: 1; 118 | transition: all 0.2s ease-in-out; 119 | .nav-right { 120 | @include flex-vertical; 121 | } 122 | } 123 | 124 | #App-Name-Header { 125 | text-shadow: 0px 0px 5px #000000; 126 | font-family: 'Baumans'; 127 | font-size: 1.5rem; 128 | margin: 0; 129 | } 130 | 131 | #App-Name-Header:hover { 132 | text-shadow: 0px 0px 5px #287aff; 133 | font-family: 'Baumans'; 134 | font-size: 1.5rem; 135 | margin: 0; 136 | } 137 | 138 | .nav-right { 139 | @include flex-horizontal; 140 | margin-left: auto; 141 | } 142 | 143 | .navLink { 144 | color: #ededed; 145 | text-decoration: none; 146 | margin: 0.5rem; 147 | cursor: pointer; 148 | text-shadow: 0px 0px 5px #000000; 149 | transition: all 0.2s ease-in-out; 150 | border-radius: 8px; 151 | padding: 0.5rem; 152 | font-size: 1.25rem; 153 | &.btn { 154 | background-color: transparent; 155 | border: transparent; 156 | } 157 | } 158 | 159 | .navLink:hover { 160 | animation: hover-up-down-3D 1s infinite ease-in-out, 161 | navLinkBlueShadow 1s infinite ease-in-out; 162 | align-items: center; 163 | color: #ededed; 164 | text-decoration: none; 165 | margin-left: 1rem; 166 | cursor: pointer; 167 | text-shadow: 0px 0px 5px #287aff; 168 | transform: translateY(-2px); 169 | box-shadow: 0 0 15px #287aff; 170 | background-color: #16428961; 171 | } 172 | 173 | .nav-right { 174 | @include flex-horizontal; 175 | margin-left: auto; 176 | padding: 0; 177 | } 178 | -------------------------------------------------------------------------------- /client/css/node.scss: -------------------------------------------------------------------------------- 1 | @import 'Variables.scss'; 2 | @import 'Base.scss'; 3 | 4 | @keyframes expand { 5 | 0% { 6 | transform: scale(1); 7 | } 8 | 9 | 50% { 10 | transform: scale(1.1); 11 | } 12 | 13 | 100% { 14 | transform: scale(1); 15 | } 16 | } 17 | 18 | @keyframes shake-left-right { 19 | 0% { 20 | transform: translateX(0px); 21 | } 22 | 23 | 50% { 24 | transform: translateX(5px); 25 | } 26 | 27 | 100% { 28 | transform: translateX(0px); 29 | } 30 | } 31 | 32 | @keyframes hover-up-down-node { 33 | 0% { 34 | transform: translateY(0px); 35 | } 36 | 37 | 50% { 38 | transform: translateY(4px); 39 | } 40 | 41 | 100% { 42 | transform: translateY(0px); 43 | } 44 | } 45 | 46 | .Node { 47 | @include flex-vertical; 48 | // background-color: $background-color-dark; 49 | background-color: $background-color-dark; 50 | background-image: linear-gradient( 51 | #1a1a1a, 52 | rgba(255, 255, 255, 0.165) 0em, 53 | transparent 8em 54 | ); 55 | // background-image: linear-gradient(45, $base-yellow, $base-orange); 56 | clip-path: polygon( 57 | 51% 2%, 58 | 89% 21%, 59 | 99% 60%, 60 | 75% 99%, 61 | 26% 99%, 62 | 1% 60%, 63 | 11% 21% 64 | ); 65 | scale: 0.95; 66 | } 67 | 68 | .borderNode { 69 | position: relative; 70 | background-color: black; 71 | border-radius: 20px; 72 | clip-path: polygon( 73 | 51% 2%, 74 | 89% 21%, 75 | 99% 60%, 76 | 75% 99%, 77 | 26% 99%, 78 | 1% 60%, 79 | 11% 21% 80 | ); 81 | transition: all 0.2s ease-in-out; 82 | } 83 | 84 | .nodeOverlay { 85 | position: absolute; 86 | width: 100%; 87 | height: 100%; 88 | box-shadow: 0px 0px 10px 1px rgb(255, 255, 255); 89 | border-radius: 8px; 90 | background-image: linear-gradient( 91 | rgba(255, 255, 255, 0.165) 0em, 92 | transparent 8em 93 | ); 94 | transition: all 0.2s ease-in-out; 95 | pointer-events: none; 96 | } 97 | 98 | #podIcon { 99 | transition: all 0.2s ease-in-out; 100 | border: 1px solid $base-orange; 101 | border-radius: 8px; 102 | background-color: #00000040; 103 | &:hover { 104 | cursor: pointer; 105 | border-radius: 50%; 106 | animation: hover-up-down-3D 1s infinite ease-in-out, 107 | podIconOrangeShadow 1s infinite ease-in-out; 108 | } 109 | 110 | &:active { 111 | cursor: pointer; 112 | border-radius: 50%; 113 | animation: podIconOrangeShadow 1s infinite ease-in-out, 114 | scale-up-down 1s infinite ease-in-out; 115 | } 116 | } 117 | 118 | .row { 119 | display: flex; 120 | width: 100%; 121 | height: fit-content; 122 | } 123 | 124 | .item { 125 | height: 100px; 126 | flex-shrink: 0; 127 | flex-grow: 1; 128 | flex-basis: 50%; 129 | width: 100px; 130 | font-size: 5rem; 131 | } 132 | 133 | .row:nth-child(odd) { 134 | background-color: rgba(255, 0, 0, 0); 135 | } 136 | 137 | .row:nth-child(even) { 138 | background-color: rgba(0, 0, 255, 0); 139 | flex-direction: row-reverse; 140 | justify-content: flex-start; 141 | } 142 | 143 | .nodeContainer { 144 | position: relative; 145 | display: grid; 146 | grid-template-columns: 1fr; 147 | grid-gap: 1rem; 148 | } 149 | 150 | .nodeContent { 151 | display: flex; 152 | position: relative; 153 | flex-direction: column; 154 | justify-content: center; 155 | height: fit-content; 156 | width: 10rem; 157 | color: white; 158 | text-shadow: 2px 2px 4px #000000; 159 | text-align: center; 160 | div { 161 | height: fit-content; 162 | overflow-x: auto; 163 | white-space: nowrap; 164 | margin: 0.2em 0em; 165 | } 166 | 167 | *:hover { 168 | cursor: text; 169 | } 170 | } 171 | 172 | .nodeContainer:has(.nodeName:hover) .tooltip-text { 173 | visibility: visible; 174 | white-space: nowrap; 175 | top: 0%; 176 | right: 0%; 177 | z-index: 100; 178 | } 179 | 180 | .nodeName { 181 | position: relative; 182 | max-height: min-content; 183 | padding: 0.3rem; 184 | } 185 | 186 | .podCount { 187 | max-width: fit-content; 188 | margin: auto; 189 | } 190 | 191 | .tooltip-text { 192 | position: absolute; 193 | left: -50%; 194 | right: 0%; 195 | margin: auto; 196 | padding: 0.6em; 197 | width: fit-content; 198 | visibility: hidden; 199 | opacity: 0.9; 200 | scale: 0.8; 201 | &.dark { 202 | border: 3px solid white; 203 | color: white; 204 | } 205 | &.light { 206 | border: 3px solid black; 207 | color: black; 208 | } 209 | } 210 | 211 | @keyframes podIconOrangeShadow { 212 | 0% { 213 | box-shadow: 0 0 0px $base-orange; 214 | } 215 | 50% { 216 | box-shadow: 0 0 8px $base-orange; 217 | text-shadow: 0px 0px 10px $base-orange; 218 | } 219 | 100% { 220 | box-shadow: 0 0 0px $base-orange; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /client/css/settings.scss: -------------------------------------------------------------------------------- 1 | @import './Base.scss'; 2 | 3 | -------------------------------------------------------------------------------- /client/css/switchbutton.scss: -------------------------------------------------------------------------------- 1 | .switchContainer { 2 | position: relative; 3 | margin: 0em 0.5em; 4 | } 5 | 6 | .switchButton { 7 | position: relative; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | width: 3.5em; 12 | height: 1.5em; 13 | } 14 | 15 | .check-box { 16 | position: relative; 17 | display: flex; 18 | align-items: center; 19 | justify-content: center; 20 | } 21 | 22 | .switchBar[type='checkbox'] { 23 | position: relative; 24 | appearance: none; 25 | width: 100%; 26 | height: 100%; 27 | background: #e4e4e4; 28 | border-radius: 50px; 29 | box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); 30 | cursor: pointer; 31 | transition: 0.4s; 32 | } 33 | 34 | .slider { 35 | position: absolute; 36 | width: 2.3em; 37 | height: 2.3em; 38 | top: -0.4em; 39 | left: -0.5em; 40 | background: #ffffff00; 41 | border-radius: 50%; 42 | // box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); 43 | transition: 0.4s ease-in-out; 44 | pointer-events: none; 45 | } 46 | 47 | .switchBar[type='checkbox']:checked { 48 | background: #1f1f1f; 49 | } 50 | 51 | .switchBar[type='checkbox']:checked + .slider { 52 | left: 50%; 53 | transform: scale3d(-1.2, 1, 1); 54 | width: 2em; 55 | height: 2.2em; 56 | top: -0.4em; 57 | } 58 | 59 | .themeIcon { 60 | position: absolute; 61 | width: 100%; 62 | height: 100%; 63 | transition: 0.2s ease-in-out; 64 | pointer-events: none; 65 | } 66 | -------------------------------------------------------------------------------- /client/css/variables.scss: -------------------------------------------------------------------------------- 1 | $background-color-dark: #222222; 2 | $background-color-light: #d0d0d0; 3 | $base-blue: #154084; 4 | $base-red: #9d2719; 5 | $base-yellow: #d7b418; 6 | $base-orange: #f4a227; 7 | $accent-blue: #188fff; 8 | $accent-red: #ff4d4d; 9 | $accent-yellow: #f5d300; 10 | $accent-orange: #f4a227; 11 | $dark-text: #ededed; 12 | $light-text: #222222; 13 | $graph-blue: #188fff; 14 | $graph-yellow: #f5d300; 15 | $diamond-blue: rgb(200, 230, 255); 16 | $unhealthy-pod: #ad4a39; 17 | $healthy-pod: #42a62b; 18 | $warning-pod: #e8c529; 19 | $pronounced-shadow: 8px 8px 1px 0 rgb(0, 0, 0); 20 | $healthyGradient: linear-gradient(-45deg, #52caee, #3c53e7, #23a6d5, #23d5ab); 21 | $unhealthyGradient: linear-gradient(-45deg, #e65252, #e73c3c, #d52323, #ab2323); 22 | $warningGradient: linear-gradient(-45deg, #e6e652, #e7e73c, #d5d523, #abab23); 23 | -------------------------------------------------------------------------------- /client/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Baumans&display=swap'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | transition: background-color 0.3s ease-in-out; 6 | font-family: 'Baumans'; 7 | } 8 | 9 | *::-webkit-scrollbar { 10 | width: 12px; 11 | height: 12px; 12 | } 13 | 14 | ::-webkit-scrollbar:hover { 15 | cursor: pointer; 16 | } 17 | 18 | *::-webkit-scrollbar-track { 19 | background-color: transparent; 20 | border: transparent; 21 | } 22 | 23 | 24 | *::-webkit-scrollbar-thumb { 25 | background-color: #888; 26 | border-radius: 6px; 27 | width: 8px; 28 | height: 60px; 29 | cursor: pointer !important; 30 | } 31 | 32 | *::-webkit-scrollbar-thumb:hover { 33 | background-color: #555; 34 | cursor: pointer; 35 | } 36 | 37 | 38 | *::-webkit-scrollbar-corner { 39 | display: none; 40 | } 41 | 42 | body { 43 | position: relative; 44 | } 45 | 46 | html, 47 | body { 48 | height: 100%; 49 | width: 100%; 50 | margin: 0; 51 | padding: 0; 52 | } 53 | 54 | #root { 55 | height: 100%; 56 | width: 100%; 57 | margin: 0; 58 | padding: 0; 59 | } 60 | 61 | .main-wrapper { 62 | height: 100%; 63 | } 64 | 65 | a { 66 | text-decoration: none; 67 | width: fit-content; 68 | height: fit-content; 69 | color: inherit; 70 | } 71 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Faros 6 | 7 | 8 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /client/index.tsx: -------------------------------------------------------------------------------- 1 | import "react-app-polyfill/stable"; 2 | import "core-js"; 3 | import { createRoot } from "react-dom/client"; 4 | import { StrictMode } from "react"; 5 | import { Provider } from "react-redux"; 6 | import App from "./App"; 7 | import "./index.css"; 8 | import "./variables.scss"; 9 | import "./theme.scss"; 10 | import { store } from "./store/store"; 11 | 12 | const container = document.getElementById("root"); 13 | 14 | container 15 | ? createRoot(container).render( 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | : null; 23 | -------------------------------------------------------------------------------- /client/layout/DefaultLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import { AppContent, NavBar } from '../components/index'; 3 | 4 | // ! TEMPORARY IMPORT 5 | import { useGetClusterInfoQuery } from '../services/api'; 6 | 7 | const DefaultLayout = () => { 8 | // ! TEMPORARY 9 | useGetClusterInfoQuery(undefined, { pollingInterval: 25000 }); 10 | 11 | return ( 12 |
13 | 14 | 25 | 26 | 27 |
28 | ); 29 | }; 30 | 31 | export default DefaultLayout; 32 | -------------------------------------------------------------------------------- /client/layout/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultLayout from './DefaultLayout'; 2 | 3 | export { DefaultLayout }; -------------------------------------------------------------------------------- /client/pages/ListViewPage/ListViewPage.scss: -------------------------------------------------------------------------------- 1 | .list-view { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 2em; 5 | padding: 4rem; 6 | overflow: visible; 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /client/pages/ListViewPage/ListViewPage.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux'; 2 | import { RootState } from '../../../types/types'; 3 | import { formatMetricsMap } from '../../util/formatters/formatMetricsMap'; 4 | // import { ListViewTable } from '../../components/List-View/ListViewTable'; 5 | import './ListViewPage.scss'; 6 | import { Box } from '@mui/material'; 7 | import { formatContainerUsage } from '../../util/formatters/formatContainerUsage'; 8 | import { 9 | useGetClusterMetricsMapQuery, 10 | useGetContainerUsageQuery, 11 | useGetClusterInfoQuery, 12 | } from '../../services/api'; 13 | import { lazy } from 'react'; 14 | import { GridColDef } from '@mui/x-data-grid'; 15 | 16 | const DataGridWithHeader = lazy( 17 | () => import('../../components/DataGridWithHeader/DataGridWithHeader') 18 | ); 19 | 20 | // Columns for Container Usage by NameSpace 21 | const columns: GridColDef[] = [ 22 | { field: 'id', headerName: 'ID', width: 100 }, 23 | { 24 | field: 'name', 25 | headerName: 'Name', 26 | headerAlign: 'left', 27 | align: 'left', 28 | // width: 160, 29 | flex: 1, 30 | editable: true, 31 | }, 32 | { 33 | field: 'CPU', 34 | headerName: 'CPU (num cores)', 35 | headerAlign: 'center', 36 | align: 'center', 37 | flex: 1, 38 | editable: true, 39 | valueGetter: (params) => { 40 | return params.value.toFixed(5); 41 | }, 42 | }, 43 | { 44 | field: 'MEM', 45 | headerName: 'MEM (bytes)', 46 | headerAlign: 'center', 47 | align: 'center', 48 | type: 'number', 49 | // width: 130, 50 | flex: 1, 51 | editable: true, 52 | // valueGetter: (params) => { 53 | // return params.value.toFixed(2); 54 | // }, 55 | }, 56 | ]; 57 | 58 | // Columns for ListView 59 | const columnsListView: GridColDef[] = [ 60 | { field: 'id', headerName: 'ID', flex: 1 }, 61 | { 62 | field: 'name', 63 | headerName: 'Name', 64 | flex: 1, 65 | editable: true, 66 | }, 67 | { 68 | field: 'cpuUsage', 69 | headerName: 'CPU Usage', 70 | headerAlign: 'center', 71 | align: 'center', 72 | flex: 1, 73 | editable: true, 74 | }, 75 | { 76 | field: 'cpuUsagePct', 77 | headerName: 'CPU Usage (%)', 78 | headerAlign: 'center', 79 | align: 'center', 80 | type: 'number', 81 | flex: 1, 82 | editable: true, 83 | valueGetter: (params) => { 84 | return params.value.toFixed(2); 85 | }, 86 | }, 87 | { 88 | field: 'memUsage', 89 | headerName: 'MEM Usage (bytes)', 90 | headerAlign: 'center', 91 | align: 'center', 92 | type: 'number', 93 | flex: 1, 94 | editable: true, 95 | }, 96 | { 97 | field: 'memUsagePct', 98 | headerName: 'MEM Usage (%)', 99 | headerAlign: 'center', 100 | align: 'center', 101 | type: 'number', 102 | flex: 1, 103 | editable: true, 104 | valueGetter: (params) => { 105 | return params.value.toFixed(2); 106 | }, 107 | }, 108 | ]; 109 | 110 | // Columns for List Container and Nodes Metrics 111 | const columnsListViewUtil: GridColDef[] = [ 112 | { field: 'id', headerName: 'ID', flex: 1 }, 113 | { 114 | field: 'name', 115 | headerName: 'Name', 116 | headerAlign: 'left', 117 | align: 'left', 118 | flex: 1, 119 | editable: true, 120 | }, 121 | { 122 | field: 'cpuUsage', 123 | headerName: 'CPU Usage (bytes)', 124 | headerAlign: 'center', 125 | align: 'center', 126 | flex: 1, 127 | editable: true, 128 | }, 129 | { 130 | field: 'cpuUtilPct', 131 | headerName: 'CPU Usage (%)', 132 | headerAlign: 'center', 133 | align: 'center', 134 | type: 'number', 135 | flex: 1, 136 | editable: true, 137 | valueGetter: (params) => { 138 | return params.value.toFixed(2); 139 | }, 140 | }, 141 | { 142 | field: 'memUsage', 143 | headerName: 'MEM Usage', 144 | headerAlign: 'center', 145 | align: 'center', 146 | type: 'number', 147 | flex: 1, 148 | editable: true, 149 | }, 150 | { 151 | field: 'memUtilPct', 152 | headerName: 'MEM Usage (%)', 153 | headerAlign: 'center', 154 | align: 'center', 155 | type: 'number', 156 | flex: 1, 157 | editable: true, 158 | valueGetter: (params) => { 159 | return params.value.toFixed(2); 160 | }, 161 | }, 162 | ]; 163 | 164 | const ListViewPage = () => { 165 | let cUsageData; 166 | let metricsState = useSelector((state: RootState) => state?.metricsMap); 167 | console.log('Metrics State ===>', metricsState?.metricsMap); 168 | const { data } = useGetContainerUsageQuery(undefined, {}); 169 | useGetClusterMetricsMapQuery(undefined, { pollingInterval: 5000 }); 170 | const { data: _clusterInfo } = useGetClusterInfoQuery(undefined, {}); 171 | 172 | if (data) { 173 | cUsageData = formatContainerUsage(data); 174 | } 175 | 176 | if (metricsState) { 177 | metricsState = formatMetricsMap(metricsState); 178 | } 179 | 180 | const capitalizeFirstLetter = (s: string) => { 181 | return s.charAt(0).toUpperCase() + s.slice(1); 182 | }; 183 | 184 | if ( 185 | !data || 186 | !metricsState.pod || 187 | !metricsState.container || 188 | !metricsState.node 189 | ) 190 | return; 191 | 192 | return ( 193 |
194 | 195 | 201 | 202 | 208 | 209 | 215 | 216 | 224 | 225 | 233 | 234 | 242 | 243 |
244 | ); 245 | }; 246 | 247 | export default ListViewPage; 248 | -------------------------------------------------------------------------------- /client/pages/NodeView/NodeView.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, Suspense } from "react"; 2 | // ! Review React docs regarding ErrorBoundary 3 | // TODO: Add error boundary 4 | // import { ErrorBoundary } from "react-error-boundary"; 5 | import { Box, useTheme } from "@mui/material"; 6 | import CircularProgress from "@mui/material/CircularProgress"; 7 | import { GridColDef } from "@mui/x-data-grid"; 8 | 9 | // Use lazy to defer loading component’s code until it is rendered for the first time. 10 | const LineChart = lazy(() => import("../../components/LineChart/LineChart")); 11 | const DataGridWithHeader = lazy( 12 | () => import("../../components/DataGridWithHeader/DataGridWithHeader") 13 | ); 14 | 15 | import { useGetNodeViewQuery } from "../../services/api"; 16 | 17 | import { 18 | CollapsiblePanel, 19 | // FlexBetween, 20 | // Header, 21 | StatBox, 22 | } from "../../components"; 23 | 24 | const columns: GridColDef[] = [ 25 | { 26 | field: "nodeName", 27 | headerName: "metadata.system.node_name", 28 | headerAlign: "left", 29 | flex: 1, 30 | align: "left", 31 | }, 32 | { 33 | field: "metricValue", 34 | headerName: "Latest Value", 35 | headerAlign: "center", 36 | width: 150, 37 | // flex: 1, 38 | align: "right", 39 | }, 40 | ]; 41 | 42 | const NodeView = () => { 43 | const theme = useTheme(); 44 | const { data, isLoading } = useGetNodeViewQuery(undefined, {}); 45 | console.log(data); 46 | let podsFormattedData = []; 47 | let containersFormattedData = []; 48 | 49 | if (data) { 50 | podsFormattedData = data.kube_pod_count_per_node.map( 51 | (item: any, index: number) => ({ 52 | id: index, 53 | metricName: item.metricName, 54 | nodeName: item.labels.node, 55 | metricValue: item.metricValue, 56 | }) 57 | ); 58 | 59 | containersFormattedData = data.kube_container_count_per_node.map( 60 | (item: any, index: number) => ({ 61 | id: index, 62 | nodeName: item.labels.node, 63 | metricValue: item.metricValue, 64 | }) 65 | ); 66 | } 67 | 68 | return ( 69 | 70 | 71 | 77 | {/* ROW 1 */} 78 | 87 | 91 | 95 | 99 | 103 | 107 | 108 | 109 | {/* ROW 2 */} 110 | 117 | 118 | }> 119 | 123 | 124 | }> 125 | 129 | 130 | 131 | 132 | 133 | {/* ROW 3 */} 134 | 140 | 141 | 147 | 153 | 154 | 155 | 156 | {/* ROW 4 */} 157 | 164 | 165 | }> 166 | 170 | 171 | }> 172 | 176 | 177 | 178 | 179 | 180 | {/* ROW 5 */} 181 | 188 | 189 | }> 190 | 194 | 195 | }> 196 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | ); 207 | }; 208 | 209 | export default NodeView; 210 | -------------------------------------------------------------------------------- /client/pages/NodeView/index.ts: -------------------------------------------------------------------------------- 1 | import NodeView from './NodeView'; 2 | export { NodeView } ; -------------------------------------------------------------------------------- /client/redux/bobbySocketService.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import{ io, Socket} from "socket.io-client"; 3 | import { addClusterEvent } from "./metricsSlice"; 4 | import store from './store' 5 | 6 | // Custom Hook for Socket.IO 7 | export const useSocket = (url: string): Socket | null => { 8 | 9 | const [socket, setSocket] = useState< Socket | null >(null); 10 | 11 | useEffect(() => { 12 | // Connect to Socket.IO server 13 | const newSocket: Socket = io(url); 14 | 15 | setSocket(newSocket); 16 | 17 | newSocket.on("connect", () => { 18 | console.log("Connected to Socket.IO server"); 19 | }); 20 | 21 | newSocket.on("disconnect", () => { 22 | console.log("Disconnected from Socket.IO server"); 23 | }); 24 | 25 | newSocket.on('podAdded', (data) => { 26 | console.log('Pod Added: ', data) 27 | data.eventType = 'Pod Added'; 28 | store.dispatch(addClusterEvent(data)) 29 | }) 30 | 31 | newSocket.on('podModified', (data) => { 32 | data.eventType = 'Pod Modified'; 33 | console.log('Pod Modified: ', data) 34 | store.dispatch(addClusterEvent(data)) 35 | }) 36 | 37 | newSocket.on('podDeleted', (data) => { 38 | data.eventType = 'Pod Deleted'; 39 | console.log('Pod Deleted: ', data) 40 | store.dispatch(addClusterEvent(data)) 41 | }) 42 | 43 | // return () =>{ newSocket.close(); } 44 | 45 | return () =>{ newSocket.disconnect();} 46 | 47 | }, [url]); 48 | 49 | return socket; 50 | }; -------------------------------------------------------------------------------- /client/redux/metricsApi.ts: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; 2 | import { Pod, Node, Container } from './metricsSlice' 3 | 4 | export const metricsApi = createApi({ 5 | reducerPath: 'metricsApi', 6 | baseQuery: fetchBaseQuery({ baseUrl: 'http://104.154.129.231:8000/' }), 7 | endpoints: (builder) => ({ 8 | getClusterInfo: builder.query({ 9 | query: () => 'clusterInfo', 10 | transformResponse: (response: any) => { 11 | 12 | let nodes: Node[] = []; 13 | let pods: Pod[] =[]; 14 | let containers: Container[] = []; 15 | 16 | response.forEach((node:Node) => { 17 | nodes.push({ ...node, id: node.nodeName}); 18 | node.pods.forEach((pod: Pod) => { 19 | pods.push({...pod, id: pod.name, nodeId: node.nodeName }); 20 | pod.containers.forEach((container: Container) => { 21 | containers.push({...container , id: container.name, podId: pod.name, }); 22 | }); 23 | }); 24 | }); 25 | 26 | return { 27 | nodes: nodes, 28 | pods: pods, 29 | containers: containers 30 | }; 31 | }, 32 | }), 33 | getClusterMetrics: builder.query({ 34 | query: () => 'clusterMetrics' 35 | }), 36 | getNodeStats: builder.query({ 37 | query: () => 'nodeStats' 38 | }), 39 | getPodStats: builder.query({ 40 | query: () => 'podStats' 41 | }), 42 | getClusterMetricsMap: builder.query({ 43 | query: () => 'clusterMetricsMap' 44 | }), 45 | getMessage: builder.query({ 46 | query: (channel) => `message/${channel}`, 47 | }) 48 | }), 49 | }); 50 | 51 | export const { useGetClusterInfoQuery, useGetNodeStatsQuery, useGetPodStatsQuery, useGetClusterMetricsMapQuery} = metricsApi; 52 | 53 | -------------------------------------------------------------------------------- /client/redux/metricsSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createEntityAdapter } from '@reduxjs/toolkit'; 2 | import { metricsApi } from './metricsApi' 3 | import {RootState} from './store' 4 | 5 | export interface Node { 6 | id: string; 7 | nodeName: string; 8 | pods: Pod[]; 9 | } 10 | export interface Pod { 11 | id: string; 12 | name: string; 13 | nodeId: string; 14 | containers: Container[]; 15 | } 16 | 17 | export interface Container { 18 | id: string; 19 | name: string; 20 | podId: string; 21 | image: string; 22 | } 23 | 24 | export interface Metrics { 25 | cpuUsage: number; 26 | memUsage: number; 27 | type: string; 28 | cpuUsagePct: number; 29 | } 30 | 31 | export interface Entity { 32 | 33 | } 34 | 35 | export interface ClusterEvent { 36 | apiVerson: string; 37 | kind: string; 38 | metadata: { uid: string;[key: string]: any }; 39 | entities: Entity[]; 40 | } 41 | 42 | export const nodesAdapter = createEntityAdapter(); 43 | export const podsAdapter = createEntityAdapter(); 44 | export const containersAdapter = createEntityAdapter(); 45 | export const metricsAdapter = createEntityAdapter(); 46 | export const clusterEventsAdapter = createEntityAdapter({ 47 | selectId: (clusterEvent) => clusterEvent.metadata.uid, 48 | }); 49 | 50 | export const nodesSlice = createSlice({ 51 | name: 'nodes', 52 | initialState: nodesAdapter.getInitialState(), 53 | reducers: {}, 54 | extraReducers: (builder) => { 55 | builder.addMatcher(metricsApi.endpoints.getClusterInfo.matchFulfilled, (state, { payload: { nodes } }) => { 56 | nodesAdapter.setAll(state, nodes); 57 | } 58 | ) 59 | }, 60 | }); 61 | 62 | export const podsSlice = createSlice({ 63 | name: 'pods', 64 | initialState: podsAdapter.getInitialState(), 65 | reducers: {}, 66 | extraReducers: (builder) => { 67 | builder.addMatcher(metricsApi.endpoints.getClusterInfo.matchFulfilled, (state, {payload: {pods}}) => { 68 | podsAdapter.setAll(state, pods); 69 | }) 70 | }, 71 | }); 72 | 73 | export const containersSlice = createSlice({ 74 | name: 'containers', 75 | initialState: containersAdapter.getInitialState(), 76 | reducers: {}, 77 | extraReducers: (builder) => { 78 | builder.addMatcher(metricsApi.endpoints.getClusterInfo.matchFulfilled, (state, { payload: { containers } }) => { 79 | containersAdapter.setAll(state, containers); 80 | }) 81 | }, 82 | }); 83 | 84 | export interface MetricsMap { 85 | metricsMap?: {[key: string]: any} 86 | } 87 | const initialState: any = { 88 | metricsMap: {} 89 | } 90 | 91 | export const metricsMapSlice = createSlice({ 92 | name: 'metricsMap', 93 | initialState: initialState , 94 | reducers: {}, 95 | extraReducers: (builder) => { 96 | builder.addMatcher(metricsApi.endpoints.getClusterMetricsMap.matchFulfilled, (state, payload) => { 97 | state.metricsMap = payload.payload; 98 | }) 99 | }, 100 | }); 101 | 102 | export const clusterEvents = createSlice({ 103 | name: 'clusterEvents', 104 | initialState: clusterEventsAdapter.getInitialState(), 105 | reducers: { 106 | addClusterEvent: clusterEventsAdapter.addOne, 107 | addClusterEvents: clusterEventsAdapter.addMany, 108 | removeClusterEvent: clusterEventsAdapter.removeOne, 109 | }, 110 | }); 111 | 112 | export const { addClusterEvent, addClusterEvents, removeClusterEvent } = clusterEvents.actions; 113 | // export const { selectAll: selectAllEvents, selectById: selectEventById} = clusterEventsAdapter.getSelectors(state => state.clusterEvents); 114 | export const { selectAll: selectAllNodes, selectById: selectNodeById } = nodesAdapter.getSelectors((state: RootState) => state.nodes); 115 | export const { selectAll: selectAllPods, selectById: selectPodById } = podsAdapter.getSelectors((state: RootState) => state.pods); 116 | export const { selectAll: selectAllContainers, selectById: selectContainerById } = containersAdapter.getSelectors((state: RootState) => state.containers); -------------------------------------------------------------------------------- /client/redux/socketService.ts: -------------------------------------------------------------------------------- 1 | import {io} from 'socket.io-client'; 2 | 3 | let socket; 4 | 5 | export const initializeSocket = () => { 6 | socket = io('http://104.154.129.231:8000/', { 7 | reconnection: true, 8 | transports: ['websocket', 'polling'] 9 | }); 10 | 11 | socket.on('connect', () => { 12 | console.log('Connected to server'); 13 | }); 14 | 15 | socket.on('message', (message) => { 16 | // ^ Update Redux store 17 | console.log('Message received: ', message); 18 | }) 19 | 20 | socket.on('disconnect', () => { 21 | console.log('Disconnected from server'); 22 | }); 23 | 24 | socket.on('podAdded', (data) => { 25 | console.log('Pod Added: ', data) 26 | }) 27 | socket.on('podModified', (data) => { 28 | console.log('Pod Modified: ', data) 29 | }) 30 | socket.on('podDeleted', (data) => { 31 | console.log('Pod Deleted: ', data) 32 | }) 33 | 34 | socket.on('error', (err) => { 35 | console.log(err); 36 | }); 37 | } 38 | 39 | export const disconnectSocket = () => { 40 | if (socket) { 41 | socket.disconnect(); 42 | } 43 | } -------------------------------------------------------------------------------- /client/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, combineReducers } from '@reduxjs/toolkit'; 2 | import { metricsApi } from './metricsApi'; 3 | import { nodesSlice, podsSlice, containersSlice,metricsMapSlice, clusterEvents } from './metricsSlice'; 4 | 5 | const rootReducer = combineReducers({ 6 | [metricsApi.reducerPath]: metricsApi.reducer, 7 | nodes: nodesSlice.reducer, 8 | pods: podsSlice.reducer, 9 | containers: containersSlice.reducer, 10 | metricsMap: metricsMapSlice.reducer, 11 | clusterEvents: clusterEvents.reducer 12 | }); 13 | 14 | const store = configureStore({ 15 | reducer: rootReducer, 16 | middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(metricsApi.middleware), 17 | }) 18 | 19 | export default store; 20 | 21 | export type RootState = ReturnType; -------------------------------------------------------------------------------- /client/routes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NodeView = React.lazy(() => import('./pages/NodeView/NodeView')); 4 | const ListView = React.lazy(() => import('./pages/ListViewPage/ListViewPage')); 5 | const Graph = React.lazy(() => import('./components/Graph/Graph')); 6 | 7 | const routes = [ 8 | { path: '/*', name: 'Home' }, 9 | { path: '/dashboard', name: 'Dashboard', element: Graph }, 10 | { path: '/node-view', name: 'Node View', element: NodeView }, 11 | { path: '/list-view', name: 'List View', element: ListView }, 12 | ]; 13 | 14 | export default routes; 15 | -------------------------------------------------------------------------------- /client/services/api.ts: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; 2 | import { Pod, Node, Container } from '../../types/types'; 3 | 4 | export const metricsApi = createApi({ 5 | reducerPath: 'metricsApi', 6 | baseQuery: fetchBaseQuery({ baseUrl: 'http://34.139.156.110:80/' }), 7 | endpoints: (builder) => ({ 8 | getClusterInfo: builder.query({ 9 | query: () => 'clusterInfo', 10 | transformResponse: (response: any) => { 11 | let nodes: Node[] = []; 12 | let pods: Pod[] = []; 13 | let containers: Container[] = []; 14 | 15 | response.nodes.forEach((node: Node) => { 16 | nodes.push({ ...node, id: node.nodeName }); 17 | node.pods.forEach((pod: Pod) => { 18 | pods.push({ ...pod, id: pod.name, nodeId: node.nodeName }); 19 | pod.containers.forEach((container: Container) => { 20 | containers.push({ 21 | ...container, 22 | id: container.name, 23 | podId: pod.name, 24 | }); 25 | }); 26 | }); 27 | }); 28 | 29 | return { 30 | nodes: nodes, 31 | pods: pods, 32 | containers: containers, 33 | serviceToPodsMapping: response.serviceToPodsMapping, 34 | namespaces: response.namespaces, 35 | }; 36 | }, 37 | }), 38 | getNodeView: builder.query({ 39 | queryFn: async () => { 40 | const response = await fetch('http://34.139.156.110:80/node-view'); 41 | const data = await response.json(); 42 | return { data: data }; 43 | }, 44 | }), 45 | 46 | getContainerUsage: builder.query({ 47 | queryFn: async () => { 48 | const response = await fetch('meteor-service/usage-metrics'); 49 | const data = await response.json(); 50 | console.log(data); 51 | return { data: data }; 52 | }, 53 | }), 54 | getClusterMetrics: builder.query({ 55 | query: () => 'clusterMetrics', 56 | }), 57 | getNodeStats: builder.query({ 58 | query: () => 'nodeStats', 59 | }), 60 | getPodStats: builder.query({ 61 | query: () => 'podStats', 62 | }), 63 | getClusterMetricsMap: builder.query({ 64 | query: () => 'clusterMetricsMap', 65 | }), 66 | getMessage: builder.query({ 67 | query: (channel) => `message/${channel}`, 68 | }), 69 | }), 70 | }); 71 | 72 | export const { 73 | useGetNodeViewQuery, 74 | useGetClusterInfoQuery, 75 | useGetNodeStatsQuery, 76 | useGetPodStatsQuery, 77 | useGetClusterMetricsMapQuery, 78 | useGetContainerUsageQuery, 79 | } = metricsApi; 80 | -------------------------------------------------------------------------------- /client/services/bobbySocketService.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { io, Socket } from 'socket.io-client'; 3 | import { addClusterEvent } from '../store/slice'; 4 | import { store } from '../store'; 5 | 6 | // Custom Hook for Socket.IO 7 | export const useSocket = (url: string): Socket | null => { 8 | const [socket, setSocket] = useState(null); 9 | 10 | useEffect(() => { 11 | // Connect to Socket.IO server 12 | const newSocket: Socket = io(url); 13 | setSocket(newSocket); 14 | 15 | newSocket.on('connect', () => { 16 | console.log('Connected to Socket.IO server'); 17 | }); 18 | 19 | newSocket.on('disconnect', () => { 20 | console.log('Disconnected from Socket.IO server'); 21 | }); 22 | 23 | newSocket.on('podAdded', (data) => { 24 | console.log('added POD'); 25 | 26 | data.eventType = 'Pod Added'; 27 | store.dispatch(addClusterEvent(data)); 28 | }); 29 | 30 | newSocket.on('podModified', (data) => { 31 | data.eventType = 'Pod Modified'; 32 | store.dispatch(addClusterEvent(data)); 33 | }); 34 | 35 | newSocket.on('podDeleted', (data) => { 36 | data.eventType = 'Pod Deleted'; 37 | store.dispatch(addClusterEvent(data)); 38 | }); 39 | 40 | return () => { 41 | newSocket.disconnect(); 42 | }; 43 | }, [url]); 44 | 45 | return socket; 46 | }; 47 | -------------------------------------------------------------------------------- /client/store/index.ts: -------------------------------------------------------------------------------- 1 | export {rootReducer, store } from './store'; -------------------------------------------------------------------------------- /client/store/slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createEntityAdapter } from "@reduxjs/toolkit"; 2 | import { metricsApi } from "../services/api"; 3 | import { RootState } from "../../types/types"; 4 | import { Node, Pod, Container, Metrics, ClusterEvent } from "../../types/types"; 5 | 6 | export const nodesAdapter = createEntityAdapter(); 7 | export const podsAdapter = createEntityAdapter(); 8 | export const containersAdapter = createEntityAdapter(); 9 | export const metricsAdapter = createEntityAdapter(); 10 | export const clusterEventsAdapter = createEntityAdapter({ 11 | selectId: (clusterEvent) => clusterEvent.metadata.resourceVersion, 12 | }); 13 | 14 | const initialAppState = { 15 | sidebarShow: false, 16 | sidebarUnfoldable: false, 17 | }; 18 | 19 | export const appState = createSlice({ 20 | name: "state", 21 | initialState: initialAppState, 22 | reducers: { 23 | changeState: (state, { payload }) => { 24 | console.log("changeState"); 25 | 26 | console.log(payload.type); 27 | switch (payload.type) { 28 | case "set": 29 | console.log("set"); 30 | return { ...state, ...payload }; 31 | default: 32 | console.log("default"); 33 | return state; 34 | } 35 | }, 36 | }, 37 | }); 38 | 39 | export const nodesSlice = createSlice({ 40 | name: "nodes", 41 | initialState: nodesAdapter.getInitialState(), 42 | reducers: {}, 43 | extraReducers: (builder) => { 44 | builder.addMatcher( 45 | metricsApi.endpoints.getClusterInfo.matchFulfilled, 46 | (state, { payload: { nodes } }) => { 47 | nodesAdapter.setAll(state, nodes); 48 | } 49 | ); 50 | }, 51 | }); 52 | 53 | export const podsSlice = createSlice({ 54 | name: "pods", 55 | initialState: podsAdapter.getInitialState(), 56 | reducers: {}, 57 | extraReducers: (builder) => { 58 | builder.addMatcher( 59 | metricsApi.endpoints.getClusterInfo.matchFulfilled, 60 | (state, { payload: { pods } }) => { 61 | podsAdapter.setAll(state, pods); 62 | } 63 | ); 64 | }, 65 | }); 66 | 67 | export const containersSlice = createSlice({ 68 | name: "containers", 69 | initialState: containersAdapter.getInitialState(), 70 | reducers: {}, 71 | extraReducers: (builder) => { 72 | builder.addMatcher( 73 | metricsApi.endpoints.getClusterInfo.matchFulfilled, 74 | (state, { payload: { containers } }) => { 75 | containersAdapter.setAll(state, containers); 76 | } 77 | ); 78 | }, 79 | }); 80 | 81 | export interface MetricsMap { 82 | metricsMap?: { [key: string]: any }; 83 | } 84 | const initialState: any = { 85 | metricsMap: {}, 86 | }; 87 | 88 | export const metricsMapSlice = createSlice({ 89 | name: "metricsMap", 90 | initialState: initialState, 91 | reducers: {}, 92 | extraReducers: (builder) => { 93 | builder.addMatcher( 94 | metricsApi.endpoints.getClusterMetricsMap.matchFulfilled, 95 | (state, payload) => { 96 | console.log("BANGERS", payload); 97 | state.metricsMap = payload.payload; 98 | } 99 | ); 100 | }, 101 | }); 102 | 103 | export const clusterEvents = createSlice({ 104 | name: "clusterEvents", 105 | initialState: clusterEventsAdapter.getInitialState(), 106 | reducers: { 107 | addClusterEvent: clusterEventsAdapter.addOne, 108 | addClusterEvents: clusterEventsAdapter.addMany, 109 | removeClusterEvent: clusterEventsAdapter.removeOne, 110 | }, 111 | }); 112 | 113 | export const { addClusterEvent, addClusterEvents, removeClusterEvent } = 114 | clusterEvents.actions; 115 | // export const { selectAll: selectAllEvents, selectById: selectEventById} = clusterEventsAdapter.getSelectors(state => state.clusterEvents); 116 | export const { selectAll: selectAllNodes, selectById: selectNodeById } = 117 | nodesAdapter.getSelectors((state: RootState) => state.nodes); 118 | export const { selectAll: selectAllPods, selectById: selectPodById } = 119 | podsAdapter.getSelectors((state: RootState) => state.pods); 120 | export const { 121 | selectAll: selectAllContainers, 122 | selectById: selectContainerById, 123 | } = containersAdapter.getSelectors((state: RootState) => state.containers); 124 | 125 | export const { changeState } = appState.actions; 126 | -------------------------------------------------------------------------------- /client/store/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore, combineReducers } from '@reduxjs/toolkit'; 2 | import { metricsApi } from '../services/api'; 3 | import { 4 | nodesSlice, 5 | podsSlice, 6 | containersSlice, 7 | metricsMapSlice, 8 | clusterEvents, 9 | appState, 10 | } from './slice'; 11 | 12 | export const rootReducer = combineReducers({ 13 | [metricsApi.reducerPath]: metricsApi.reducer, 14 | appState: appState.reducer, 15 | nodes: nodesSlice.reducer, 16 | pods: podsSlice.reducer, 17 | containers: containersSlice.reducer, 18 | metricsMap: metricsMapSlice.reducer, 19 | clusterEvents: clusterEvents.reducer, 20 | }); 21 | 22 | export const store = configureStore({ 23 | reducer: rootReducer, 24 | middleware: (getDefaultMiddleware) => 25 | getDefaultMiddleware().concat(metricsApi.middleware), 26 | }); 27 | -------------------------------------------------------------------------------- /client/theme.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | .dark { 4 | background-color: $background-color-dark; 5 | } 6 | 7 | .light { 8 | background-color: $background-color-light; 9 | } 10 | 11 | .textShadowLight { 12 | text-shadow: 2px 2px 2px black; 13 | } 14 | 15 | .textShadowLighter { 16 | text-shadow: 1px 1px 1px black; 17 | } 18 | 19 | .Opacity-In { 20 | animation: opacity 0.5s ease-in-out; 21 | animation-fill-mode: forwards; 22 | } 23 | 24 | .Opacity-Out { 25 | animation: opacity-reverse 0.5s ease-in-out; 26 | animation-fill-mode: backwards; 27 | } 28 | -------------------------------------------------------------------------------- /client/theme.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useMemo } from "react"; 2 | import { createTheme } from "@mui/material/styles"; 3 | 4 | type ThemeMode = "light" | "dark"; 5 | 6 | // export const tokens = (mode: ThemeMode) => ({ 7 | // ...(mode === 'dark' 8 | // ? { 9 | // primary: { 10 | // black: { 11 | // 100: '#d0d9e6', 12 | // 200: '#a1b3ce', 13 | // 300: '#738cb5', 14 | // 400: '#44669d', 15 | // 500: '#154084', 16 | // 600: '#11336a', 17 | // 700: '#0d264f', 18 | // 800: '#081a35', 19 | // 900: '#040d1a', 20 | // }, 21 | // }, 22 | // grey: { 23 | // 100: '#e0e0e0', 24 | // 200: '#c2c2c2', 25 | // 300: '#a3a3a3', 26 | // 400: '#858585', 27 | // 500: '#666666', 28 | // 600: '#525252', 29 | // 700: '#3d3d3d', 30 | // 800: '#292929', 31 | // 900: '#141414', 32 | // }, 33 | // greenAccent: { 34 | // 100: '#dbf5ee', 35 | // 200: '#b7ebde', 36 | // 300: '#94e2cd', 37 | // 400: '#70d8bd', 38 | // 500: '#4cceac', 39 | // 600: '#3da58a', 40 | // 700: '#2e7c67', 41 | // 800: '#1e5245', 42 | // 900: '#0f2922', 43 | // }, 44 | // redAccent: { 45 | // 100: '#f8dcdb', 46 | // 200: '#f1b9b7', 47 | // 300: '#e99592', 48 | // 400: '#e2726e', 49 | // 500: '#db4f4a', 50 | // 600: '#af3f3b', 51 | // 700: '#832f2c', 52 | // 800: '#58201e', 53 | // 900: '#2c100f', 54 | // }, 55 | // blueAccent: { 56 | // 100: '#e1e2fe', 57 | // 200: '#c3c6fd', 58 | // 300: '#a4a9fc', 59 | // 400: '#868dfb', 60 | // 500: '#6870fa', 61 | // 600: '#535ac8', 62 | // 700: '#3e4396', 63 | // 800: '#2a2d64', 64 | // 900: '#151632', 65 | // }, 66 | // } 67 | // : { 68 | // primary: { 69 | // 100: '#000000', 70 | // 200: '#080b12', 71 | // 300: '#0c101b', 72 | // 400: '#f2f0f0', 73 | // 500: '#141b2d', 74 | // 600: '#434957', 75 | // 700: '#727681', 76 | // 800: '#a1a4ab', 77 | // 900: '#ffffff', 78 | // }, 79 | // grey: { 80 | // 100: '#141414', 81 | // 200: '#292929', 82 | // 300: '#3d3d3d', 83 | // 400: '#525252', 84 | // 500: '#666666', 85 | // 600: '#858585', 86 | // 700: '#a3a3a3', 87 | // 800: '#c2c2c2', 88 | // 900: '#e0e0e0', 89 | // }, 90 | // greenAccent: { 91 | // 100: '#0f2922', 92 | // 200: '#1e5245', 93 | // 300: '#2e7c67', 94 | // 400: '#3da58a', 95 | // 500: '#4cceac', 96 | // 600: '#70d8bd', 97 | // 700: '#94e2cd', 98 | // 800: '#b7ebde', 99 | // 900: '#dbf5ee', 100 | // }, 101 | // redAccent: { 102 | // 100: '#2c100f', 103 | // 200: '#58201e', 104 | // 300: '#832f2c', 105 | // 400: '#af3f3b', 106 | // 500: '#db4f4a', 107 | // 600: '#e2726e', 108 | // 700: '#e99592', 109 | // 800: '#f1b9b7', 110 | // 900: '#f8dcdb', 111 | // }, 112 | // blueAccent: { 113 | // 100: '#151632', 114 | // 200: '#2a2d64', 115 | // 300: '#3e4396', 116 | // 400: '#535ac8', 117 | // 500: '#6870fa', 118 | // 600: '#868dfb', 119 | // 700: '#a4a9fc', 120 | // 800: '#c3c6fd', 121 | // 900: '#e1e2fe', 122 | // }, 123 | // }), 124 | // }); 125 | declare module "@mui/material/styles" { 126 | interface Theme { 127 | palette: { 128 | mode: ThemeMode; 129 | primary: { 130 | main?: string; 131 | alt?: string; 132 | altMain?: { 133 | 100?: string; 134 | 200?: string; 135 | 300?: string; 136 | 400?: string; 137 | 500?: string; 138 | 600?: string; 139 | 700?: string; 140 | 800?: string; 141 | 900?: string; 142 | }; 143 | }; 144 | secondary: { 145 | main: string; 146 | alt?: string; 147 | }; 148 | neutral: { 149 | dark?: string; 150 | main?: string; 151 | light?: string; 152 | }; 153 | background: { 154 | linechart: { 155 | main?: string; 156 | }; 157 | accordion: { 158 | main?: string; 159 | }; 160 | default?: string; 161 | alt?: string; 162 | inverted?: string; 163 | }; 164 | typography: { 165 | main?: string; 166 | inverted?: string; 167 | letters?: string; 168 | numbers?: string; 169 | }; 170 | }; 171 | status?: { 172 | danger?: string; 173 | }; 174 | } 175 | } 176 | 177 | // mui theme settings 178 | export const themeSettings = (mode: ThemeMode) => { 179 | // const colors = tokens(mode); 180 | return { 181 | palette: { 182 | mode: mode, 183 | ...(mode === "dark" 184 | ? { 185 | primary: { 186 | main: "#154084", // $base-blue 187 | alt: "#9d2719", // $base-red 188 | altMain: { 189 | 100: "#d0d9e6", 190 | 200: "#a1b3ce", 191 | 300: "#738cb5", 192 | 400: "#44669d", 193 | 500: "#154084", 194 | 600: "#11336a", 195 | 700: "#0d264f", 196 | 800: "#081a35", 197 | 900: "#040d1a", 198 | }, 199 | }, 200 | secondary: { 201 | main: "#188fff", // $accent-blue 202 | alt: "#e95f4d", // $accent-red 203 | }, 204 | neutral: { 205 | dark: "#222222", // $background-color-dark 206 | main: "#d0d0d0", // $background-color-light 207 | light: "#fcfcfc", // Default light background color 208 | }, 209 | background: { 210 | linechart: { 211 | main: "rgba(161,183,201, 0.06)", 212 | }, 213 | accordion: { 214 | main: "rgb(34, 35, 39)" 215 | }, 216 | default: "#20232A", // $background-color-dark 217 | alt: "#16181D", // $background-color-light 218 | inverted: "#fcfcfc", // Default light background color 219 | }, 220 | typography: { 221 | inverted: "black", 222 | main: "#FFFFFF", 223 | letters: "#FFFFFF", 224 | numbers: "#61DAFB", 225 | }, 226 | } 227 | : { 228 | primary: { 229 | main: "#154084", // $base-blue 230 | }, 231 | secondary: { 232 | main: "#188fff", // $accent-blue 233 | }, 234 | neutral: { 235 | dark: "#222222", // $background-color-dark 236 | main: "#d0d0d0", // $background-color-light 237 | light: "#fcfcfc", // Default light background color 238 | }, 239 | background: { 240 | linechart: { 241 | main: "white", 242 | }, 243 | accordion: { 244 | main: "white" 245 | }, 246 | default: "#fcfcfc", // Default light background color 247 | alt: "#ffffff", // $background-color-light 248 | inverted: "#222222", // $background-color-dark 249 | }, 250 | typography: { 251 | main: "#222222", 252 | }, 253 | }), 254 | }, 255 | typography: { 256 | fontFamily: ["Source Sans 3", "Baumans", "sans-serif"].join(","), 257 | fontSize: 16, 258 | h1: { 259 | fontFamily: ["Source Sans 3", "sans-serif"].join(","), 260 | fontSize: 40, 261 | }, 262 | h2: { 263 | fontFamily: ["Source Sans 3", "sans-serif"].join(","), 264 | fontSize: 32, 265 | }, 266 | h3: { 267 | fontFamily: ["Source Sans 3", "sans-serif"].join(","), 268 | fontSize: 24, 269 | }, 270 | h4: { 271 | fontFamily: ["Source Sans 3", "sans-serif"].join(","), 272 | fontSize: 20, 273 | }, 274 | h5: { 275 | fontFamily: ["Source Sans 3", "sans-serif"].join(","), 276 | fontSize: 16, 277 | }, 278 | h6: { 279 | fontFamily: ["Source Sans 3", "sans-serif"].join(","), 280 | fontSize: 14, 281 | }, 282 | }, 283 | }; 284 | }; 285 | 286 | // context for color mode 287 | export const ColorModeContext = createContext({ 288 | toggleColorMode: () => {}, 289 | }); 290 | 291 | export const useMode = () => { 292 | const [mode, setMode] = useState("dark"); 293 | 294 | const colorMode = useMemo( 295 | () => ({ 296 | toggleColorMode: () => { 297 | setMode((prev) => (prev === "light" ? "dark" : "light")); 298 | }, 299 | }), 300 | [] 301 | ); 302 | 303 | const theme = useMemo(() => createTheme(themeSettings(mode)), [mode]); 304 | 305 | return { theme, colorMode }; 306 | }; 307 | -------------------------------------------------------------------------------- /client/util/formatters/formatContainerUsage.tsx: -------------------------------------------------------------------------------- 1 | type containerUsageMetrics = { 2 | pod: any; 3 | namespace: any; 4 | node: any; 5 | }; 6 | 7 | type cUsageMetricsItem = { 8 | [key: string]: { 9 | MEM: number; 10 | CPU: number; 11 | }; 12 | }; 13 | 14 | type Cache = { 15 | node: any; 16 | pod: any; 17 | namespace: any; 18 | }; 19 | 20 | export const formatContainerUsage = (data: containerUsageMetrics): any => { 21 | const cache: Cache = { node: [], pod: [], namespace: [] }; 22 | if (data.pod) { 23 | data.pod.forEach((item: cUsageMetricsItem, idx: number) => { 24 | const key = Object.keys(item)[0]; 25 | const spreadPod = { name: key, id: idx, type: 'pod', ...item[key] }; 26 | cache.pod.push(spreadPod); 27 | }); 28 | } 29 | 30 | if (data.node) { 31 | data.node.forEach((item: cUsageMetricsItem, idx: number) => { 32 | const key = Object.keys(item)[0]; 33 | const spreadNode = { name: key, id: idx, type: 'node', ...item[key] }; 34 | cache.node.push(spreadNode); 35 | }); 36 | } 37 | 38 | if (data.namespace) { 39 | data.namespace.forEach((item: cUsageMetricsItem, idx: number) => { 40 | const key = Object.keys(item)[0]; 41 | const spreadNamespace = { 42 | name: key, 43 | id: idx, 44 | type: 'namespace', 45 | ...item[key], 46 | }; 47 | cache.namespace.push(spreadNamespace); 48 | }); 49 | } 50 | return cache; 51 | }; 52 | -------------------------------------------------------------------------------- /client/util/formatters/formatMetricsMap.tsx: -------------------------------------------------------------------------------- 1 | type ListViewDisplayProps = { 2 | metricsMap: any; 3 | }; 4 | 5 | type MetricsMapItem = { 6 | cpuUsage: number; 7 | cpuUsagePct: number; 8 | memUsage: number; 9 | memUsagePct: number; 10 | type: string; 11 | }; 12 | 13 | type Cache = { 14 | [key: string]: MetricsMapItem[]; 15 | }; 16 | 17 | export const formatMetricsMap = (data: ListViewDisplayProps): any => { 18 | const cache: Cache = {}; 19 | // console.log('inside Metrics', data) 20 | 21 | for (const item in data.metricsMap) { 22 | const dataObj: MetricsMapItem = { 23 | ...data.metricsMap[item], 24 | name: item, 25 | }; 26 | if (!cache[data.metricsMap[item].type]) { 27 | cache[data.metricsMap[item].type] = [dataObj]; 28 | } else { 29 | cache[data.metricsMap[item].type].push(dataObj); 30 | } 31 | } 32 | 33 | cache.pod = cache?.pod?.map((elm, i) => { 34 | return { ...elm, id: i }; 35 | }); 36 | 37 | cache.node = cache?.node?.map((elm, i) => { 38 | return { ...elm, id: i }; 39 | }); 40 | 41 | cache.container = cache?.container?.map((elm, i) => { 42 | return { ...elm, id: i }; 43 | }); 44 | 45 | return cache; 46 | }; 47 | -------------------------------------------------------------------------------- /client/util/formatters/formatNotifications.tsx: -------------------------------------------------------------------------------- 1 | // import { Notification } from '../../components'; 2 | 3 | // interface Props { 4 | // logs: { 5 | // [key: string]: { name: string , logText: string } 6 | // } 7 | // } 8 | // interface Notif { 9 | // name: string; 10 | // logText: string; 11 | // } 12 | 13 | // /** 14 | // * Converts the clusterEvent object in redux in notification JSX elements 15 | // * @param logs 16 | // * @returns A list of JSX elements 17 | // * 18 | // */ 19 | 20 | // export const formatNotifications = (logs: Props): JSX.Element[] => { 21 | // const result: Notif[] = []; 22 | 23 | // for (const obj in logs) { 24 | // const newLog: Notif = { 25 | // name: logs[obj].metadata.name, 26 | // logText: logs[obj].eventType, 27 | // }; 28 | 29 | // result.push(newLog); 30 | // } 31 | // const notifsArr = result.map((elm) => { 32 | // return ; 33 | // }); 34 | // return notifsArr; 35 | // }; 36 | -------------------------------------------------------------------------------- /client/util/formatters/index.ts: -------------------------------------------------------------------------------- 1 | export { formatNotifications } from './formatNotifications'; 2 | -------------------------------------------------------------------------------- /client/util/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | exitAnimations, 3 | formatLettersAsSpans, 4 | setAnimationDelay, 5 | setArrayAnimationDelay, 6 | setArrayScal, 7 | setScale, 8 | } from './animation'; 9 | export { formatNotifications } from './formatters'; 10 | -------------------------------------------------------------------------------- /client/util/timeUtils.ts: -------------------------------------------------------------------------------- 1 | export function convertMillisToTime(millis: number): string { 2 | if (typeof millis !== 'number' || isNaN(millis)) { 3 | return 'Invalid input'; 4 | } 5 | 6 | let seconds = Math.floor(millis / 1000); 7 | let minutes = Math.floor(seconds / 60); 8 | let hours = Math.floor(minutes / 60); 9 | 10 | seconds = seconds % 60; 11 | minutes = minutes % 60; 12 | 13 | const formattedHours = hours.toString().padStart(2, '0'); 14 | const formattedMinutes = minutes.toString().padStart(2, '0'); 15 | const formattedSeconds = seconds.toString().padStart(2, '0'); 16 | 17 | return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`; 18 | } 19 | 20 | console.log(convertMillisToTime(10000000)); // 02:46:40 21 | -------------------------------------------------------------------------------- /client/variables.scss: -------------------------------------------------------------------------------- 1 | $background-color-dark: #222222; 2 | $background-color-light: #d0d0d0; 3 | $base-blue: #154084; 4 | $base-red: #9d2719; 5 | $base-yellow: #d7b418; 6 | $base-orange: #f4a227; 7 | $accent-blue: #188fff; 8 | $accent-red: #ff4d4d; 9 | $accent-yellow: #f5d300; 10 | $accent-orange: #f4a227; 11 | $dark-border: rgba(0, 0, 0, 0.2); 12 | $light-border: rgba(255, 255, 255, 1); 13 | $dark-text: #ededed; 14 | $light-text: #222222; 15 | $graph-blue: #188fff; 16 | $graph-yellow: #f5d300; 17 | $diamond-blue: rgb(200, 230, 255); 18 | $unhealthy-pod: #ad4a39; 19 | $healthy-pod: #42a62b; 20 | $warning-pod: #e8c529; 21 | $pronounced-shadow: 4px 4px 1px 0 rgba(0, 0, 0, 0.5); 22 | $healthyGradient: linear-gradient(-45deg, #52caee, #3c53e7, #23a6d5, #23d5ab); 23 | $unhealthyGradient: linear-gradient(-45deg, #e65252, #e73c3c, #d52323, #ab2323); 24 | $warningGradient: linear-gradient(-45deg, #e6e652, #e7e73c, #d5d523, #abab23); 25 | 26 | @mixin flex-horizontal { 27 | display: flex; 28 | flex-direction: row; 29 | align-items: center; 30 | justify-content: center; 31 | } 32 | 33 | @mixin flex-vertical { 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | justify-content: center; 38 | } 39 | 40 | @mixin flex-center { 41 | display: flex; 42 | justify-content: center; 43 | align-items: center; 44 | } 45 | 46 | @mixin grid-base { 47 | display: grid; 48 | grid-template-columns: repeat(12, 1fr); 49 | grid-gap: 1rem; 50 | } 51 | 52 | @keyframes hover-up-down { 53 | 0% { 54 | transform: translateY(0px); 55 | } 56 | 50% { 57 | transform: translateY(-3px); 58 | } 59 | 100% { 60 | transform: translateY(0px); 61 | } 62 | } 63 | 64 | @keyframes opacity { 65 | 0% { 66 | opacity: 0; 67 | } 68 | 100% { 69 | opacity: 1; 70 | } 71 | } 72 | 73 | @keyframes opacity-reverse { 74 | 0% { 75 | opacity: 1; 76 | } 77 | 100% { 78 | opacity: 0; 79 | } 80 | } 81 | 82 | @keyframes hover-up-down-3D { 83 | 0% { 84 | transform: translateY(0px); 85 | } 86 | 50% { 87 | transform: translateY(-3.5px); 88 | } 89 | 100% { 90 | transform: translateY(0px); 91 | } 92 | } 93 | 94 | @keyframes navLinkBlueShadow { 95 | 0% { 96 | box-shadow: 0 0 0px #287aff; 97 | } 98 | 50% { 99 | box-shadow: 0 0 8px #287aff; 100 | text-shadow: 0px 0px 10px #287aff; 101 | } 102 | 100% { 103 | box-shadow: 0 0 0px #287aff; 104 | } 105 | } 106 | 107 | @keyframes slide-in-left { 108 | 0% { 109 | transform: translateX(-100%); 110 | } 111 | 112 | 100% { 113 | transform: translateX(0%); 114 | } 115 | } 116 | 117 | $inline-blue: #24b0df; 118 | $inline-red: #db3523; 119 | $inline-orange: #f4a227; 120 | $inline-white: #ededed; 121 | 122 | .inlineOrangeText { 123 | color: $inline-orange; 124 | text-shadow: 2px 2px 2px black; 125 | display: inline; 126 | } 127 | 128 | .inlineBlueText { 129 | color: $inline-blue; 130 | text-shadow: 2px 2px 2px black; 131 | display: inline; 132 | } 133 | 134 | .inlineRedText { 135 | color: $inline-red; 136 | text-shadow: 2px 2px 2px black; 137 | display: inline; 138 | } 139 | 140 | .inlineWhiteText { 141 | color: $inline-white; 142 | text-shadow: 2px 2px 2px black; 143 | display: inline; 144 | } 145 | 146 | .custom-container { 147 | margin-left: 0; 148 | margin-right: 0; 149 | max-width: 100%; 150 | height: 100%; 151 | } 152 | -------------------------------------------------------------------------------- /dist/client_pages_NodeView_NodeView_tsx.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"client_pages_NodeView_NodeView_tsx.bundle.js","mappings":";;;;;;;;;;;;;;;;;;AAAA;;AAE+B;AACiD;AACrB;;AAE3D;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,4BAA4B,2CAAc;AAC1C;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,GAAG;AACH,EAAE,oEAAiB;AACnB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;;AAEA;AACA,uCAAuC,kCAAK;AAC5C;AACA,6BAA6B,8CAAiB;AAC9C,4BAA4B,0CAAa;AACzC;AACA;AACA;AACA;AACA;AACA;AACA,QAAQ;AACR;AACA;AACA;AACA,GAAG;AACH,mCAAmC,0CAAa;AAChD;AACA,gDAAgD;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL,GAAG;AACH;AACA;AACA;AACe,+CAA+C;AAC9D,gBAAgB,uDAAQ;AACxB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAI,EAAE,uDAAa;AACnB;AACA;AACA;AACA,GAAG;AACH,MAAM,IAAqC;AAC3C;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,MAAM,IAAqC;AAC3C;AACA,IAAI,gDAAmB;AACvB;AACA;AACA,KAAK;AACL;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;AC5HuC;AACvC;AACA;AACA;AAC6D;AACC;AAG9D;AACA,IAAMM,SAAS,gBAAGN,2CAAI,CAAC;EAAA,OAAM,6TAA8C;AAAA,EAAC;AAC5E,IAAMO,kBAAkB,gBAAGP,2CAAI,CAC7B;EAAA,OAAM,mNAAgE;AAAA,CACxE,CAAC;AAEwD;AAO/B;AAAA;AAAA;AAE1B,IAAMiB,OAAqB,GAAG,CAC5B;EACEC,KAAK,EAAE,UAAU;EACjBC,UAAU,EAAE,2BAA2B;EACvCC,WAAW,EAAE,MAAM;EACnBC,IAAI,EAAE,CAAC;EACPC,KAAK,EAAE;AACT,CAAC,EACD;EACEJ,KAAK,EAAE,aAAa;EACpBC,UAAU,EAAE,cAAc;EAC1BC,WAAW,EAAE,QAAQ;EACrBG,KAAK,EAAE,GAAG;EACV;EACAD,KAAK,EAAE;AACT,CAAC,CACF;AAED,IAAME,QAAQ,GAAG,SAAXA,QAAQA,CAAA,EAAS;EACrB,IAAMC,KAAK,GAAGrB,yDAAQ,CAAC,CAAC;EAExB,IAAMsB,kBAAkB,GAAGvB,yDAAa,CAAC,qBAAqB,CAAC;EAC/D,IAAAwB,oBAAA,GAA4BnB,kEAAmB,CAACoB,SAAS,EAAE,CAAC,CAAC,CAAC;IAAtDC,IAAI,GAAAF,oBAAA,CAAJE,IAAI;IAAEC,SAAS,GAAAH,oBAAA,CAATG,SAAS;EACvBC,OAAO,CAACC,GAAG,CAACH,IAAI,CAAC;EACjB,IAAII,iBAAiB,GAAG,EAAE;EAC1B,IAAIC,uBAAuB,GAAG,EAAE;EAEhC,IAAIL,IAAI,EAAE;IACRI,iBAAiB,GAAGJ,IAAI,CAACM,uBAAuB,CAACC,GAAG,CAAC,UAACC,IAAS,EAAEC,KAAa;MAAA,OAAM;QAClFC,EAAE,EAAED,KAAK;QACTE,UAAU,EAAEH,IAAI,CAACG,UAAU;QAC3BC,QAAQ,EAAEJ,IAAI,CAACK,MAAM,CAACC,IAAI;QAC1BC,WAAW,EAAEP,IAAI,CAACO;MACpB,CAAC;IAAA,CAAC,CAAC;IAEHV,uBAAuB,GAAGL,IAAI,CAACgB,6BAA6B,CAACT,GAAG,CAC9D,UAACC,IAAS,EAAEC,KAAa;MAAA,OAAM;QAC7BC,EAAE,EAAED,KAAK;QACTG,QAAQ,EAAEJ,IAAI,CAACK,MAAM,CAACC,IAAI;QAC1BC,WAAW,EAAEP,IAAI,CAACO;MACpB,CAAC;IAAA,CACH,CAAC;EACH;EAEA,oBACE9B,sDAAA,CAACZ,qDAAG;IAAC4C,SAAS,EAAC,UAAU;IAACC,MAAM,EAAC,MAAM;IAAAC,QAAA,eACrChC,uDAAA,CAACd,qDAAG;MAAC+C,CAAC,EAAC,eAAe;MAAAD,QAAA,gBACpBlC,sDAAA,CAACJ,oDAAW;QAAAsC,QAAA,eACVlC,sDAAA,CAACH,+CAAM;UAACuC,KAAK,EAAC,YAAY;UAACC,QAAQ,EAAC;QAA+B,CAAE;MAAC,CAC3D,CAAC,eAEdnC,uDAAA,CAACd,qDAAG;QACFkD,EAAE,EAAC,MAAM;QACTC,OAAO,EAAC,MAAM;QACdC,mBAAmB,EAAC,aAAa;QACjCC,YAAY,EAAC,oBAAoB;QACjCC,GAAG,EAAC,MAAM;QACVC,EAAE,EAAE;UACF,SAAS,EAAE;YACTC,UAAU,EAAEhC,kBAAkB,GAAGE,SAAS,GAAG;UAC/C;QACF,CAAE;QAAAoB,QAAA,gBAGFhC,uDAAA,CAACd,qDAAG;UAACmD,OAAO,EAAC,MAAM;UAACC,mBAAmB,EAAC,gBAAgB;UAACE,GAAG,EAAC,MAAM;UAAAR,QAAA,gBACjElC,sDAAA,CAACF,gDAAO;YACNsC,KAAK,EAAC,aAAa;YACnBS,KAAK,EAAE9B,IAAI,IAAIA,IAAI,CAAC+B,gBAAgB,CAAChB;UAAY,CAClD,CAAC,eACF9B,sDAAA,CAACF,gDAAO;YACNsC,KAAK,EAAC,aAAa;YACnBS,KAAK,EAAE9B,IAAI,IAAIA,IAAI,CAACgC,gBAAgB,CAACjB;UAAY,CAClD,CAAC,eACF9B,sDAAA,CAACF,gDAAO;YACNsC,KAAK,EAAC,mBAAmB;YACzBS,KAAK,EAAE9B,IAAI,IAAIA,IAAI,CAACiC,4BAA4B,CAAClB;UAAY,CAC9D,CAAC,eACF9B,sDAAA,CAACF,gDAAO;YACNsC,KAAK,EAAC,cAAc;YACpBS,KAAK,EAAE9B,IAAI,IAAIA,IAAI,CAACkC,iBAAiB,CAACnB;UAAY,CACnD,CAAC,eACF9B,sDAAA,CAACF,gDAAO;YACNsC,KAAK,EAAC,oBAAoB;YAC1BS,KAAK,EAAE9B,IAAI,IAAIA,IAAI,CAACmC,6BAA6B,CAACpB;UAAY,CAC/D,CAAC;QAAA,CACC,CAAC,eAGN9B,sDAAA,CAACZ,qDAAG;UACF+D,SAAS,EAAC;UACV;UACA;UAAA;UACAR,EAAE,EAAE;YAAES,eAAe,EAAEzC,KAAK,CAAC0C,OAAO,CAACC,UAAU;UAAS,CAAE;UAC1DC,CAAC,EAAC,MAAM;UACRC,YAAY,EAAC,SAAS;UAAAtB,QAAA,eAEtBhC,uDAAA,CAACP,yDAAgB;YAACyC,KAAK,EAAC,gBAAgB;YAAAF,QAAA,gBACtClC,sDAAA,CAACb,2CAAQ;cAACsE,QAAQ,eAAEzD,sDAAA,CAACT,sEAAgB,IAAE,CAAE;cAAA2C,QAAA,eACvClC,sDAAA,CAACR,SAAS;gBACR4C,KAAK,EAAE,6BAA8B;gBACrCsB,GAAG,EAAE;cAAe,CACrB;YAAC,CACM,CAAC,eACX1D,sDAAA,CAACb,2CAAQ;cAACsE,QAAQ,eAAEzD,sDAAA,CAACT,sEAAgB,IAAE,CAAE;cAAA2C,QAAA,eACvClC,sDAAA,CAACR,SAAS;gBACR4C,KAAK,EAAE,gCAAiC;gBACxCsB,GAAG,EAAE;cAAY,CAClB;YAAC,CACM,CAAC;UAAA,CACK;QAAC,CAChB,CAAC,eAGN1D,sDAAA,CAACZ,qDAAG;UACF+D,SAAS,EAAC,KAAK;UACfR,EAAE,EAAE;YAAES,eAAe,EAAEzC,KAAK,CAAC0C,OAAO,CAACC,UAAU;UAAS,CAAE;UAC1DC,CAAC,EAAC,MAAM;UACRC,YAAY,EAAC,SAAS;UAAAtB,QAAA,eAEtBhC,uDAAA,CAACP,yDAAgB;YAACyC,KAAK,EAAC,yBAAyB;YAAAF,QAAA,gBAC/ClC,sDAAA,CAACP,kBAAkB;cACjB2C,KAAK,EAAE,WAAY;cACnBjC,OAAO,EAAEA,OAAQ;cACjBY,IAAI,EAAEI,iBAAkB;cACxBH,SAAS,EAAEA;YAAU,CACtB,CAAC,eACFhB,sDAAA,CAACP,kBAAkB;cACjB2C,KAAK,EAAE,iBAAkB;cACzBjC,OAAO,EAAEA,OAAQ;cACjBY,IAAI,EAAEK,uBAAwB;cAC9BJ,SAAS,EAAEA;YAAU,CACtB,CAAC;UAAA,CACc;QAAC,CAChB,CAAC,eAGNhB,sDAAA,CAACZ,qDAAG;UACF+D,SAAS,EAAC;UACV;UACA;UAAA;UACAR,EAAE,EAAE;YAAES,eAAe,EAAEzC,KAAK,CAAC0C,OAAO,CAACC,UAAU;UAAS,CAAE;UAC1DC,CAAC,EAAC,MAAM;UACRC,YAAY,EAAC,SAAS;UAAAtB,QAAA,eAEtBhC,uDAAA,CAACP,yDAAgB;YAACyC,KAAK,EAAC,WAAW;YAAAF,QAAA,gBACjClC,sDAAA,CAACb,2CAAQ;cAACsE,QAAQ,eAAEzD,sDAAA,CAACT,sEAAgB,IAAE,CAAE;cAAA2C,QAAA,eACvClC,sDAAA,CAACR,SAAS;gBACR4C,KAAK,EAAE,sCAAuC;gBAC9CsB,GAAG,EAAE;cAAoB,CAC1B;YAAC,CACM,CAAC,eACX1D,sDAAA,CAACb,2CAAQ;cAACsE,QAAQ,eAAEzD,sDAAA,CAACT,sEAAgB,IAAE,CAAE;cAAA2C,QAAA,eACvClC,sDAAA,CAACR,SAAS;gBACR4C,KAAK,EAAE,sCAAuC;gBAC9CsB,GAAG,EAAE;cAAa,CACnB;YAAC,CACM,CAAC;UAAA,CACK;QAAC,CAChB,CAAC,eAGN1D,sDAAA,CAACZ,qDAAG;UACF+D,SAAS,EAAC;UACV;UACA;UAAA;UACAR,EAAE,EAAE;YAAES,eAAe,EAAEzC,KAAK,CAAC0C,OAAO,CAACC,UAAU;UAAS,CAAE;UAC1DC,CAAC,EAAC,MAAM;UACRC,YAAY,EAAC,SAAS;UAAAtB,QAAA,eAEtBhC,uDAAA,CAACP,yDAAgB;YAACyC,KAAK,EAAC,iBAAiB;YAAAF,QAAA,gBACvClC,sDAAA,CAACb,2CAAQ;cAACsE,QAAQ,eAAEzD,sDAAA,CAACT,sEAAgB,IAAE,CAAE;cAAA2C,QAAA,eACvClC,sDAAA,CAACR,SAAS;gBACR4C,KAAK,EAAE,yCAA0C;gBACjDsB,GAAG,EAAE;cAAkB,CACxB;YAAC,CACM,CAAC,eACX1D,sDAAA,CAACb,2CAAQ;cAACsE,QAAQ,eAAEzD,sDAAA,CAACT,sEAAgB,IAAE,CAAE;cAAA2C,QAAA,eACvClC,sDAAA,CAACR,SAAS;gBACR4C,KAAK,EAAE,4CAA6C;gBACpDsB,GAAG,EAAE;cAAqB,CAC3B;YAAC,CACM,CAAC;UAAA,CACK;QAAC,CAChB,CAAC;MAAA,CACH,CAAC;IAAA,CACH;EAAC,CACH,CAAC;AAEV,CAAC;AAED,iEAAehD,QAAQ","sources":["webpack://faros-scope/./node_modules/@mui/material/useMediaQuery/useMediaQuery.js","webpack://faros-scope/./client/pages/NodeView/NodeView.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { getThemeProps, useThemeWithoutDefault as useTheme } from '@mui/system';\nimport useEnhancedEffect from '../utils/useEnhancedEffect';\n\n/**\n * @deprecated Not used internally. Use `MediaQueryListEvent` from lib.dom.d.ts instead.\n */\n\n/**\n * @deprecated Not used internally. Use `MediaQueryList` from lib.dom.d.ts instead.\n */\n\n/**\n * @deprecated Not used internally. Use `(event: MediaQueryListEvent) => void` instead.\n */\n\nfunction useMediaQueryOld(query, defaultMatches, matchMedia, ssrMatchMedia, noSsr) {\n const [match, setMatch] = React.useState(() => {\n if (noSsr && matchMedia) {\n return matchMedia(query).matches;\n }\n if (ssrMatchMedia) {\n return ssrMatchMedia(query).matches;\n }\n\n // Once the component is mounted, we rely on the\n // event listeners to return the correct matches value.\n return defaultMatches;\n });\n useEnhancedEffect(() => {\n let active = true;\n if (!matchMedia) {\n return undefined;\n }\n const queryList = matchMedia(query);\n const updateMatch = () => {\n // Workaround Safari wrong implementation of matchMedia\n // TODO can we remove it?\n // https://github.com/mui/material-ui/pull/17315#issuecomment-528286677\n if (active) {\n setMatch(queryList.matches);\n }\n };\n updateMatch();\n // TODO: Use `addEventListener` once support for Safari < 14 is dropped\n queryList.addListener(updateMatch);\n return () => {\n active = false;\n queryList.removeListener(updateMatch);\n };\n }, [query, matchMedia]);\n return match;\n}\n\n// eslint-disable-next-line no-useless-concat -- Workaround for https://github.com/webpack/webpack/issues/14814\nconst maybeReactUseSyncExternalStore = React['useSyncExternalStore' + ''];\nfunction useMediaQueryNew(query, defaultMatches, matchMedia, ssrMatchMedia, noSsr) {\n const getDefaultSnapshot = React.useCallback(() => defaultMatches, [defaultMatches]);\n const getServerSnapshot = React.useMemo(() => {\n if (noSsr && matchMedia) {\n return () => matchMedia(query).matches;\n }\n if (ssrMatchMedia !== null) {\n const {\n matches\n } = ssrMatchMedia(query);\n return () => matches;\n }\n return getDefaultSnapshot;\n }, [getDefaultSnapshot, query, ssrMatchMedia, noSsr, matchMedia]);\n const [getSnapshot, subscribe] = React.useMemo(() => {\n if (matchMedia === null) {\n return [getDefaultSnapshot, () => () => {}];\n }\n const mediaQueryList = matchMedia(query);\n return [() => mediaQueryList.matches, notify => {\n // TODO: Use `addEventListener` once support for Safari < 14 is dropped\n mediaQueryList.addListener(notify);\n return () => {\n mediaQueryList.removeListener(notify);\n };\n }];\n }, [getDefaultSnapshot, matchMedia, query]);\n const match = maybeReactUseSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n return match;\n}\nexport default function useMediaQuery(queryInput, options = {}) {\n const theme = useTheme();\n // Wait for jsdom to support the match media feature.\n // All the browsers MUI support have this built-in.\n // This defensive check is here for simplicity.\n // Most of the time, the match media logic isn't central to people tests.\n const supportMatchMedia = typeof window !== 'undefined' && typeof window.matchMedia !== 'undefined';\n const {\n defaultMatches = false,\n matchMedia = supportMatchMedia ? window.matchMedia : null,\n ssrMatchMedia = null,\n noSsr = false\n } = getThemeProps({\n name: 'MuiUseMediaQuery',\n props: options,\n theme\n });\n if (process.env.NODE_ENV !== 'production') {\n if (typeof queryInput === 'function' && theme === null) {\n console.error(['MUI: The `query` argument provided is invalid.', 'You are providing a function without a theme in the context.', 'One of the parent elements needs to use a ThemeProvider.'].join('\\n'));\n }\n }\n let query = typeof queryInput === 'function' ? queryInput(theme) : queryInput;\n query = query.replace(/^@media( ?)/m, '');\n\n // TODO: Drop `useMediaQueryOld` and use `use-sync-external-store` shim in `useMediaQueryNew` once the package is stable\n const useMediaQueryImplementation = maybeReactUseSyncExternalStore !== undefined ? useMediaQueryNew : useMediaQueryOld;\n const match = useMediaQueryImplementation(query, defaultMatches, matchMedia, ssrMatchMedia, noSsr);\n if (process.env.NODE_ENV !== 'production') {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n React.useDebugValue({\n query,\n match\n });\n }\n return match;\n}","import { lazy, Suspense } from 'react';\n// ! Review React docs regarding ErrorBoundary\n// TODO: Add error boundary\n// import { ErrorBoundary } from \"react-error-boundary\";\nimport { Box, useMediaQuery, useTheme } from '@mui/material';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport { GridColDef } from '@mui/x-data-grid';\n\n// Use lazy to defer loading component’s code until it is rendered for the first time.\nconst LineChart = lazy(() => import('../../components/LineChart/LineChart'));\nconst DataGridWithHeader = lazy(\n () => import('../../components/DataGridWithHeader/DataGridWithHeader'),\n);\n\nimport { useGetNodeViewQuery } from '../../services/api';\n\nimport {\n CollapsiblePanel,\n FlexBetween,\n Header,\n StatBox,\n} from '../../components';\n\nconst columns: GridColDef[] = [\n {\n field: 'nodeName',\n headerName: 'metadata.system.node_name',\n headerAlign: 'left',\n flex: 1,\n align: 'left',\n },\n {\n field: 'metricValue',\n headerName: 'Latest Value',\n headerAlign: 'center',\n width: 150,\n // flex: 1,\n align: 'right',\n },\n];\n\nconst NodeView = () => {\n const theme = useTheme();\n\n const isNonMediumScreens = useMediaQuery('(min-width: 1200px)');\n const { data, isLoading } = useGetNodeViewQuery(undefined, {});\n console.log(data);\n let podsFormattedData = [];\n let containersFormattedData = [];\n\n if (data) {\n podsFormattedData = data.kube_pod_count_per_node.map((item: any, index: number) => ({\n id: index,\n metricName: item.metricName,\n nodeName: item.labels.node,\n metricValue: item.metricValue,\n }));\n\n containersFormattedData = data.kube_container_count_per_node.map(\n (item: any, index: number) => ({\n id: index,\n nodeName: item.labels.node,\n metricValue: item.metricValue,\n }),\n );\n }\n\n return (\n \n \n \n
\n \n\n div': {\n gridColumn: isNonMediumScreens ? undefined : 'span 12',\n },\n }}\n >\n {/* ROW 1 */}\n \n \n \n \n \n \n \n\n {/* ROW 2 */}\n \n \n }>\n \n \n }>\n \n \n \n \n\n {/* ROW 3 */}\n \n \n \n \n \n \n\n {/* ROW 4 */}\n \n \n }>\n \n \n }>\n \n \n \n \n\n {/* ROW 5 */}\n \n \n }>\n \n \n }>\n \n \n \n \n \n \n \n );\n};\n\nexport default NodeView;\n"],"names":["lazy","Suspense","Box","useMediaQuery","useTheme","CircularProgress","LineChart","DataGridWithHeader","useGetNodeViewQuery","CollapsiblePanel","FlexBetween","Header","StatBox","jsx","_jsx","jsxs","_jsxs","columns","field","headerName","headerAlign","flex","align","width","NodeView","theme","isNonMediumScreens","_useGetNodeViewQuery","undefined","data","isLoading","console","log","podsFormattedData","containersFormattedData","kube_pod_count_per_node","map","item","index","id","metricName","nodeName","labels","node","metricValue","kube_container_count_per_node","className","height","children","m","title","subtitle","mt","display","gridTemplateColumns","gridAutoRows","gap","sx","gridColumn","value","kube_nodes_total","kube_total_cores","kube_total_allocatable_cores","kube_total_memory","kube_total_allocatable_memory","component","backgroundColor","palette","background","p","borderRadius","fallback","URL"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Faros 6 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /installation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: kubby-meteor-service-account 5 | --- 6 | apiVersion: rbac.authorization.k8s.io/v1 7 | kind: ClusterRole 8 | metadata: 9 | name: kubby-meteor-clusterRole 10 | rules: 11 | - apiGroups: 12 | - "" 13 | resources: 14 | - nodes 15 | - pods 16 | - services 17 | verbs: 18 | - get 19 | - list 20 | - watch 21 | - apiGroups: 22 | - metrics.k8s.io 23 | resources: 24 | - pods 25 | - nodes 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | --- 31 | apiVersion: rbac.authorization.k8s.io/v1 32 | kind: ClusterRoleBinding 33 | metadata: 34 | name: kubby-meteor-clusterrolebinding 35 | roleRef: 36 | apiGroup: rbac.authorization.k8s.io 37 | kind: ClusterRole 38 | name: kubby-meteor-clusterrole 39 | subjects: 40 | - kind: ServiceAccount 41 | name: kubby-meteor-service-account 42 | namespace: default 43 | --- 44 | apiVersion: apps/v1 45 | kind: Deployment 46 | metadata: 47 | labels: 48 | app: kubby-meteor 49 | name: kubby-meteor 50 | spec: 51 | replicas: 1 52 | selector: 53 | matchLabels: 54 | app: kubby-meteor 55 | strategy: {} 56 | template: 57 | metadata: 58 | labels: 59 | app: kubby-meteor 60 | spec: 61 | serviceAccountName: kubby-meteor-service-account 62 | containers: 63 | - name: kubby-meteor 64 | image: ndoolan/kubby-meteor:2.4 65 | ports: 66 | - containerPort: 8000 67 | resources: {} 68 | --- 69 | apiVersion: v1 70 | kind: Service 71 | metadata: 72 | labels: 73 | app: kubby-meteor 74 | name: kubby-meteor 75 | spec: 76 | ports: 77 | - port: 80 78 | protocol: TCP 79 | targetPort: 8000 80 | selector: 81 | app: kubby-meteor 82 | status: 83 | loadBalancer: {} 84 | --- 85 | apiVersion: v1 86 | kind: Pod 87 | metadata: 88 | labels: 89 | run: faros-scope 90 | name: faros-scope 91 | spec: 92 | containers: 93 | - env: 94 | - name: NODE_ENV 95 | value: prod 96 | image: marcoalagna/faros-scope:latest 97 | name: faros-scope 98 | ports: 99 | - containerPort: 3000 100 | resources: {} 101 | dnsPolicy: ClusterFirst 102 | restartPolicy: Always 103 | status: {} 104 | --- 105 | apiVersion: v1 106 | kind: Service 107 | metadata: 108 | labels: 109 | run: faros-scope 110 | name: faros-scope 111 | spec: 112 | ports: 113 | - port: 80 114 | protocol: TCP 115 | targetPort: 3000 116 | selector: 117 | run: faros-scope 118 | type: LoadBalancer 119 | status: 120 | loadBalancer: {} 121 | -------------------------------------------------------------------------------- /kubeConfigs/config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | - role: worker 6 | - role: worker 7 | -------------------------------------------------------------------------------- /kubeConfigs/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: nginx 10 | template: 11 | metadata: 12 | labels: 13 | app: nginx 14 | spec: 15 | containers: 16 | - name: nginx 17 | image: nginx:latest 18 | ports: 19 | - containerPort: 80 20 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["server"], 3 | "ext": "ts,json", 4 | "exec": "ts-node ./server/index.ts", 5 | "execMap": { 6 | "ts": "ts-node --project tsconfig.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "faros-scope", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index", 6 | "types": "index", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "buildClient": "webpack", 10 | "buildServer": "tsc -p tsconfig.json", 11 | "build": "npm run buildClient && npm run buildServer", 12 | "dev": "concurrently --kill-others \"nodemon ./server/index.ts\" \"webpack serve --mode development --env development\" ", 13 | "start": "node build/server/index.js" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/oslabs-beta/faros-scope.git" 18 | }, 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/oslabs-beta/faros-scope/issues" 23 | }, 24 | "homepage": "https://github.com/oslabs-beta/faros-scope#readme", 25 | "dependencies": { 26 | "@babel/register": "^7.22.15", 27 | "@coreui/chartjs": "^3.1.2", 28 | "@coreui/coreui": "^4.3.0", 29 | "@coreui/icons": "^3.0.1", 30 | "@coreui/icons-react": "^2.2.1", 31 | "@coreui/react": "^4.11.1", 32 | "@coreui/react-chartjs": "^2.1.3", 33 | "@coreui/utils": "^2.0.2", 34 | "@dnd-kit/core": "^6.1.0", 35 | "@emotion/react": "^11.11.3", 36 | "@emotion/styled": "^11.11.0", 37 | "@fontsource/roboto": "^5.0.8", 38 | "@mui/icons-material": "^5.15.2", 39 | "@mui/material": "^5.15.2", 40 | "@mui/system": "^5.15.5", 41 | "@mui/x-charts": "^6.19.1", 42 | "@mui/x-data-grid": "^6.19.1", 43 | "@mui/x-date-pickers": "^6.19.3", 44 | "@nivo/core": "^0.84.0", 45 | "@nivo/line": "^0.84.0", 46 | "@nivo/network": "^0.84.0", 47 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", 48 | "@popperjs/core": "^2.11.8", 49 | "@reduxjs/toolkit": "^1.9.7", 50 | "@types/express-validator": "^3.0.0", 51 | "@types/html-webpack-plugin": "^3.2.9", 52 | "@types/morgan": "^1.9.9", 53 | "@types/node": "^20.9.0", 54 | "@types/react-redux": "^7.1.30", 55 | "chart.js": "^4.4.1", 56 | "cookie-parser": "^1.4.6", 57 | "core-js": "^3.35.1", 58 | "cors": "^2.8.5", 59 | "css-loader": "^6.8.1", 60 | "dayjs": "^1.11.10", 61 | "dotenv": "^16.3.1", 62 | "express": "^4.18.2", 63 | "express-validator": "^7.0.1", 64 | "http-proxy-middleware": "^2.0.6", 65 | "install": "^0.13.0", 66 | "morgan": "^1.10.0", 67 | "npm": "^10.4.0", 68 | "react": "^18.2.0", 69 | "react-app-polyfill": "^3.0.0", 70 | "react-dom": "^18.2.0", 71 | "react-error-boundary": "^4.0.12", 72 | "react-icons": "^5.0.1", 73 | "react-redux": "^8.1.3", 74 | "react-router": "^6.18.0", 75 | "react-router-dom": "^6.18.0", 76 | "react-zoom-pan-pinch": "^3.4.1", 77 | "redux": "^4.2.1", 78 | "sass": "^1.69.5", 79 | "sass-loader": "^13.3.2", 80 | "simplebar": "^6.2.5", 81 | "simplebar-react": "^3.2.4", 82 | "socket.io-client": "^4.7.2", 83 | "style-loader": "^3.3.3" 84 | }, 85 | "devDependencies": { 86 | "@babel/core": "^7.23.3", 87 | "@babel/preset-env": "^7.23.3", 88 | "@babel/preset-react": "^7.23.3", 89 | "@babel/preset-typescript": "^7.23.3", 90 | "@types/css-modules": "^1.0.5", 91 | "@types/dotenv-webpack": "^7.0.7", 92 | "@types/express": "^4.17.21", 93 | "@types/http-proxy-middleware": "^1.0.0", 94 | "@types/react-dom": "^18.2.15", 95 | "@types/webpack": "^5.28.5", 96 | "@types/webpack-dev-server": "^4.7.2", 97 | "babel-loader": "^9.1.3", 98 | "classnames": "^2.5.1", 99 | "concurrently": "^8.2.2", 100 | "dotenv-webpack": "^8.0.1", 101 | "file-loader": "^6.2.0", 102 | "html-webpack-plugin": "^5.5.3", 103 | "nodemon": "^3.0.1", 104 | "postcss": "^8.4.31", 105 | "postcss-cli": "^10.1.0", 106 | "postcss-import": "^15.1.0", 107 | "postcss-loader": "^7.3.3", 108 | "postcss-nested": "^6.0.1", 109 | "postcss-simple-vars": "^7.0.1", 110 | "react-refresh": "^0.14.0", 111 | "react-refresh-webpack-plugin": "^0.1.0", 112 | "thread-loader": "^4.0.2", 113 | "ts-loader": "^9.5.1", 114 | "ts-node": "^10.9.1", 115 | "typescript": "^5.2.2", 116 | "typescript-plugin-css-modules": "^5.0.2", 117 | "webpack": "^5.89.0", 118 | "webpack-bundle-analyzer": "^4.10.1", 119 | "webpack-cli": "^5.1.4", 120 | "webpack-dev-server": "^4.15.1" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /postcss.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: [ 4 | require('postcss-simple-vars'), 5 | require('postcss-nested'), 6 | require('postcss-import') 7 | ] 8 | } 9 | 10 | module.exports = config -------------------------------------------------------------------------------- /server/controllers/metricsController.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Response, Request } from 'express'; 2 | 3 | 4 | const metricsController = { 5 | async getMetrics(_req: Request, res: Response, next: NextFunction) { 6 | try { 7 | 8 | 9 | const data = await fetch( 10 | 'http://localhost:9090/api/v1/query?query=kube_node_info', 11 | ); 12 | const json = await data.json(); 13 | res.locals.metrics = json; 14 | return next(); 15 | } catch (error) { 16 | return next({ 17 | log: 'Problem in MetricsController.getMetrics', 18 | status: 500, 19 | message: { err: 'Unknown Error Occurred' } 20 | }) 21 | } 22 | }, 23 | }; 24 | 25 | export default metricsController; 26 | -------------------------------------------------------------------------------- /server/index.ts: -------------------------------------------------------------------------------- 1 | import express, { Application, Request, Response, NextFunction } from 'express'; 2 | import history from 'connect-history-api-fallback'; 3 | import path from 'path'; 4 | import morgan from 'morgan'; 5 | import { createProxyMiddleware } from 'http-proxy-middleware'; 6 | // import Router from "./routers/router"; 7 | // import { Sequelize } from 'sequelize'; 8 | 9 | import 'dotenv/config'; 10 | 11 | const app: Application = express(); 12 | const PORT = process.env.PORT || 3000; 13 | const METEOR_SVC = 14 | process.env.NODE_ENV !== 'prod' 15 | ? 'http://34.139.156.110' 16 | : 'http://kubby-meteor.default.svc.cluster.local:80'; 17 | 18 | const PROM_SVC = 19 | process.env.NODE_ENV !== 'prod' 20 | ? 'http://35.227.104.153:31374' 21 | : 'http://my-prom-prometheus-server.default.svc.cluster.local:80'; 22 | 23 | // middleware 24 | app.use(morgan('dev')); 25 | app.use(express.json()); 26 | app.use(express.urlencoded({ extended: true })); 27 | 28 | app.use( 29 | '/meteor-service', 30 | createProxyMiddleware({ 31 | target: METEOR_SVC, 32 | changeOrigin: true, 33 | pathRewrite: { 34 | '^/meteor-service': '', 35 | }, 36 | }), 37 | ); 38 | 39 | app.use( 40 | '/prom-service', 41 | createProxyMiddleware({ 42 | target: PROM_SVC, 43 | changeOrigin: true, 44 | pathRewrite: { 45 | '^/prom-service': '', 46 | }, 47 | }), 48 | ); 49 | 50 | app.use(history()); 51 | app.use(express.static(path.join(__dirname, '../client'))); 52 | app.use(express.static(path.join(__dirname, '../build'))); 53 | app.use('/assets', express.static(path.join(__dirname, '../client/assets'))); 54 | 55 | // app.use("/api", Router); 56 | 57 | // catch all route handler 58 | app.use('*', (_req: Request, res: Response): void => { 59 | res.sendFile(path.join(__dirname, '../client/index.html')); 60 | }); 61 | 62 | // global error handler 63 | app.use( 64 | (err: Error, _req: Request, res: Response, _next: NextFunction): void => { 65 | const defaultError = { 66 | log: 'Express error handler caught unknown middleware error', 67 | status: 500, 68 | message: { err: 'An error occurred' }, 69 | }; 70 | const errorObj = { 71 | ...defaultError, 72 | ...(err instanceof Error ? { message: { err: err.message } } : err), 73 | }; 74 | console.log(errorObj.log); 75 | res.status(errorObj.status).json(errorObj.message); 76 | }, 77 | ); 78 | 79 | app.listen(PORT, () => { 80 | console.log(`Listening on PORT ${PORT}`); 81 | }); 82 | 83 | // export default sequelize; 84 | -------------------------------------------------------------------------------- /server/models/userModel.ts: -------------------------------------------------------------------------------- 1 | // import sequelize from './../index'; 2 | // import { DataTypes } from 'sequelize'; 3 | // import { User } from '../../types/types'; 4 | 5 | // User.init( 6 | // { 7 | // id: { 8 | // type: DataTypes.INTEGER.UNSIGNED, 9 | // autoIncrement: true, 10 | // primaryKey: true, 11 | // }, 12 | // firstName: { 13 | // type: DataTypes.STRING(255), 14 | // allowNull: false, 15 | // }, 16 | // lastName: { 17 | // type: DataTypes.STRING(255), 18 | // allowNull: false, 19 | // }, 20 | // email: { 21 | // type: DataTypes.STRING(255), 22 | // allowNull: false, 23 | // }, 24 | // password: { 25 | // type: DataTypes.STRING(255), 26 | // allowNull: false, 27 | // }, 28 | // }, 29 | // { , modelName: 'user' }, 30 | // ); 31 | -------------------------------------------------------------------------------- /server/routers/router.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import metricsController from '../controllers/metricsController'; 3 | const Router = express.Router(); 4 | 5 | Router.get('/metrics', metricsController.getMetrics, (_req, res) => { 6 | if (!res.locals?.metrics) res.status(500).send({message:'No metrics found'}); 7 | res.status(200).send(res.locals?.metrics); 8 | }); 9 | 10 | export default Router; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": ["es2015", "es2017", "dom"], 6 | "jsx": "react-jsx", 7 | "moduleResolution": "node", 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "strict": true, 12 | "allowSyntheticDefaultImports": true, 13 | "allowJs": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": false, 17 | "outDir": "./build", 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "plugins": [{ "name": "typescript-plugin-css-modules" }] 21 | }, 22 | "include": ["server/**/*.ts", "./**/*.tsx", "types", "client/index.tsx", "client/views/widgets/Widgets.js", "client/views/widgets/WidgetsDropdown.js", "client/views/widgets/WidgetsBrand.js", "client/views/dashboard/MainChart.js", "client/views/dashboard/Dashboard.js"], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /types/css.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.module.css' { 2 | const content: {[key: string]: string}; 3 | export default content; 4 | } -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.jpg'; 2 | declare module '*.png'; 3 | declare module '*.svg'; 4 | -------------------------------------------------------------------------------- /types/types.ts: -------------------------------------------------------------------------------- 1 | // import { Model, InferAttributes, InferCreationAttributes } from 'sequelize'; 2 | 3 | import { rootReducer } from '../client/store/store'; 4 | 5 | export type RootState = ReturnType; 6 | 7 | export interface Node { 8 | id: string; 9 | nodeName: string; 10 | pods: Pod[]; 11 | } 12 | export interface Pod { 13 | id: string; 14 | name: string; 15 | nodeId: string; 16 | namespace?: string; 17 | containers: Container[]; 18 | } 19 | 20 | export interface Container { 21 | id: string; 22 | name: string; 23 | podId: string; 24 | image: string; 25 | } 26 | 27 | export interface Metrics { 28 | cpuUsage: number; 29 | memUsage: number; 30 | type: string; 31 | cpuUsagePct: number; 32 | } 33 | 34 | export interface Entity {} 35 | 36 | export interface ClusterEvent { 37 | apiVerson: string; 38 | kind: string; 39 | metadata: { uid: string; [key: string]: any }; 40 | entities: Entity[]; 41 | } 42 | 43 | export interface IDefaultError { 44 | log: string; 45 | status: number; 46 | message: string | object; 47 | } 48 | 49 | export interface ThemeContextType { 50 | theme: string; 51 | toggleTheme: () => void; 52 | } 53 | 54 | // export class User extends Model< 55 | // InferAttributes, 56 | // InferCreationAttributes 57 | // > { 58 | // declare id: number; 59 | // declare firstName: string; 60 | // declare lastName: string; 61 | // declare email: string; 62 | // declare password: string; 63 | // } 64 | 65 | export interface IMetrics { 66 | metrics: {}; 67 | } 68 | -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 3 | import Dotenv from 'dotenv-webpack'; 4 | import { Configuration as WebpackConfiguration } from 'webpack'; 5 | import { Configuration as WepbackDevServerConfiguration } from 'webpack-dev-server'; 6 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; 7 | 8 | interface Configuration extends WebpackConfiguration { 9 | devServer?: WepbackDevServerConfiguration; 10 | } 11 | 12 | const config: Configuration = { 13 | entry: './client/index.tsx', 14 | mode: 'development', 15 | target: process.env.NODE_ENV !== 'production' ? 'web' : 'browserslist', 16 | output: { 17 | path: path.resolve(__dirname, 'build/client'), 18 | publicPath: '/', 19 | filename: 'bundle.js', 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.(j|t)sx?$/, 25 | exclude: /(node_modules)/, 26 | use: [ 27 | { 28 | loader: 'babel-loader', 29 | options: { 30 | presets: [ 31 | '@babel/preset-env', 32 | '@babel/preset-typescript', // separate TypeScript preset 33 | ['@babel/preset-react', { runtime: 'automatic' }], 34 | ], 35 | }, 36 | }, 37 | ], 38 | }, 39 | { 40 | test: /\.s?css$/i, 41 | use: [ 42 | 'style-loader', 43 | 'css-loader', 44 | { 45 | loader: 'postcss-loader', 46 | }, 47 | 'sass-loader', 48 | ], 49 | }, 50 | { 51 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 52 | type: 'asset/resource', 53 | }, 54 | ], 55 | }, 56 | resolve: { 57 | extensions: ['.tsx', '.ts', '.js'], 58 | }, 59 | devServer: { 60 | static: { 61 | publicPath: '/client', 62 | directory: path.resolve(__dirname, 'client'), 63 | }, 64 | // historyApiFallback: true, 65 | proxy: { 66 | '/': 'http://localhost:3000', 67 | }, 68 | port: 8080, 69 | hot: true, 70 | }, 71 | plugins: [ 72 | new HtmlWebpackPlugin({ 73 | template: './client/index.html', 74 | inject: false 75 | }), 76 | new Dotenv(), 77 | new ReactRefreshWebpackPlugin(), 78 | ], 79 | devtool: 'source-map', 80 | }; 81 | 82 | export default config; 83 | --------------------------------------------------------------------------------