├── .gitignore ├── public ├── icon.ico ├── icon.icns ├── dmgbackground.tiff └── assets │ ├── container.png │ ├── kaptn4ico.png │ ├── Artboard-1.png │ ├── kraneSetupImg.png │ ├── kraneDashboardImg.png │ ├── kraneMetricsImg.png │ ├── kraneQuickStartImg2.png │ ├── Artboard 1.svg │ ├── deploy-2.svg │ ├── pod.svg │ ├── rs.svg │ ├── node.svg │ └── container.svg ├── src ├── .DS_Store ├── Pages │ ├── .DS_Store │ └── assets │ │ ├── container.png │ │ ├── kaptn4ico.png │ │ ├── kraneSetupImg.png │ │ ├── kraneMetricsImg.png │ │ ├── kraneDashboardImg.png │ │ ├── kraneQuickStartImg2.png │ │ ├── deploy-2.svg │ │ ├── pod.svg │ │ ├── rs.svg │ │ ├── node.svg │ │ └── container.svg ├── components │ ├── assets │ │ ├── container.png │ │ ├── kaptn4ico.png │ │ ├── kraneSetupImg.png │ │ ├── kraneMetricsImg.png │ │ ├── kraneDashboardImg.png │ │ ├── kraneQuickStartImg2.png │ │ ├── deploy-2.svg │ │ ├── pod.svg │ │ ├── rs.svg │ │ └── node.svg │ ├── Topbar.tsx │ ├── Terminal.tsx │ ├── commands.tsx │ ├── SetupCommandLine.tsx │ ├── Sidebar.tsx │ ├── DashboardCommandLine.tsx │ ├── PodCpuChart.tsx │ ├── NodeCpuChart.tsx │ ├── PodMemoryChart.tsx │ └── NodeMemoryChart.tsx ├── main.tsx ├── index.css ├── App.tsx └── theme.ts ├── babel.config.js ├── .editorconfig ├── __tests__ ├── components │ ├── Topbar.test.js │ ├── Sidebar.test.js │ ├── Terminal.test.js │ └── CommandLine.test.js └── pages │ ├── Dashboard.test.js │ └── Krane.test.js ├── tsconfig.json ├── vite.config.ts ├── index.html ├── electron-builder.json ├── CHANGELOG.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | dist/ 4 | .DS_Store 5 | **/.DS_Store -------------------------------------------------------------------------------- /public/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/icon.ico -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /public/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/icon.icns -------------------------------------------------------------------------------- /src/Pages/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/Pages/.DS_Store -------------------------------------------------------------------------------- /public/dmgbackground.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/dmgbackground.tiff -------------------------------------------------------------------------------- /public/assets/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/assets/container.png -------------------------------------------------------------------------------- /public/assets/kaptn4ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/assets/kaptn4ico.png -------------------------------------------------------------------------------- /public/assets/Artboard-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/assets/Artboard-1.png -------------------------------------------------------------------------------- /src/Pages/assets/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/Pages/assets/container.png -------------------------------------------------------------------------------- /src/Pages/assets/kaptn4ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/Pages/assets/kaptn4ico.png -------------------------------------------------------------------------------- /public/assets/kraneSetupImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/assets/kraneSetupImg.png -------------------------------------------------------------------------------- /public/assets/kraneDashboardImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/assets/kraneDashboardImg.png -------------------------------------------------------------------------------- /public/assets/kraneMetricsImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/assets/kraneMetricsImg.png -------------------------------------------------------------------------------- /src/Pages/assets/kraneSetupImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/Pages/assets/kraneSetupImg.png -------------------------------------------------------------------------------- /src/components/assets/container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/components/assets/container.png -------------------------------------------------------------------------------- /src/components/assets/kaptn4ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/components/assets/kaptn4ico.png -------------------------------------------------------------------------------- /public/assets/kraneQuickStartImg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/public/assets/kraneQuickStartImg2.png -------------------------------------------------------------------------------- /src/Pages/assets/kraneMetricsImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/Pages/assets/kraneMetricsImg.png -------------------------------------------------------------------------------- /src/Pages/assets/kraneDashboardImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/Pages/assets/kraneDashboardImg.png -------------------------------------------------------------------------------- /src/Pages/assets/kraneQuickStartImg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/Pages/assets/kraneQuickStartImg2.png -------------------------------------------------------------------------------- /src/components/assets/kraneSetupImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/components/assets/kraneSetupImg.png -------------------------------------------------------------------------------- /src/components/assets/kraneMetricsImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/components/assets/kraneMetricsImg.png -------------------------------------------------------------------------------- /src/components/assets/kraneDashboardImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/components/assets/kraneDashboardImg.png -------------------------------------------------------------------------------- /src/components/assets/kraneQuickStartImg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/kaptn/HEAD/src/components/assets/kraneQuickStartImg2.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | ['@babel/preset-react', { 5 | "runtime": "automatic" 6 | }], 7 | '@babel/preset-typescript', 8 | ], 9 | }; -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 80 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | 17 | [COMMIT_EDITMSG] 18 | max_line_length = 0 -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import './index.css'; 5 | import { ProSidebarProvider } from 'react-pro-sidebar'; 6 | 7 | ReactDOM.createRoot(document.getElementById('root')).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /__tests__/components/Topbar.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen, waitFor, fireEvent } from '@testing-library/react'; 3 | import Topbar from '../../src/components/Topbar'; 4 | import '@testing-library/jest-dom'; 5 | 6 | describe('Topbar test', () => { 7 | beforeEach(() => { 8 | 9 | render( 10 | 11 | ); 12 | }); 13 | 14 | it(`Renders logo`, () => { 15 | const topbar = screen.getByText('kaptn'); 16 | expect(topbar).toBeTruthy(); 17 | }) 18 | }) -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "jsx": "react-jsx", 6 | "esModuleInterop": true, 7 | "lib": ["dom", "es2021"], 8 | "allowJs": true, 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "strict": true, 12 | "moduleResolution": "node", 13 | "removeComments": true, 14 | "noEmit": true, 15 | "allowSyntheticDefaultImports": true, 16 | "noImplicitAny": false, 17 | "strictNullChecks": false 18 | }, 19 | "include": ["src/**/*", "server/**/*", "global.d.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /__tests__/components/Sidebar.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, screen, waitFor, fireEvent } from "@testing-library/react"; 3 | import SideNav from "../../src/components/Sidebar"; 4 | import "@testing-library/jest-dom"; 5 | import { BrowserRouter } from "react-router-dom"; 6 | 7 | describe("Sidebar test", () => { 8 | beforeEach(() => { 9 | render( 10 | 11 | 12 | 13 | ); 14 | }); 15 | 16 | it("Renders five buttons", () => { 17 | const links = screen.getAllByRole("link"); 18 | expect(links.length).toBe(5); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | export default defineConfig({ 5 | plugins: [react({ 6 | include: '**/*.{jsx,tsx}', 7 | })], 8 | base: './', 9 | server: { 10 | port: 4444, 11 | host: true, 12 | hmr: true, 13 | proxy: { 14 | '/api': { 15 | target: 'http://localhost:6666', 16 | changeOrigin: true, 17 | }, 18 | '/user': { 19 | target: 'http://localhost:6666', 20 | changeOrigin: true, 21 | }, 22 | '/prom-graf-setup': { 23 | target: 'http://localhost:6666', 24 | changeOrigin: true, 25 | }, 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Kaptn - Powerful Kubernetes Tool 8 | 9 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /electron-builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "Kaptn", 3 | "productName": "Kaptn", 4 | "directories": { 5 | "buildResources": "assets" 6 | }, 7 | "files": [ 8 | "package.json", 9 | "dist/**/*", 10 | "node_modules", 11 | "main.js", 12 | "src/assets/**/*" 13 | ], 14 | "mac": { 15 | "target": "dmg", 16 | "icon": "./public/icon.icns", 17 | "category": "public.app-category.developer-tools" 18 | }, 19 | "win": { 20 | "target": "portable", 21 | "icon": "./public/icon.ico", 22 | "requestedExecutionLevel": "asInvoker" 23 | }, 24 | "linux": { 25 | "target": "AppImage", 26 | "icon": "./public/icon.ico" 27 | }, 28 | "nsis": { 29 | "oneClick": false, 30 | "createDesktopShortcut": true, 31 | "createStartMenuShortcut": true, 32 | "shortcutName": "kaptn", 33 | "perMachine": false, 34 | "deleteAppDataOnUninstall": true, 35 | "allowElevation": false, 36 | "allowToChangeInstallationDirectory": true, 37 | "menuCategory": true, 38 | "installerLanguages": ["en_US"] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /__tests__/components/Terminal.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen, waitFor, fireEvent } from '@testing-library/react'; 3 | import Terminal from '../../src/components/Terminal'; 4 | import '@testing-library/jest-dom'; 5 | 6 | describe('Terminal test', () => { 7 | beforeEach(() => { 8 | 9 | const props = { 10 | response: [{id: 1, command: 'command1', response: 'response1'}, {id: 2, command: 'command2', response: 'response2'}] 11 | } 12 | 13 | render( 14 | 15 | ); 16 | }); 17 | 18 | it(`Renders both commands`, () => { 19 | const command1 = screen.getByText('$ command1'); 20 | const command2 = screen.getByText('$ command2'); 21 | expect(command1).toBeTruthy(); 22 | expect(command2).toBeTruthy(); 23 | }) 24 | 25 | it(`Renders both responses`, () => { 26 | const response1 = screen.getByText('response1'); 27 | const response2 = screen.getByText('response2'); 28 | expect(response1).toBeTruthy(); 29 | expect(response2).toBeTruthy(); 30 | }) 31 | }) -------------------------------------------------------------------------------- /__tests__/pages/Dashboard.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | const { ipcRenderer } = require("electron"); 3 | import { render, screen, waitFor, fireEvent } from "@testing-library/react"; 4 | import Dashboard from "../../src/Pages/Dashboard"; 5 | import "@testing-library/jest-dom"; 6 | import { ProSidebarProvider } from "react-pro-sidebar"; 7 | import { BrowserRouter } from "react-router-dom"; 8 | 9 | // Mock electron so we can use ipcRenderer 10 | jest.mock("electron", () => { 11 | const mElectron = { ipcRenderer: { on: jest.fn(), send: jest.fn() } }; 12 | return mElectron; 13 | }); 14 | 15 | describe("Dashboard test", () => { 16 | beforeEach(() => { 17 | render( 18 | // BrowserRouter is used here in order to render the sidebar component 19 | 20 | 21 | 22 | ); 23 | }); 24 | 25 | 26 | it(`Renders Kubectl toggle`, () => { 27 | const kubectl = screen.getByText("kubectl"); 28 | expect(kubectl).toBeTruthy(); 29 | }); 30 | 31 | it(`Renders Commands dropdown`, () => { 32 | const commands = screen.getByLabelText("Commands"); 33 | expect(commands).toBeTruthy(); 34 | }); 35 | 36 | it(`Renders Types dropdown`, () => { 37 | const types = screen.getByLabelText("Types"); 38 | expect(types).toBeTruthy(); 39 | }); 40 | 41 | it(`Renders Name dropdown`, () => { 42 | const name = screen.getByLabelText("Name"); 43 | expect(name).toBeTruthy(); 44 | }); 45 | 46 | it(`Renders Flags dropdown`, () => { 47 | const flags = screen.getByLabelText("Flags"); 48 | expect(flags).toBeTruthy(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /__tests__/pages/Krane.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | const { ipcRenderer } = require("electron"); 3 | import { render, screen, waitFor, fireEvent } from "@testing-library/react"; 4 | import Krane from "../../src/Pages/Krane"; 5 | import "@testing-library/jest-dom"; 6 | import { ProSidebarProvider } from "react-pro-sidebar"; 7 | import { BrowserRouter } from "react-router-dom"; 8 | 9 | // Mock electron so we can use ipcRenderer 10 | jest.mock("electron", () => { 11 | const mElectron = { ipcRenderer: { on: jest.fn(), send: jest.fn() } }; 12 | return mElectron; 13 | }); 14 | 15 | const props = { 16 | intervalArray : [], 17 | setIntervalArray : jest.fn() 18 | } 19 | props.intervalArray.map = jest.fn() 20 | 21 | 22 | describe("Krane test", () => { 23 | beforeEach(() => { 24 | render( 25 | // BrowserRouter is used here in order to render the sidebar component 26 | 27 | 28 | 29 | ); 30 | }); 31 | 32 | it("Renders seven Buttons", () => { 33 | const buttons = screen.getAllByRole("button"); 34 | expect(buttons.length).toBe(7); 35 | }); 36 | 37 | it(`Renders choose directory from above text`, () => { 38 | const choose = screen.getByText("Choose From Above to Begin"); 39 | expect(choose).toBeTruthy(); 40 | }); 41 | 42 | it(`Renders clusters not appearing text`, () => { 43 | const choose = screen.getByText("CLUSTERS STILL NOT APPEARING AFTER CHOOSING?"); 44 | expect(choose).toBeTruthy(); 45 | }); 46 | 47 | it("Renders custom config button", () => { 48 | const customKubeConfigButton = screen.getByRole("button", { name: "CLICK HERE TO LOAD A CUSTOM .KUBE/CONFIG FILE" }); 49 | expect(customKubeConfigButton).toBeTruthy(); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /src/components/Topbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Grid from "@mui/system/Unstable_Grid"; 3 | 4 | import { useTheme } from "@mui/material"; 5 | 6 | function Topbar(): JSX.Element { 7 | const theme = useTheme(); 8 | return ( 9 |
23 |
32 |
46 |
56 | kaptn 57 |
58 |
59 |
69 |
70 | ); 71 | } 72 | 73 | export default Topbar; 74 | -------------------------------------------------------------------------------- /__tests__/components/CommandLine.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { render, screen, waitFor, fireEvent } from "@testing-library/react"; 3 | import CommandLine from "../../src/components/DashboardCommandLine"; 4 | import "@testing-library/jest-dom"; 5 | 6 | describe("CommandLine tests", () => { 7 | let props = { 8 | handleSubmit: jest.fn((e) => e.preventDefault()), 9 | setUserInput: jest.fn(), 10 | setVerb: jest.fn(), 11 | setType: jest.fn(), 12 | setName: jest.fn(), 13 | setFlags: jest.fn() 14 | }; 15 | 16 | beforeEach(() => { 17 | render(); 18 | }); 19 | 20 | it("Renders 3 buttons", () => { 21 | const buttons = screen.getAllByRole("button"); 22 | expect(buttons.length).toBe(3); 23 | }); 24 | 25 | it("Renders one TextField", () => { 26 | const textField = screen.getAllByRole("textbox"); 27 | expect(textField.length).toBe(1); 28 | }); 29 | 30 | it("Renders Run as text value of run button", () => { 31 | const runButton = screen.getByRole("button", { name: "Run" }); 32 | expect(runButton.textContent).toBe("Run"); 33 | }); 34 | 35 | it("Renders Clear as text value of clear button", () => { 36 | const clearButton = screen.getByRole("button", { name: "Clear" }); 37 | expect(clearButton.textContent).toBe("Clear"); 38 | }); 39 | 40 | it(`Renders working directory field`, () => { 41 | const directory = screen.getByText("Working Directory"); 42 | expect(directory).toBeTruthy(); 43 | }); 44 | 45 | it("Runs a function when Run button is pressed", () => { 46 | const button = screen.getByRole("button", { name: "Run" }); 47 | fireEvent.click(button); 48 | expect(props.handleSubmit.mock.calls).toHaveLength(1); 49 | }); 50 | 51 | it("Clears input fields when Clear button is pressed", () => { 52 | const clearButton = screen.getByRole("button", { name: "Clear" }); 53 | fireEvent.click(clearButton); 54 | expect(props.setUserInput.mock.calls).toHaveLength(1); 55 | expect(props.setVerb.mock.calls).toHaveLength(1); 56 | expect(props.setType.mock.calls).toHaveLength(1); 57 | expect(props.setName.mock.calls).toHaveLength(1); 58 | expect(props.setFlags.mock.calls).toHaveLength(1); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /public/assets/Artboard 1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/components/Terminal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import Grid from "@mui/system/Unstable_Grid"; 3 | import { Button, useTheme } from "@mui/material"; 4 | 5 | const Terminal = (props) => { 6 | const theme = useTheme(); 7 | 8 | // Create a div for each command/response in the current session 9 | let commandLog: JSX.Element[] = []; 10 | let key = 1; 11 | 12 | // Format the shell response for line breaks and spacing 13 | // The
 tag here ensures proper spacing
14 |   props.response.forEach((el) => {
15 |     const paredResponse: JSX.Element[] = el.response
16 |       .split("\n")
17 |       .map(function (item: string) {
18 |         return (
19 |           
20 |             {item}
21 |           
22 | ); 23 | }); 24 | commandLog.push( 25 |
26 | 32 | {props.shortDir} $ {el.command} 33 | 34 | <>{paredResponse} 35 |
36 | ); 37 | key++; 38 | }); 39 | 40 | const handleClearLog = () => { 41 | props.setResponse([]); 42 | }; 43 | 44 | let clearButtonDiv; 45 | if (commandLog.length > 0) { 46 | clearButtonDiv = ( 47 |
64 | CLEAR 65 |
66 | ); 67 | } 68 | 69 | return ( 70 | 87 | {commandLog} 88 | {clearButtonDiv} 89 | 90 | ); 91 | }; 92 | 93 | export default Terminal; 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog for kaptn v2.0.1 and earlier. 2 | 3 | 4 | ## Version 2.0.1 - 5 | 6 | - Adds interactive, expandable visx graphs for pods' and nodes' historical cpu and memory usage. 7 | 8 | - Adds variable refresh rate. 9 | 10 | - Adds various other bugs fixes and additions including: Fixes bug with user directory in CLI 11 | 12 | 13 | ## Version 2.0.0 - 14 | 15 | - **Kaptn Krane Cluster Manager:** 16 | View live and historical metrics, and scale, delete or restart resources like pods, nodes, and deployments in our revolutionary, easy-to-use interface that harnesses the power of kubectl commands. Features including filtering by namespace, sorting by cpu and memory percent, one-click control of your clusters, and much more makes taking command of Kubernetes easier than ever before! 17 | 18 | - **New Start Page:** 19 | We've completely revamped the start page, including the addition of installation checks and quickstart links. Now you can troubleshoot problems more quickly, and get right into your workflow. 20 | 21 | - Adds various other bugs fixes and additions including: clear terminal log button, redesign of CLI, Instant Help Desk, Learning Center and much more! 22 | 23 | ## Version 1.2.0 - 24 | 25 | - Adds ability to use kubectl commands without choosing a working directory. 26 | 27 | ## Version 1.1.0 - 28 | 29 | - **_Now available for Mac, Windows, and Linux_** 30 | 31 | - **Cluster Metrics Visualizer:** 32 | Easily sync your Kaptn workspace to Grafana and Prometheus to allow for clear and real-time visualization of your clusters' health. Utilize our quick set-up if you are not already connected, and consider Kaptn your only stop for working with and monitoring your Kubernetes clusters. 33 | 34 | - **Instant Help Desk:** 35 | Get help information on demand and at the click of a button with the Instant Help Desk. Now you can get more info about any command or type without leaving the command line, and losing the code you've already written. 36 | 37 | - **Kaptn Learning Center:** 38 | Inside the Easy Setup page you can now find the Learning Center with resources you need to learn Kubernetes. You can follow tutorials, read articles and documentation, and master Kubernetes faster than ever. 39 | 40 | - **Light/Dark Mode:** 41 | Whether it's eye strain, or just personal preference, we know engineers can be selective about their work environments. So we created a Light/Dark mode that allows you to work with your favorite color combination. Now you can focus on coding with no distractions to your workflow. 42 | 43 | This update also includes various bugs fixes, including: 44 | 45 | - Bug where kubectl commands could not be used on some Mac operating systems. 46 | -------------------------------------------------------------------------------- /src/components/commands.tsx: -------------------------------------------------------------------------------- 1 | const commands = [ 2 | { title: 'create', category: 'Beginners Commands' }, 3 | { title: 'expose', category: 'Beginners Commands' }, 4 | { title: 'run', category: 'Beginners Commands' }, 5 | { title: 'set', category: 'Beginners Commands' }, 6 | { title: 'explain', category: 'Intermediate Commands' }, 7 | { title: 'get', category: 'Intermediate Commands' }, 8 | { title: 'edit', category: 'Intermediate Commands' }, 9 | { title: 'delete', category: 'Intermediate Commands' }, 10 | { title: 'rollout', category: 'Deploy Commands' }, 11 | { title: 'scale', category: 'Deploy Commands' }, 12 | { title: 'autoscale', category: 'Deploy Commands' }, 13 | { title: 'certificate', category: 'Cluster Management Commands' }, 14 | { title: 'cluster-info', category: 'Cluster Management Commands' }, 15 | { title: 'top', category: 'Cluster Management Commands' }, 16 | { title: 'cordon', category: 'Cluster Management Commands' }, 17 | { title: 'uncordon', category: 'Cluster Management Commands' }, 18 | { title: 'drain', category: 'Cluster Management Commands' }, 19 | { title: 'taint', category: 'Cluster Management Commands' }, 20 | { title: 'describe', category: 'Troubleshoot/Debug Commands' }, 21 | { title: 'logs', category: 'Troubleshoot/Debug Commands' }, 22 | { title: 'attach', category: 'Troubleshoot/Debug Commands' }, 23 | { title: 'exec', category: 'Troubleshoot/Debug Commands' }, 24 | { title: 'port-forward', category: 'Troubleshoot/Debug Commands' }, 25 | { title: 'proxy', category: 'Troubleshoot/Debug Commands' }, 26 | { title: 'cp', category: 'Troubleshoot/Debug Commands' }, 27 | { title: 'auth', category: 'Troubleshoot/Debug Commands' }, 28 | { title: 'debug', category: 'Troubleshoot/Debug Commands' }, 29 | { title: 'diff', category: 'Advanced Commands' }, 30 | { title: 'apply', category: 'Advanced Commands' }, 31 | { title: 'patch', category: 'Advanced Commands' }, 32 | { title: 'replace', category: 'Advanced Commands' }, 33 | { title: 'wait', category: 'Advanced Commands' }, 34 | { title: 'kustomize', category: 'Advanced Commands' }, 35 | { title: 'label', category: 'Settings Commands' }, 36 | { title: 'annotate', category: 'Settings Commands' }, 37 | { title: 'completion', category: 'Settings Commands' }, 38 | { title: 'alpha', category: 'Other Commands' }, 39 | { title: 'api-resources', category: 'Other Commands' }, 40 | { title: 'api-versions', category: 'Other Commands' }, 41 | { title: 'config', category: 'Other Commands' }, 42 | { title: 'plugin', category: 'Other Commands' }, 43 | { title: 'version', category: 'Other Commands' }, 44 | ]; 45 | 46 | export default commands -------------------------------------------------------------------------------- /src/components/SetupCommandLine.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | InputAdornment, 4 | Button, 5 | TextField, 6 | useTheme, 7 | Box, 8 | Grid, 9 | } from "@mui/material"; 10 | // import { clipboard } from 'electron'; 11 | 12 | const CommandLine = (props) => { 13 | const theme = useTheme(); 14 | 15 | // Add/remove functionality in text box 16 | const handleChange = (e) => { 17 | let newUserInput = ""; 18 | 19 | if (e.nativeEvent.inputType === "deleteContentBackward") { 20 | newUserInput = props.userInput.slice(0, props.userInput.length - 1); 21 | } else { 22 | newUserInput = 23 | props.userInput + e.target.value[e.target.value.length - 1]; 24 | } 25 | props.setUserInput(newUserInput); 26 | }; 27 | 28 | const handleClear = (e) => { 29 | let userInput2 = ""; 30 | 31 | props.setUserInput(userInput2); 32 | props.setVerb(userInput2); 33 | props.setType(userInput2); 34 | props.setName(userInput2); 35 | props.setFlags([]); 36 | }; 37 | 38 | // const handlePaste = (event) => { 39 | // let userInput = event.clipboardData.items[0].getAsString(); 40 | // // console.log(userInput); 41 | // props.setUserInput(userInput); 42 | // // props.setVerb(userInput); 43 | // // props.setType(userInput); 44 | // // props.setName(userInput); 45 | // // props.setFlags([]); 46 | // }; 47 | 48 | return ( 49 |
57 | {" "} 58 |
69 | 91 | $ 92 | 93 | ), 94 | }} 95 | onChange={(e) => { 96 | handleChange(e); 97 | }} 98 | value={props.command} 99 | // onPaste={(e) => { 100 | // handlePaste(e); 101 | // }} 102 | /> 103 | 119 | 138 | 139 |
140 | ); 141 | }; 142 | 143 | export default CommandLine; 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react", 3 | "productName": "Kaptn", 4 | "private": true, 5 | "version": "2.0.5", 6 | "description": "Powerful K8s Development Tool", 7 | "main": "main.js", 8 | "scripts": { 9 | "start": "NODE_ENV='development' electron .", 10 | "dev": "concurrently \"NODE_ENV='development' nodemon server/server.js\" \"NODE_ENV='development' vite dev\" --host 0.0.0.0", 11 | "build": "vite build", 12 | "build:mwl": "vite build && npm run pack", 13 | "build64": "vite build && npm run pack64", 14 | "build:windows": "npm run build && electron-builder --windows --x64", 15 | "build:winportable": "npm run build && electron-builder --win portable --x64", 16 | "build:mac": "npm run build && electron-builder --mac", 17 | "build:linux": "npm run build && electron-builder --linux --x64", 18 | "postinstall": "electron-builder install-app-deps", 19 | "pack": "electron-builder -mwl", 20 | "pack64": "electron-builder -mwl --x64", 21 | "preview": "vite preview", 22 | "vite": "vite", 23 | "test": "jest" 24 | }, 25 | "jest": { 26 | "testEnvironment": "jsdom" 27 | }, 28 | "author": { 29 | "name": "Kaptn", 30 | "url": "https://kaptn.io/" 31 | }, 32 | "contributors": [ 33 | "Brecht Horn", 34 | "Olivia Hodel", 35 | "Hwi Won Choi", 36 | "Natalie Cordoves", 37 | "Yining Wang" 38 | ], 39 | "license": "MIT", 40 | "dependencies": { 41 | "@emotion/react": "^11.10.6", 42 | "@emotion/styled": "^11.10.6", 43 | "@mui/icons-material": "^5.11.16", 44 | "@mui/material": "^5.12.0", 45 | "@visx/curve": "^3.3.0", 46 | "@visx/event": "^3.3.0", 47 | "@visx/gradient": "^3.3.0", 48 | "@visx/grid": "^3.3.0", 49 | "@visx/scale": "^3.3.0", 50 | "@visx/shape": "^3.3.0", 51 | "@visx/tooltip": "^3.3.0", 52 | "@visx/vendor": "^3.3.0", 53 | "concurrently": "^8.0.1", 54 | "fix-path": "^3.0.0", 55 | "nodemon": "^2.0.22", 56 | "react": "^18.2.0", 57 | "react-dom": "^18.2.0", 58 | "react-pro-sidebar": "^1.0.0", 59 | "react-router-dom": "^6.11.0" 60 | }, 61 | "devDependencies": { 62 | "@babel/core": "^7.21.8", 63 | "@babel/plugin-transform-runtime": "^7.21.4", 64 | "@babel/preset-env": "^7.21.5", 65 | "@babel/preset-react": "^7.18.6", 66 | "@babel/preset-typescript": "^7.21.5", 67 | "@testing-library/jest-dom": "^5.16.5", 68 | "@testing-library/react": "^14.0.0", 69 | "@types/node": "^18.16.0", 70 | "@types/react": "^18.0.38", 71 | "@types/react-dom": "^18.0.11", 72 | "@vitejs/plugin-react": "^3.1.0", 73 | "babel-jest": "^29.5.0", 74 | "electron": "^24.0.0", 75 | "electron-builder": "^24.4.0", 76 | "electron-packager": "^17.1.1", 77 | "esbuild": "^0.19.3", 78 | "https": "^1.0.0", 79 | "jest": "^29.5.0", 80 | "jest-environment-jsdom": "^29.5.0", 81 | "typescript": "^5.0.4", 82 | "vite": "^4.2.0" 83 | }, 84 | "build": { 85 | "appId": "Kaptn", 86 | "productName": "Kaptn", 87 | "asar": true, 88 | "win": { 89 | "target": "nsis", 90 | "icon": "./public/icon.ico", 91 | "requestedExecutionLevel": "asInvoker" 92 | }, 93 | "nsis": { 94 | "oneClick": false, 95 | "createDesktopShortcut": true, 96 | "createStartMenuShortcut": true, 97 | "shortcutName": "Kaptn", 98 | "perMachine": false, 99 | "deleteAppDataOnUninstall": true, 100 | "allowElevation": false, 101 | "allowToChangeInstallationDirectory": true, 102 | "menuCategory": true, 103 | "installerLanguages": [ 104 | "en_US" 105 | ] 106 | }, 107 | "mac": { 108 | "target": "dmg", 109 | "icon": "./public/icon.icns", 110 | "category": "public.app-category.developer-tools" 111 | }, 112 | "dmg": { 113 | "icon": "./public/icon.icns", 114 | "title": "Kaptn", 115 | "background": "./public/dmgbackground.tiff" 116 | }, 117 | "linux": { 118 | "icon": "./public/icon.icns", 119 | "target": "AppImage" 120 | }, 121 | "directories": { 122 | "buildResources": "assets" 123 | }, 124 | "files": [ 125 | "package.json", 126 | "dist/**/*", 127 | "node_modules", 128 | "main.js" 129 | ] 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"); 2 | 3 | body { 4 | top: 0px; 5 | right: 0px; 6 | bottom: 0px; 7 | left: 0px; 8 | margin: 0px; 9 | padding: 0px; 10 | overflow-x: hidden; 11 | } 12 | 13 | /* killport 300 link turns to hand when hovered */ 14 | #killport:hover { 15 | cursor: pointer; 16 | } 17 | 18 | /* make metrics install link a cursor on hover */ 19 | #metrics_install_link:hover { 20 | cursor: pointer; 21 | } 22 | 23 | /* Scrollbar */ 24 | ::-webkit-scrollbar { 25 | width: 8px; 26 | height: 7px; 27 | } 28 | 29 | /* Track */ 30 | ::-webkit-scrollbar-track { 31 | background: rgba(210, 223, 61, 0.129); 32 | } 33 | 34 | /* Handle */ 35 | ::-webkit-scrollbar-thumb { 36 | background: #716b88; 37 | border-radius: 2.5px; 38 | } 39 | 40 | /* Handle on Hover */ 41 | ::-webkit-scrollbar-track:hover { 42 | background: rgb(160, 160, 195, 0.2); 43 | } 44 | 45 | a { 46 | color: inherit; 47 | text-decoration: none; 48 | } 49 | 50 | #sidebarTopDiv { 51 | display: flex; 52 | flex-direction: column; 53 | position: fixed; 54 | top: 30px; 55 | left: 0px; 56 | width: 36px; 57 | height: 100%; 58 | /* margin: 0 30px 0 0; */ 59 | padding: 30px 0px 0 0; 60 | /* background-color: red; */ 61 | border-right: 10px; 62 | border-color: white; 63 | /* z-index: -1000; */ 64 | } 65 | 66 | .menuIcons:hover { 67 | background-color: #8383de70; 68 | border-radius: 9px; 69 | } 70 | 71 | #runButt:hover { 72 | /* border: 1.5px solid #685aef !important; */ 73 | /* color: #685aef !important; */ 74 | filter: brightness(120%); 75 | -webkit-filter: brightness(120%); 76 | } 77 | 78 | #clear-button:hover { 79 | /* border: 1.5px solid #685aef !important; */ 80 | /* color: #685aef !important; */ 81 | filter: brightness(120%); 82 | -webkit-filter: brightness(120%); 83 | border: 1px solid gray !important; 84 | } 85 | 86 | #nodeButt:hover { 87 | border: 1.2px solid #685aef !important; 88 | color: #685aef !important; 89 | } 90 | 91 | #podButt:hover { 92 | border: 1.3px solid #685aef !important; 93 | color: #685aef !important; 94 | } 95 | 96 | #podsSmallStat { 97 | font-size: 10px; 98 | justify-content: center !important; 99 | text-align: center; 100 | } 101 | 102 | /* #podsSmallStat:hover { */ 103 | /* color: white !important; */ 104 | /* } */ 105 | 106 | #subtitle { 107 | text-shadow: 1px 1px 5px rgb(0, 0, 0, 0.3) !important; 108 | } 109 | 110 | #yaml { 111 | transform: none; 112 | -webkit-transform: scale(0.9); 113 | -moz-transform: scale(0.9); 114 | } 115 | 116 | #terminal { 117 | overflow-anchor: none !important; 118 | } 119 | 120 | .clusterStatusIcons { 121 | transform: scale(6.8); 122 | } 123 | 124 | .clusterLoadingIcon { 125 | transform: scale(9.8); 126 | } 127 | 128 | .button3D-pushable { 129 | position: relative; 130 | border: none; 131 | background: transparent; 132 | padding: 0; 133 | cursor: pointer; 134 | outline-offset: 4px; 135 | transition: filter 250ms; 136 | user-select: none; 137 | -webkit-user-select: none; 138 | touch-action: manipulation; 139 | /* margin: 10px 10px 0 10px */ 140 | } 141 | 142 | .button3D-shadow { 143 | position: absolute; 144 | top: 0; 145 | left: 0; 146 | width: 100%; 147 | height: 100%; 148 | border-radius: 7px; 149 | background: hsl(0deg 0% 0% / 0.08); 150 | will-change: transform; 151 | transform: translateY(6px); 152 | transition: transform 600ms cubic-bezier(0.3, 0.7, 0.4, 1); 153 | } 154 | 155 | .button3D-edge { 156 | position: absolute; 157 | top: 0; 158 | left: 0; 159 | width: 100%; 160 | height: 100%; 161 | border-radius: 7px; 162 | /* background: linear-gradient( 163 | to left, 164 | hsl(239, 40%, 25%) 0%, 165 | hsl(239, 40%, 30%) 8%, 166 | hsl(239, 40%, 30%) 92%, 167 | hsl(239, 40%, 25%) 100% 168 | ); */ 169 | } 170 | 171 | .button3D-front { 172 | display: block; 173 | position: relative; 174 | padding: 12px 27px; 175 | border-radius: 7px; 176 | /* border: 1px solid rgb(29, 38, 127); */ 177 | font-size: 0.9rem; 178 | color: white; 179 | /* background: hsl(239, 38%, 51%); */ 180 | will-change: transform; 181 | transform: translateY(-6px); 182 | transition: transform 600ms cubic-bezier(0.3, 0.7, 0.4, 1); 183 | filter: brightness(100%); 184 | -webkit-filter: brightness(100%); 185 | font-family: Roboto; 186 | font-weight: 500; 187 | } 188 | 189 | .button3D-pushable:hover { 190 | filter: brightness(110%); 191 | -webkit-filter: brightness(110%); 192 | } 193 | 194 | .button3D-pushable:hover .button3D-front { 195 | transform: translateY(-4px); 196 | transition: transform 250ms cubic-bezier(0.3, 0.7, 0.4, 1.5); 197 | } 198 | 199 | .button3D-pushable:active .button3D-front { 200 | transform: translateY(-2px); 201 | transition: transform 34ms; 202 | } 203 | 204 | .button3D-pushable:hover .button3D-shadow { 205 | transform: translateY(4px); 206 | transition: transform 250ms cubic-bezier(0.3, 0.7, 0.4, 1.5); 207 | } 208 | 209 | .button3D-pushable:active .button3D-shadow { 210 | transform: translateY(1px); 211 | transition: transform 34ms; 212 | } 213 | 214 | .button3D-pushable:focus:not(:focus-visible) { 215 | outline: none; 216 | } 217 | 218 | -------------------------------------------------------------------------------- /src/components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useContext } from "react"; 3 | import { IconButton, useTheme } from "@mui/material"; 4 | import { Link } from "react-router-dom"; 5 | import { 6 | AutoFixHigh, 7 | ExitToAppOutlined, 8 | LightMode, 9 | BarChart, 10 | DarkMode, 11 | } from "@mui/icons-material"; 12 | import { ColorModeContext } from "../theme"; 13 | import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip"; 14 | import { styled } from "@mui/material/styles"; 15 | import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing"; 16 | import TerminalIcon from "@mui/icons-material/Terminal"; 17 | 18 | function SideNav(props) { 19 | // Color theme is toggled here with the light/dark mode menu item 20 | const theme = useTheme(); 21 | const colorMode = useContext(ColorModeContext); 22 | 23 | const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( 24 | 25 | ))(({ theme }) => ({ 26 | [`& .${tooltipClasses.tooltip}`]: { 27 | backgroundColor: theme.palette.mode === "dark" ? "#5c4d9a" : "#8383de", 28 | color: "white", 29 | fontSize: 11, 30 | }, 31 | })); 32 | 33 | return ( 34 |
40 | 49 |
52 | 53 | 60 | 61 |
62 |
63 | 64 | 73 |
76 | 77 | 84 | 85 |
86 |
87 | 88 | 96 |
99 | 100 | 107 | 108 |
109 |
110 | 111 | 119 |
120 | 121 | 128 | 129 |
130 |
131 | 132 | 140 |
141 | 142 | 149 | 150 |
151 |
152 | 160 |
169 | 170 | {theme.palette.mode === "dark" ? ( 171 | 172 | ) : ( 173 | 174 | )} 175 | 176 |
177 |
178 |
179 | ); 180 | } 181 | 182 | export default SideNav; 183 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import Start from "./Pages/Start.jsx"; 2 | import Cluster from "./Pages/Cluster.jsx"; 3 | import { HashRouter, Route, Routes } from "react-router-dom"; 4 | import Dashboard from "./Pages/Dashboard"; 5 | import KlusterManager from "./Pages/Krane"; 6 | import Topbar from "./components/Topbar.js"; 7 | import Setup from "./Pages/Setup.js"; 8 | import { ColorModeContext, useMode } from "./theme.js"; 9 | import { CssBaseline, ThemeProvider } from "@mui/material"; 10 | import React, { useState } from "react"; 11 | 12 | function App() { 13 | const [theme, colorMode] = useMode(); 14 | 15 | const [promGrafCheckStatus, setPromGrafCheckStatus] = useState("checking"); 16 | const [grafVersion, setGrafVersion] = useState(""); 17 | const [promVersion, setPromVersion] = useState(""); 18 | 19 | const [podsStatsObj, setPodsStatsObj] = useState({}); 20 | const [nodesStatsObj, setNodesStatsObj] = useState({}); 21 | const [intervalArray, setIntervalArray] = useState([]); 22 | 23 | 24 | // Hash router is used here to optimize for static file serving from Electron 25 | // More information here: https://reactrouter.com/en/main/router-components/hash-router 26 | return ( 27 | 28 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | 52 | } 53 | /> 54 | 71 | } 72 | /> 73 | 91 | } 92 | /> 93 | 111 | } 112 | /> 113 | 130 | } 131 | /> 132 | 133 |
134 |
135 |
136 |
137 |
138 | ); 139 | } 140 | 141 | export default App; 142 | -------------------------------------------------------------------------------- /src/theme.ts: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useMemo } from 'react'; 2 | import { createTheme } from '@mui/material/styles'; 3 | 4 | // set up color scheme using tailwind shades extension 5 | const tokens = (mode) => ({ 6 | ...(mode === 'dark' 7 | ? { 8 | gray: { 9 | 100: '#5c4d9a', 10 | 200: '#5c4d9a', 11 | 300: '#5c4d9a', 12 | 400: '#5c4d9a', 13 | 500: '#5c4d9a', 14 | 600: '#5c4d9a', 15 | 700: '#5c4d9a', 16 | 800: '#5c4d9a', 17 | 900: '#5c4d9a', 18 | }, 19 | 20 | // a primary dark color 21 | primary: { 22 | 100: '#d0ced7', 23 | 200: '#a09caf', 24 | 300: '#8f85fb', 25 | 400: 'lightblue', 26 | 500: '#120838', 27 | 600: '#0e062d', 28 | 700: '#0b0522', 29 | 800: '#070316', 30 | 900: '#04020b', 31 | }, 32 | 33 | // an accent 34 | greenAccent: { 35 | 100: '#d3f0d8', 36 | 200: '#a7e1b1', 37 | 300: '#7ad18a', 38 | 400: '#4ec263', 39 | 500: '#22b33c', 40 | 600: '#1b8f30', 41 | 700: '#146b24', 42 | 800: '#0e4818', 43 | 900: '#07240c', 44 | }, 45 | 46 | // a dff accent 47 | yellowAccent: { 48 | 100: '#f8f7d6', 49 | 200: '#f1eeac', 50 | 300: '#e9e683', 51 | 400: '#e2dd59', 52 | 500: '#dbd530', 53 | 600: '#afaa26', 54 | 700: '#83801d', 55 | 800: '#585513', 56 | 900: '#2c2b0a', 57 | }, 58 | 59 | // another accent 60 | pinkAccent: { 61 | 100: '#fbe4f7', 62 | 200: '#f8c9ef', 63 | 300: '#f4afe8', 64 | 400: '#f194e0', 65 | 500: '#ed79d8', 66 | 600: '#be61ad', 67 | 700: '#8e4982', 68 | 800: '#5f3056', 69 | 900: '#2f182b', 70 | }, 71 | } 72 | : { 73 | gray: { 74 | 100: '#252627', 75 | 200: '#4a4c4e', 76 | 300: '#6e7274', 77 | 400: '#93989b', 78 | 500: '#b8bec2', 79 | 600: '#c6cbce', 80 | 700: '#d4d8da', 81 | 800: '#e3e5e7', 82 | 900: '#f1f2f3', 83 | }, 84 | 85 | // a primary dark color 86 | primary: { 87 | 100: '#595ce3', 88 | 200: '#070316', 89 | 300: '#0b0522', 90 | 400: '#0e062d', 91 | 500: '#120838', 92 | 600: '#413960', 93 | 700: '#716b88', 94 | 800: '#a09caf', 95 | 900: '#d0ced7', 96 | }, 97 | 98 | // an accent 99 | greenAccent: { 100 | 100: '#07240c', 101 | 200: '#0e4818', 102 | 300: '#146b24', 103 | 400: '#1b8f30', 104 | 500: '#22b33c', 105 | 600: '#4ec263', 106 | 700: '#7ad18a', 107 | 800: '#a7e1b1', 108 | 900: '#d3f0d8', 109 | }, 110 | 111 | // a dff accent 112 | yellowAccent: { 113 | 100: '#2c2b0a', 114 | 200: '#585513', 115 | 300: '#83801d', 116 | 400: '#afaa26', 117 | 500: '#dbd530', 118 | 600: '#e2dd59', 119 | 700: '#e9e683', 120 | 800: '#f1eeac', 121 | 900: '#f8f7d6', 122 | }, 123 | 124 | // another accent 125 | pinkAccent: { 126 | 100: '#2f182b', 127 | 200: '#5f3056', 128 | 300: '#8e4982', 129 | 400: '#be61ad', 130 | 500: '#ed79d8', 131 | 600: '#f194e0', 132 | 700: '#f4afe8', 133 | 800: '#f8c9ef', 134 | 900: '#fbe4f7', 135 | }, 136 | }), 137 | }); 138 | 139 | // mui theme settings 140 | const themeSettings = (mode) => { 141 | const colors = tokens(mode); 142 | 143 | return { 144 | palette: { 145 | mode: mode, 146 | ...(mode === 'dark' 147 | ? { 148 | primary: { 149 | main: colors.primary[300], 150 | }, 151 | secondary: { 152 | main: colors.greenAccent[500], 153 | }, 154 | neutral: { 155 | dark: colors.gray[700], 156 | main: colors.gray[500], 157 | light: colors.gray[100], 158 | }, 159 | background: { 160 | default: colors.primary[500], 161 | }, 162 | } 163 | : { 164 | primary: { 165 | main: colors.primary[100], 166 | }, 167 | secondary: { 168 | main: colors.primary[500], 169 | }, 170 | neutral: { 171 | dark: colors.gray[700], 172 | main: colors.gray[500], 173 | light: colors.gray[100], 174 | }, 175 | background: { 176 | default: '#f6f4fe', 177 | }, 178 | }), 179 | }, 180 | typography: { 181 | fontFamily: ['Roboto', 'sans-serif'].join(','), 182 | fontSize: 12, 183 | h1: { 184 | fontFamily: ['Roboto', 'sans-serif'].join(','), 185 | fontSize: 40, 186 | }, 187 | h2: { 188 | fontFamily: ['Roboto', 'sans-serif'].join(','), 189 | fontSize: 32, 190 | }, 191 | h3: { 192 | fontFamily: ['Roboto', 'sans-serif'].join(','), 193 | fontSize: 24, 194 | }, 195 | h4: { 196 | fontFamily: ['Roboto', 'sans-serif'].join(','), 197 | fontSize: 20, 198 | }, 199 | h5: { 200 | fontFamily: ['Roboto', 'sans-serif'].join(','), 201 | fontSize: 16, 202 | }, 203 | h6: { 204 | fontFamily: ['Roboto', 'sans-serif'].join(','), 205 | fontSize: 14, 206 | }, 207 | }, 208 | }; 209 | }; 210 | 211 | // create a context so that we can have easy access to the condition of whether it's dark or light, 212 | // and allow us to provide the function that changes it 213 | const ColorModeContext = createContext({ 214 | toggleColorMode: () => {}, 215 | }); 216 | 217 | const useMode = () => { 218 | const [mode, setMode] = useState<'light' | 'dark'>('dark'); 219 | 220 | const colorMode = useMemo( 221 | () => ({ 222 | toggleColorMode: () => 223 | setMode((prev) => (prev === 'light' ? 'dark' : 'light')), 224 | }), 225 | [] 226 | ); 227 | // create the theme from material UI and passing mode into our theme setting 228 | const theme = useMemo(() => createTheme(themeSettings(mode)), [mode]); 229 | 230 | return [theme, colorMode] as const; 231 | }; 232 | 233 | export { useMode, ColorModeContext, tokens }; 234 | -------------------------------------------------------------------------------- /public/assets/deploy-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | deploy 90 | 93 | 98 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/Pages/assets/deploy-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | deploy 90 | 93 | 98 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /src/components/assets/deploy-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | deploy 90 | 93 | 98 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /public/assets/pod.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | pod 90 | 93 | 100 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/Pages/assets/pod.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | pod 90 | 93 | 100 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/components/assets/pod.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | pod 90 | 93 | 100 | 107 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /public/assets/rs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | rs 90 | 93 | 98 | 103 | 108 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/Pages/assets/rs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | rs 90 | 93 | 98 | 103 | 108 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/components/assets/rs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | rs 90 | 93 | 98 | 103 | 108 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/components/DashboardCommandLine.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { InputAdornment, Button, TextField, useTheme } from "@mui/material"; 3 | import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip"; 4 | import { styled, lighten, darken } from "@mui/system"; 5 | // import { clipboard } from 'electron'; 6 | 7 | const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( 8 | 9 | ))(({ theme }) => ({ 10 | [`& .${tooltipClasses.tooltip}`]: { 11 | backgroundColor: theme.palette.mode === "dark" ? "#5c4d9a" : "#8383de", 12 | color: "white", 13 | fontSize: 11, 14 | }, 15 | })); 16 | 17 | const DashboardCommandLine = (props) => { 18 | const theme = useTheme(); 19 | 20 | // Add/remove functionality in text box 21 | const handleChange = (e) => { 22 | let newUserInput = ""; 23 | 24 | if (e.nativeEvent.inputType === "deleteContentBackward") { 25 | newUserInput = props.userInput.slice(0, props.userInput.length - 1); 26 | } else { 27 | newUserInput = 28 | props.userInput + e.target.value[e.target.value.length - 1]; 29 | } 30 | props.setUserInput(newUserInput); 31 | }; 32 | 33 | const handleClear = (e) => { 34 | let userInput2 = ""; 35 | 36 | props.setUserInput(userInput2); 37 | props.setVerb(userInput2); 38 | props.setType(userInput2); 39 | props.setName(userInput2); 40 | props.setFlags([]); 41 | }; 42 | 43 | return ( 44 |
54 | 62 |
70 |
82 |
91 | Working Directory 92 |
93 | 124 |
125 |
126 | 127 |
135 |
147 |
159 |
168 | Input Command 169 |
170 |
181 | 203 | $ 204 | 205 | ), 206 | }} 207 | onChange={(e) => { 208 | handleChange(e); 209 | }} 210 | value={props.command} 211 | // onPaste={(e) => { 212 | // handlePaste(e); 213 | // }} 214 | /> 215 | 216 | 231 | 250 | 251 |
252 |
253 | ); 254 | }; 255 | 256 | export default DashboardCommandLine; 257 | -------------------------------------------------------------------------------- /public/assets/node.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | node 90 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/Pages/assets/node.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | node 90 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/components/assets/node.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 63 | 71 | 77 | 78 | node 90 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/components/PodCpuChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useCallback, useEffect } from "react"; 2 | import { AreaClosed, Line, Bar } from "@visx/shape"; 3 | import { curveMonotoneX } from "@visx/curve"; 4 | import { GridRows, GridColumns } from "@visx/grid"; 5 | import { scaleTime, scaleLinear } from "@visx/scale"; 6 | import { 7 | withTooltip, 8 | Tooltip, 9 | TooltipWithBounds, 10 | defaultStyles, 11 | } from "@visx/tooltip"; 12 | import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip"; 13 | import { localPoint } from "@visx/event"; 14 | import { LinearGradient } from "@visx/gradient"; 15 | import { max, extent, bisector } from "@visx/vendor/d3-array"; 16 | import { timeFormat } from "@visx/vendor/d3-time-format"; 17 | import { useTheme } from "@mui/material"; 18 | 19 | interface podStats { 20 | date: string; 21 | cpu: number; 22 | memory: number; 23 | memoryDisplay: string; 24 | } 25 | 26 | type TooltipData = podStats; 27 | 28 | // util 29 | const formatDate = timeFormat("%m/%d/%y @ %H:%M:%S"); 30 | 31 | // accessors 32 | const getDate = (d: podStats) => new Date(d.date); 33 | const getCpuValue = (d: podStats) => d.cpu; 34 | const bisectDate = bisector((d) => new Date(d.date)).left; 35 | 36 | export type AreaProps = { 37 | podsStatsObj: any; 38 | selectedPod: any; 39 | width: number; 40 | height: number; 41 | margin?: { top: number; right: number; bottom: number; left: number }; 42 | }; 43 | 44 | export default withTooltip( 45 | ({ 46 | podsStatsObj, 47 | selectedPod, 48 | width, 49 | height, 50 | margin = { top: 0, right: 0, bottom: 0, left: 0 }, 51 | showTooltip, 52 | hideTooltip, 53 | tooltipData, 54 | tooltipTop = 0, 55 | tooltipLeft = 0, 56 | }: AreaProps & WithTooltipProvidedProps) => { 57 | if (width < 10) return null; 58 | 59 | let selectedPodStats = podsStatsObj[`${selectedPod[0]["name"]}`]; 60 | 61 | const theme = useTheme(); 62 | 63 | const background = theme.palette.mode === "dark" ? "#0e0727" : "#eeebfb"; 64 | const background2 = theme.palette.mode === "dark" ? "#120838" : "#eeebfb"; 65 | const accentColor = 66 | theme.palette.mode === "dark" ? "white" : "#7b76c2" 67 | const textColor = theme.palette.mode === "dark" ? "white" : "grey"; 68 | 69 | const tooltipStyles = { 70 | ...defaultStyles, 71 | background, 72 | border: "1px solid white", 73 | color: textColor, 74 | }; 75 | 76 | // bounds 77 | const innerWidth = width - margin.left - margin.right; 78 | const innerHeight = height - margin.top - margin.bottom; 79 | 80 | // scales 81 | const dateScale = useMemo( 82 | () => 83 | scaleTime({ 84 | range: [margin.left, innerWidth + margin.left], 85 | domain: extent(selectedPodStats, getDate) as [Date, Date], 86 | }), 87 | [innerWidth, margin.left] 88 | ); 89 | const CpuValueScale = useMemo( 90 | () => 91 | scaleLinear({ 92 | range: [innerHeight + margin.top, margin.top], 93 | domain: [ 94 | -20, 95 | (max(selectedPodStats, getCpuValue) || 0) + innerHeight / 3, 96 | ], 97 | nice: true, 98 | }), 99 | [margin.top, innerHeight] 100 | ); 101 | 102 | // tooltip handler 103 | const handleTooltip = useCallback( 104 | ( 105 | event: 106 | | React.TouchEvent 107 | | React.MouseEvent 108 | ) => { 109 | const { x } = localPoint(event) || { x: 0 }; 110 | const x0 = dateScale.invert(x); 111 | const index = bisectDate(selectedPodStats, x0, 1); 112 | const d0 = selectedPodStats[index - 1]; 113 | const d1 = selectedPodStats[index]; 114 | let d = d0; 115 | if (d1 && getDate(d1)) { 116 | d = 117 | x0.valueOf() - getDate(d0).valueOf() > 118 | getDate(d1).valueOf() - x0.valueOf() 119 | ? d1 120 | : d0; 121 | } 122 | showTooltip({ 123 | tooltipData: d, 124 | tooltipLeft: x, 125 | tooltipTop: CpuValueScale(getCpuValue(d)), 126 | }); 127 | }, 128 | [showTooltip, CpuValueScale, dateScale] 129 | ); 130 | 131 | return ( 132 |
133 | 134 | 142 | 147 | 154 | 163 | 172 | 173 | data={selectedPodStats} 174 | x={(d) => dateScale(getDate(d)) ?? 0} 175 | y={(d) => CpuValueScale(getCpuValue(d)) ?? 0} 176 | yScale={CpuValueScale} 177 | strokeWidth={1.7} 178 | stroke="url(#area-gradient)" 179 | fill="url(#area-gradient)" 180 | curve={curveMonotoneX} 181 | /> 182 | hideTooltip()} 193 | /> 194 | {tooltipData && ( 195 | 196 | 204 | 215 | 224 | 225 | )} 226 | 227 | {tooltipData && ( 228 |
229 | 241 | {`${getCpuValue(tooltipData)}m`} 242 | 243 | 260 | {formatDate(getDate(tooltipData))} 261 | 262 |
263 | )} 264 |
265 | ); 266 | } 267 | ); 268 | -------------------------------------------------------------------------------- /src/components/NodeCpuChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useCallback, useEffect } from "react"; 2 | import { AreaClosed, Line, Bar } from "@visx/shape"; 3 | import { curveMonotoneX } from "@visx/curve"; 4 | import { GridRows, GridColumns } from "@visx/grid"; 5 | import { scaleTime, scaleLinear } from "@visx/scale"; 6 | import { 7 | withTooltip, 8 | Tooltip, 9 | TooltipWithBounds, 10 | defaultStyles, 11 | } from "@visx/tooltip"; 12 | import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip"; 13 | import { localPoint } from "@visx/event"; 14 | import { LinearGradient } from "@visx/gradient"; 15 | import { max, extent, bisector } from "@visx/vendor/d3-array"; 16 | import { timeFormat } from "@visx/vendor/d3-time-format"; 17 | import { useTheme } from "@mui/material"; 18 | 19 | interface nodeStats { 20 | date: string; 21 | cpu: number; 22 | memory: number; 23 | memoryDisplay: string; 24 | } 25 | 26 | type TooltipData = nodeStats; 27 | 28 | // util 29 | const formatDate = timeFormat("%m/%d/%y @ %H:%M:%S"); 30 | 31 | // accessors 32 | const getDate = (d: nodeStats) => new Date(d.date); 33 | const getCpuValue = (d: nodeStats) => d.cpu; 34 | const bisectDate = bisector((d) => new Date(d.date)).left; 35 | 36 | export type AreaProps = { 37 | nodesStatsObj: any; 38 | selectedNode: any; 39 | width: number; 40 | height: number; 41 | margin?: { top: number; right: number; bottom: number; left: number }; 42 | }; 43 | 44 | export default withTooltip( 45 | ({ 46 | nodesStatsObj, 47 | selectedNode, 48 | width, 49 | height, 50 | margin = { top: 0, right: 0, bottom: 0, left: 0 }, 51 | showTooltip, 52 | hideTooltip, 53 | tooltipData, 54 | tooltipTop = 0, 55 | tooltipLeft = 0, 56 | }: AreaProps & WithTooltipProvidedProps) => { 57 | if (width < 10) return null; 58 | 59 | let selectedNodeStats = nodesStatsObj[`${selectedNode[0]["name"]}`]; 60 | 61 | const theme = useTheme(); 62 | 63 | const background = theme.palette.mode === "dark" ? "#0e0727" : "#eeebfb"; 64 | const background2 = theme.palette.mode === "dark" ? "#120838" : "#eeebfb"; 65 | const accentColor = theme.palette.mode === "dark" ? "white" : "#7b76c2" 66 | const textColor = theme.palette.mode === "dark" ? "white" : "grey"; 67 | 68 | const tooltipStyles = { 69 | ...defaultStyles, 70 | background, 71 | border: "1px solid white", 72 | color: textColor, 73 | }; 74 | 75 | // bounds 76 | const innerWidth = width - margin.left - margin.right; 77 | const innerHeight = height - margin.top - margin.bottom; 78 | 79 | // scales 80 | const dateScale = useMemo( 81 | () => 82 | scaleTime({ 83 | range: [margin.left, innerWidth + margin.left], 84 | domain: extent(selectedNodeStats, getDate) as [Date, Date], 85 | }), 86 | [innerWidth, margin.left] 87 | ); 88 | const CpuValueScale = useMemo( 89 | () => 90 | scaleLinear({ 91 | range: [innerHeight + margin.top, margin.top], 92 | domain: [ 93 | -5, 94 | //@ts-ignore 95 | (max(selectedNodeStats, getCpuValue) + 200 || 0) + innerHeight / 3, 96 | ], 97 | nice: true, 98 | }), 99 | [margin.top, innerHeight] 100 | ); 101 | 102 | // tooltip handler 103 | const handleTooltip = useCallback( 104 | ( 105 | event: 106 | | React.TouchEvent 107 | | React.MouseEvent 108 | ) => { 109 | const { x } = localPoint(event) || { x: 0 }; 110 | const x0 = dateScale.invert(x); 111 | const index = bisectDate(selectedNodeStats, x0, 1); 112 | const d0 = selectedNodeStats[index - 1]; 113 | const d1 = selectedNodeStats[index]; 114 | let d = d0; 115 | if (d1 && getDate(d1)) { 116 | d = 117 | x0.valueOf() - getDate(d0).valueOf() > 118 | getDate(d1).valueOf() - x0.valueOf() 119 | ? d1 120 | : d0; 121 | } 122 | showTooltip({ 123 | tooltipData: d, 124 | tooltipLeft: x, 125 | tooltipTop: CpuValueScale(getCpuValue(d)), 126 | }); 127 | }, 128 | [showTooltip, CpuValueScale, dateScale] 129 | ); 130 | 131 | return ( 132 |
133 | 134 | 142 | 147 | 154 | 163 | 172 | 173 | data={selectedNodeStats} 174 | x={(d) => dateScale(getDate(d)) ?? 0} 175 | y={(d) => CpuValueScale(getCpuValue(d)) ?? 0} 176 | yScale={CpuValueScale} 177 | strokeWidth={1.7} 178 | stroke="url(#area-gradient)" 179 | fill="url(#area-gradient)" 180 | curve={curveMonotoneX} 181 | /> 182 | hideTooltip()} 193 | /> 194 | {tooltipData && ( 195 | 196 | 204 | 215 | 224 | 225 | )} 226 | 227 | {tooltipData && ( 228 |
229 | 241 | {`${getCpuValue(tooltipData)}m`} 242 | 243 | 260 | {formatDate(getDate(tooltipData))} 261 | 262 |
263 | )} 264 |
265 | ); 266 | } 267 | ); 268 | -------------------------------------------------------------------------------- /src/components/PodMemoryChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useCallback, useEffect } from "react"; 2 | import { AreaClosed, Line, Bar } from "@visx/shape"; 3 | import { curveMonotoneX } from "@visx/curve"; 4 | import { GridRows, GridColumns } from "@visx/grid"; 5 | import { scaleTime, scaleLinear } from "@visx/scale"; 6 | import { 7 | withTooltip, 8 | Tooltip, 9 | TooltipWithBounds, 10 | defaultStyles, 11 | } from "@visx/tooltip"; 12 | import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip"; 13 | import { localPoint } from "@visx/event"; 14 | import { LinearGradient } from "@visx/gradient"; 15 | import { max, extent, bisector } from "@visx/vendor/d3-array"; 16 | import { timeFormat } from "@visx/vendor/d3-time-format"; 17 | import { useTheme } from "@mui/material"; 18 | 19 | interface podStats { 20 | date: string; 21 | cpu: number; 22 | memory: number; 23 | memoryDisplay: string; 24 | } 25 | 26 | type TooltipData = podStats; 27 | 28 | // util 29 | const formatDate = timeFormat("%m/%d/%y @ %H:%M:%S"); 30 | 31 | // accessors 32 | const getDate = (d: podStats) => new Date(d.date); 33 | const getMemoryValue = (d: podStats) => d.memory; 34 | const getMemoryDisplayValue = (d: podStats) => d.memoryDisplay; 35 | const bisectDate = bisector((d) => new Date(d.date)).left; 36 | 37 | export type AreaProps = { 38 | podsStatsObj: any; 39 | selectedPod: any; 40 | width: number; 41 | height: number; 42 | margin?: { top: number; right: number; bottom: number; left: number }; 43 | }; 44 | 45 | export default withTooltip( 46 | ({ 47 | podsStatsObj, 48 | selectedPod, 49 | width, 50 | height, 51 | margin = { top: 0, right: 0, bottom: 0, left: 0 }, 52 | showTooltip, 53 | hideTooltip, 54 | tooltipData, 55 | tooltipTop = 0, 56 | tooltipLeft = 0, 57 | }: AreaProps & WithTooltipProvidedProps) => { 58 | if (width < 10) return null; 59 | 60 | const theme = useTheme(); 61 | 62 | const background = theme.palette.mode === "dark" ? "#0e0727" : "#eeebfb"; 63 | const background2 = theme.palette.mode === "dark" ? "#120838" : "#eeebfb"; 64 | const accentColor = theme.palette.mode === "dark" ? "white" : "#7b76c2" 65 | const textColor = theme.palette.mode === "dark" ? "white" : "grey"; 66 | 67 | const tooltipStyles = { 68 | ...defaultStyles, 69 | background, 70 | border: "1px solid white", 71 | color: textColor, 72 | }; 73 | 74 | // bounds 75 | const innerWidth = width - margin.left - margin.right; 76 | const innerHeight = height - margin.top - margin.bottom; 77 | let selectedPodStats = podsStatsObj[`${selectedPod[0]["name"]}`]; 78 | 79 | // scales 80 | const dateScale = useMemo( 81 | () => 82 | scaleTime({ 83 | range: [margin.left, innerWidth + margin.left], 84 | domain: extent(selectedPodStats, getDate) as [Date, Date], 85 | }), 86 | [innerWidth, margin.left] 87 | ); 88 | const memoryValueScale = useMemo( 89 | () => 90 | scaleLinear({ 91 | range: [innerHeight + margin.top, margin.top], 92 | domain: [ 93 | -5, 94 | (max(selectedPodStats, getMemoryValue) || 0) + innerHeight / 0.001, 95 | ], 96 | nice: true, 97 | }), 98 | [margin.top, innerHeight] 99 | ); 100 | 101 | // tooltip handler 102 | const handleTooltip = useCallback( 103 | ( 104 | event: 105 | | React.TouchEvent 106 | | React.MouseEvent 107 | ) => { 108 | const { x } = localPoint(event) || { x: 0 }; 109 | const x0 = dateScale.invert(x); 110 | const index = bisectDate(selectedPodStats, x0, 1); 111 | const d0 = selectedPodStats[index - 1]; 112 | const d1 = selectedPodStats[index]; 113 | let d = d0; 114 | if (d1 && getDate(d1)) { 115 | d = 116 | x0.valueOf() - getDate(d0).valueOf() > 117 | getDate(d1).valueOf() - x0.valueOf() 118 | ? d1 119 | : d0; 120 | } 121 | showTooltip({ 122 | tooltipData: d, 123 | tooltipLeft: x, 124 | tooltipTop: memoryValueScale(getMemoryValue(d)), 125 | }); 126 | }, 127 | [showTooltip, memoryValueScale, dateScale] 128 | ); 129 | 130 | return ( 131 |
132 | 133 | 141 | 146 | 153 | 162 | 171 | 172 | data={selectedPodStats} 173 | x={(d) => dateScale(getDate(d)) ?? 0} 174 | y={(d) => memoryValueScale(getMemoryValue(d)) ?? 0} 175 | yScale={memoryValueScale} 176 | strokeWidth={1} 177 | stroke="url(#area-gradient)" 178 | fill="url(#area-gradient)" 179 | curve={curveMonotoneX} 180 | /> 181 | hideTooltip()} 192 | /> 193 | {tooltipData && ( 194 | 195 | 203 | 214 | 223 | 224 | )} 225 | 226 | {tooltipData && ( 227 |
228 | 240 | {`${getMemoryDisplayValue(tooltipData)}`} 241 | 242 | 259 | {formatDate(getDate(tooltipData))} 260 | 261 |
262 | )} 263 |
264 | ); 265 | } 266 | ); 267 | -------------------------------------------------------------------------------- /src/components/NodeMemoryChart.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useCallback, useEffect } from "react"; 2 | import { AreaClosed, Line, Bar } from "@visx/shape"; 3 | import { curveMonotoneX } from "@visx/curve"; 4 | import { GridRows, GridColumns } from "@visx/grid"; 5 | import { scaleTime, scaleLinear } from "@visx/scale"; 6 | import { 7 | withTooltip, 8 | Tooltip, 9 | TooltipWithBounds, 10 | defaultStyles, 11 | } from "@visx/tooltip"; 12 | import { WithTooltipProvidedProps } from "@visx/tooltip/lib/enhancers/withTooltip"; 13 | import { localPoint } from "@visx/event"; 14 | import { LinearGradient } from "@visx/gradient"; 15 | import { max, extent, bisector } from "@visx/vendor/d3-array"; 16 | import { timeFormat } from "@visx/vendor/d3-time-format"; 17 | import { useTheme } from "@mui/material"; 18 | 19 | interface nodeStats { 20 | date: string; 21 | cpu: number; 22 | memory: number; 23 | memoryDisplay: string; 24 | } 25 | 26 | type TooltipData = nodeStats; 27 | 28 | // util 29 | const formatDate = timeFormat("%m/%d/%y @ %H:%M:%S"); 30 | 31 | // accessors 32 | const getDate = (d: nodeStats) => new Date(d.date); 33 | const getMemoryValue = (d: nodeStats) => d.memory; 34 | const getMemoryDisplayValue = (d: nodeStats) => d.memoryDisplay; 35 | const bisectDate = bisector((d) => new Date(d.date)).left; 36 | 37 | export type AreaProps = { 38 | nodesStatsObj: any; 39 | selectedNode: any; 40 | width: number; 41 | height: number; 42 | margin?: { top: number; right: number; bottom: number; left: number }; 43 | }; 44 | 45 | export default withTooltip( 46 | ({ 47 | nodesStatsObj, 48 | selectedNode, 49 | width, 50 | height, 51 | margin = { top: 0, right: 0, bottom: 0, left: 0 }, 52 | showTooltip, 53 | hideTooltip, 54 | tooltipData, 55 | tooltipTop = 0, 56 | tooltipLeft = 0, 57 | }: AreaProps & WithTooltipProvidedProps) => { 58 | if (width < 10) return null; 59 | 60 | const theme = useTheme(); 61 | 62 | const background = theme.palette.mode === "dark" ? "#0e0727" : "#eeebfb"; 63 | const background2 = theme.palette.mode === "dark" ? "#120838" : "#eeebfb"; 64 | const accentColorMemory = theme.palette.mode === "dark" ? "white" : "#7b76c2" 65 | const textColor = theme.palette.mode === "dark" ? "white" : "grey"; 66 | 67 | const tooltipStyles = { 68 | ...defaultStyles, 69 | background, 70 | border: "1px solid white", 71 | color: textColor, 72 | }; 73 | 74 | // bounds 75 | const innerWidth = width - margin.left - margin.right; 76 | const innerHeight = height - margin.top - margin.bottom; 77 | let selectedNodeStats = nodesStatsObj[`${selectedNode[0]["name"]}`]; 78 | 79 | // scales 80 | const dateScale = useMemo( 81 | () => 82 | scaleTime({ 83 | range: [margin.left, innerWidth + margin.left], 84 | domain: extent(selectedNodeStats, getDate) as [Date, Date], 85 | }), 86 | [innerWidth, margin.left] 87 | ); 88 | const memoryValueScale = useMemo( 89 | () => 90 | scaleLinear({ 91 | range: [innerHeight + margin.top, margin.top], 92 | domain: [ 93 | -5, 94 | (max(selectedNodeStats, getMemoryValue) || 0) + 95 | innerHeight / 0.00005, 96 | ], 97 | nice: true, 98 | }), 99 | [margin.top, innerHeight] 100 | ); 101 | 102 | // tooltip handler 103 | const handleTooltip = useCallback( 104 | ( 105 | event: 106 | | React.TouchEvent 107 | | React.MouseEvent 108 | ) => { 109 | const { x } = localPoint(event) || { x: 0 }; 110 | const x0 = dateScale.invert(x); 111 | const index = bisectDate(selectedNodeStats, x0, 1); 112 | const d0 = selectedNodeStats[index - 1]; 113 | const d1 = selectedNodeStats[index]; 114 | let d = d0; 115 | if (d1 && getDate(d1)) { 116 | d = 117 | x0.valueOf() - getDate(d0).valueOf() > 118 | getDate(d1).valueOf() - x0.valueOf() 119 | ? d1 120 | : d0; 121 | } 122 | showTooltip({ 123 | tooltipData: d, 124 | tooltipLeft: x, 125 | tooltipTop: memoryValueScale(getMemoryValue(d)), 126 | }); 127 | }, 128 | [showTooltip, memoryValueScale, dateScale] 129 | ); 130 | 131 | return ( 132 |
133 | 134 | 142 | 147 | 154 | 163 | 172 | 173 | data={selectedNodeStats} 174 | x={(d) => dateScale(getDate(d)) ?? 0} 175 | y={(d) => memoryValueScale(getMemoryValue(d)) ?? 0} 176 | yScale={memoryValueScale} 177 | strokeWidth={1} 178 | stroke="url(#area-gradient)" 179 | fill="url(#area-gradient)" 180 | curve={curveMonotoneX} 181 | /> 182 | hideTooltip()} 193 | /> 194 | {tooltipData && ( 195 | 196 | 204 | 215 | 224 | 225 | )} 226 | 227 | {tooltipData && ( 228 |
229 | 90 250 | // ? "#cf4848" 251 | // : "yellow", 252 | }} 253 | > 254 | {`${getMemoryDisplayValue(tooltipData)}`} 255 | 256 | 273 | {formatDate(getDate(tooltipData))} 274 | 275 |
276 | )} 277 |
278 | ); 279 | } 280 | ); 281 | -------------------------------------------------------------------------------- /public/assets/container.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 33 | 34 | pod 35 | 36 | 37 | 39 | 40 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 77 | 78 | 80 | 81 | 83 | 84 | container 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 96 | 102 | 103 | container 104 | 105 | 106 | 107 | 110 | 111 | 112 | 113 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/Pages/assets/container.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 33 | 34 | pod 35 | 36 | 37 | 39 | 40 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 77 | 78 | 80 | 81 | 83 | 84 | container 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 96 | 102 | 103 | container 104 | 105 | 106 | 107 | 110 | 111 | 112 | 113 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | --------------------------------------------------------------------------------