├── .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 |
62 | Faros-Scope
63 |
64 |
65 |
66 |
67 |
86 |
103 |
104 |
105 |
126 |
134 |
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 |
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 |
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 |
--------------------------------------------------------------------------------