├── .gitignore ├── LICENSE ├── README.md ├── client.zip ├── client ├── .env ├── Types │ └── VisualizerType.ts ├── assets │ └── images │ │ ├── UserIcon.png │ │ └── UserIconWhite.png ├── index.html ├── src │ ├── App.tsx │ ├── Login.tsx │ ├── colors.tsx │ ├── components │ │ ├── SearchBar.tsx │ │ ├── SearchBarCluster.tsx │ │ ├── SearchBarTrace.tsx │ │ ├── clusterView.tsx │ │ ├── clusterViewSlice.ts │ │ ├── home.tsx │ │ ├── loadingScreen.tsx │ │ ├── logout.tsx │ │ ├── searchBarSlice.ts │ │ ├── sourceMap.tsx │ │ ├── sourceMapSlice.ts │ │ ├── span-table │ │ │ ├── spanData.tsx │ │ │ ├── spanDataSlice.tsx │ │ │ ├── spanHeader.tsx │ │ │ ├── spanList.tsx │ │ │ ├── spanListSlice.tsx │ │ │ ├── spanMap.tsx │ │ │ ├── spanMapSlice.tsx │ │ │ ├── spanResultsMap.tsx │ │ │ ├── spanResultsMapSlice.tsx │ │ │ └── spanTableContent.tsx │ │ ├── trace-table │ │ │ ├── drawerSlice.tsx │ │ │ ├── durationSelector.tsx │ │ │ ├── footerDrawer.tsx │ │ │ ├── footerDrawerTab.tsx │ │ │ ├── tableDropdownMenu.tsx │ │ │ ├── tableHeader.tsx │ │ │ ├── tableList.tsx │ │ │ ├── tableListSlice.tsx │ │ │ ├── traceTableContent.tsx │ │ │ └── windowHeader.tsx │ │ ├── traceView.tsx │ │ ├── traceViewSlice.tsx │ │ └── traceViewTester.tsx │ ├── constants │ │ ├── CytoscapeConfig.tsx │ │ └── config.ts │ ├── index.tsx │ ├── lib │ │ └── hooks.ts │ ├── store.ts │ └── styles │ │ ├── Stylesheet.tsx │ │ ├── home.scss │ │ ├── images │ │ ├── Screen_Shot_2022-11-08_at_9.29.34_PM.png │ │ ├── eye-closed.png │ │ ├── eye-open.png │ │ └── login-background.png │ │ ├── login.scss │ │ ├── logout.scss │ │ ├── searchBar.scss │ │ ├── spanTable.scss │ │ ├── theme.scss │ │ └── traceTable.scss ├── style.css └── style.scss ├── cluster ├── auth │ ├── drew.yaml │ ├── jon.yaml │ ├── kat.yaml │ ├── richard.yaml │ └── team.yaml ├── cert-manager.yaml ├── iam_policy.json ├── ingress │ └── ingress.yaml ├── service │ ├── apple.yaml │ ├── banana.yaml │ ├── durian.yaml │ ├── hello-landing-page.yaml │ └── hello-world.yaml ├── small_node_group.yaml ├── v2_4_4_full.yaml └── v2_4_4_ingclass.yaml ├── data.txt ├── dist └── style.css ├── electron ├── electron.js └── preload.js ├── examples └── comment.ts ├── images ├── JaegerUI.png ├── KonstellationCluster.png ├── konstellation-login.png ├── konstellation-logo.png ├── konstellation-span-details.png ├── konstellation-trace-table.png └── trace-view.png ├── konstellation-yaml └── setup │ ├── 00-namespace.yaml │ ├── 01-opentelemetry-operator.yaml │ ├── 02-jaeger-operator.yaml │ ├── 03-opentelemetry-collector.yaml │ ├── 04-jaegerconfig.yaml │ ├── 05-autoinstrumentation.yaml │ ├── 06-default-annotation.yaml │ ├── runFirst.yaml │ └── runSecond.yaml ├── otel-related-ymls ├── instrumentation.yml ├── namespace-setup.yml └── otel-collector-jaeger.yml ├── package-lock.json ├── package.json ├── server.zip ├── server ├── .env ├── constants │ └── config.ts ├── controllers │ ├── AuthController.ts │ ├── ClusterController.ts │ ├── CookieController.ts │ ├── ElemController.ts │ └── TraceController.ts ├── data │ ├── fakeTraceData.ts │ └── fakeTraceData2.ts ├── interfaces │ └── IExpress.ts ├── models │ ├── AuthModel.ts │ ├── ClusterModel.ts │ ├── TraceModel.ts │ └── test.tsx ├── proxy.ts ├── routers │ ├── AuthRouter.ts │ ├── ClusterRouter.ts │ └── TraceRouter.ts ├── server.ts ├── types │ ├── EdgeData.ts │ ├── PodData.ts │ ├── SpanData.ts │ ├── TraceData.ts │ ├── TraceViewData.ts │ ├── Types.ts │ └── traceLogData.ts └── utils │ └── Utils.ts ├── tsconfig.json ├── types.ts ├── typings ├── custom │ └── import-png.d.ts └── index.d.ts └── webpack.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | cluster/cluster_app 2 | node_modules 3 | TEMP/* 4 | dist/* 5 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Konstellation logo](./images/konstellation-logo.png) 2 | ![Copy of Konstellation](https://user-images.githubusercontent.com/96984912/201180097-7da4799f-ae61-4e08-8dff-6bec1a5d3326.gif) 3 | 4 | 5 | 6 | # Introduction 7 | Konstellation is an Open Source Distributed Tracing tool built specifically for Kubernetes. 8 | 9 | This tool gathers telemetry data within a kubernetes cluster and presents it within an intuitive Source Map topology, and provides developers with additional tools to identify and examine trace data in detail. 10 | 11 | Please read the [website](http://konstellationapp.com/) and [medium](https://medium.com/@katalystyt0/kubernetes-clusters-visualization-with-konstellation-647d61aa817b) article for more information. 12 | 13 | # Getting Started 14 | Prerequisites: 15 | - [ ] Kubectl Installed 16 | - [ ] cert-manager installed 17 | - [ ] Opentelemetry collector and operator deployed on the cluster 18 | - [ ] Opentelemetry instrumentation integrated within your app 19 | - [ ] Jaeger deployed on the cluster 20 | 21 | Check out our [QuickStart](#quickstart) section for instructions on how to quickly setup the application. 22 | 23 | *Note* 24 | - If your App is not instrumented with Opentelemetry instrumentation, the following instructions will provide a method to instrument a NodeJS, Python, or Java app with Opentelemetry Autoinstrumentation. Do note that Opentelemtry is not yet compatible with [WhatWG standardized](https://fetch.spec.whatwg.org/) fetch requests introduced in NodeJS 17.5 (commonly called Node 18/native fetch), and so services using this feature will not correctly propagate. 25 | 26 | - Custom deployment of the Opentelemetry Operator Collector, and the deployment of the Jaeger Operator and collector is possible. As long as the `Jaeger-frontend` is exposed on `localhost:16686`, Konstellation can be used. 27 | 28 | - The app has been tested on both AWS Elastic Kubernetes Service as well as unmanaged clusters. While it is expected that this application will work with other managed Kubernetes providers, support on such platforms is not officially supported. 29 | 30 | - ENV templates have been provided in the client.zip and server.zip files 31 | ___ 32 | # Installing Kubectl 33 | - Instructions to install kubectl [here](https://kubernetes.io/docs/tasks/tools/) 34 | 35 | ## Install with Homebrew on macOS 36 | If you are on macOS and using [Homebrew](http://brew.sh) package manager, you can install kubectl with Homebrew. 37 | 1. Run the installation command: 38 | 39 | ``` 40 | brew install kubectl 41 | ``` 42 | 43 | or 44 | 45 | ``` 46 | brew install kubernetes-cli 47 | ``` 48 | 49 | 2. Test to ensure the version you installed is up-to-date: 50 | 51 | ``` 52 | kubectl version --client 53 | ``` 54 | ___ 55 | # Installing cert-manager 56 | - Detailed Instructions for installing `cert-manager` [here](https://cert-manager.io/docs/installation/) 57 | 58 | To install cert-manager run: 59 | 60 | ``` 61 | kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.0/cert-manager.yaml 62 | ``` 63 | ___ 64 | # Deploying Opentelemetry Operator and Collector 65 | - Detailed instructions for installing the Opentelemetry Operator and Collector can be found [here](https://github.com/open-telemetry/opentelemetry-operator) 66 | 67 | When deploying the operator and collector services, please ensure that cert-manager has also been installed in your system. The Opentelemetry Operator and Collector may be deployed via helm chart or manually. 68 | 69 | ## (Optional) Deploy Via Helm chart 70 | 71 | The Opentelemetry Operator can be deployed via [Helm Chart](https://github.com/open-telemetry/opentelemetry-helm-charts/tree/main/charts/opentelemetry-operator) from the opentelemetry-helm-charts repository. More information can be found [here](https://github.com/open-telemetry/opentelemetry-helm-charts/tree/main/charts/opentelemetry-operator). 72 | 73 | ## Install Opentelemetry Operator 74 | Once cert-manager is installed, deploy the operator by applying the YAML cofiguration with the following command: 75 | 76 | ```bash 77 | kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml 78 | ``` 79 | 80 | Once the `opentelemetry-operator` deployment is succefully running, create an OpenTelemetry Collector (otelcol) instance: 81 | 82 | **_NOTE:_** For AWS EKS users, a dedicated opentelemetry collector, `AWS Distro for OpenTelemetry` can be used instead. Instructions and documentation can be found [here](https://aws.amazon.com/otel/) 83 | 84 | A pre-configured OpenTelemetry collector YAML file is provided in the konstellation-yaml folder of this repository. With it available on your local system, run the following command: 85 | 86 | ``` 87 | kubectl apply -f ./konstellation-yaml/setup/03-opentelemetry-collector.yaml 88 | ``` 89 | 90 | This will create an OpenTelemetry instance named `otel` which exposes a `jaeger-grpc` port to consume spans from your instrumented applications. This service will export those spans via `logging`, which writes the spans to the console (`stdout`) of the OpenTelemetry Collector instance that receives the span and to the `jaeger-collector`. 91 | 92 | # (Optional) Setting up Opentelemetry Autoinstrumentation Injection 93 | - Only required if your app does not have Opentelemetry's Autoinstrumentation built in. Do not set up Opentelemetry auoinstrumentation injection if your app is already instrumented with Opentelemetry SDK tools. 94 | 95 | The operator can inject and configure OpenTelemetry auto-instrumentation libraries. Applications written in Java, NodeJS, Python, or using .NET Core/Framework are supported. 96 | 97 | To use auto-instrumentation, configure an `Instrumentation` resource with the configuration for the SDK and instrumentation. 98 | 99 | The requisite autoinstrumentation YAML file can again be found in the konstellation-yaml folder. To apply the instrumentation run: 100 | 101 | ```bash 102 | kubectl apply -f ./konstellation-yaml/05-konstellation-autoinstrumentation.yaml 103 | ``` 104 | 105 | Next, an annotation must be added to enable pod injection. The annotation can be added to a namespace, so that all pods within that namespace will get instrumentation, or by adding the annotation to individual PodSpec objects, available as part of Deployment, Statefulset, and other resources. 106 | 107 | Java: 108 | ``` 109 | instrumentation.opentelemetry.io/inject-java: "true" 110 | ``` 111 | 112 | NodeJS: 113 | ``` 114 | instrumentation.opentelemetry.io/inject-nodejs: "true" 115 | ``` 116 | 117 | Python: 118 | ``` 119 | instrumentation.opentelemetry.io/inject-python: "true" 120 | ``` 121 | 122 | .NET Core/Framework: 123 | ``` 124 | instrumentation.opentelemetry.io/inject-dotnet: "true" 125 | ``` 126 | 127 | For example, running the following command will patch the `default` namespace to allow Node-JS autoinstrumentation injection: 128 | 129 | ``` 130 | kubectl patch namespace default -p '{"metadata":{"annotations":{"instrumentation.opentelemetry.io/inject-nodejs": "true"}}}' 131 | ``` 132 | ___ 133 | # Setting up Jaeger Operator and Collector 134 | - Documentation to set up the Jaeger Operator on a Kubernetes cluster is available [here](https://www.jaegertracing.io/docs/1.39/operator/) 135 | 136 | Before installing the Jaeger Operator, please ensure cert-manager is installed (it should be installed from earlier). 137 | 138 | To install the Operator, run: 139 | 140 | ``` 141 | kubectl create namespace observability # <1> 142 | kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.39.0/jaeger-operator.yaml -n observability # <2> 143 | 144 | ``` 145 | - The first command will create a namespace on your cluster named observability. 146 | - The second command will create the jaeger operator in the observability namespace. 147 | 148 | Once the Jaeger operator has been set up, we will need to create an instance of the jaeger collector on the cluster. 149 | 150 | Create an instance of the Jaeger collector with the following command 151 | 152 | ``` 153 | kubectl apply -f ./konstellation-yaml/setup/04-jaegerconfig.yaml 154 | ``` 155 | 156 | # Port-Forwarding Jaeger 157 | 158 | In order to retrieve trace data from these entities, the application requires access to the Jaeger Collector. To provide this access, port-forward the Jaeger Collector service to `localhost:16686`. This can be done with the following command: 159 | 160 | ```bash 161 | kubectl port-forward jaeger-collector 16686:16686 162 | ``` 163 | 164 | With these services deployed, and the service now ported to the local machine, we can confirm functionality. Open a browser and navigate to `http://localhost:16686`. The jaeger UI should load. 165 | 166 | drawing 167 | 168 | ___ 169 | 170 | # Quickstart 171 | The quickstart instructions will require that [`kubectl`](#installing-kubectl) is set up and [`cert-manager`](#installing-cert-manager) is installed. 172 | 173 | To begin, issue the following commands which will create and deploy the `Jaeger Operator` and `OpenTelemetry Operator` into the observability namespace: 174 | 175 | ``` 176 | kubectl apply -f ./konstellation-yaml/setup/runFirst.yaml 177 | ``` 178 | 179 | Once the operator pods are deployed, run the following command: 180 | 181 | ``` 182 | kubectl apply -f ./konstellation-yaml/setup/runSecond.yaml 183 | ``` 184 | 185 | This second command will configure an `OpenTelemetry Collector` and `Jaeger Collector` in the default namespace. It will additionally set up autoinstrumentation for Node-JS applications in the default namespace and add an annotation to the namespace to allow the Operator to inject the autoinstrumentation to all pods residing in the namespace. 186 | 187 | Once the Jaeger Collector has been set up, port-forward the jaeger collector to port 16686 with the following command: 188 | 189 | ``` 190 | kubectl port-forward services/jaeger-query 16686:16686 191 | ``` 192 | 193 | Confirm proper setup by visiting http://localhost:16686. You should see the JaegerUI. 194 | ___ 195 | # Running the Application 196 | 197 | Once all of the prerequisite conditions are met, and Jaeger is port-forwarded to `localhost:16686`, run the following command: 198 | 199 | 200 | ``` 201 | npm install 202 | ``` 203 | 204 | - Konstellation can be run as either in the browser or as an electron application. 205 | 206 | ## Running in the browser 207 | - Once all of the npm dependencies have resolved, run: 208 | 209 | ``` 210 | npm start 211 | ``` 212 | 213 | Navigate to `localhost:8080` to run the application 214 | 215 | ## Running as an Electron Application 216 | The application can also run as an electron application, to build the electron app, run: 217 | 218 | ``` 219 | npm run build 220 | ``` 221 | - Once the webpack build is finished, run: 222 | ``` 223 | npm run server-prod 224 | ``` 225 | - Then, on a separate terminal, run to start the electon app: 226 | ``` 227 | npm run electron-start 228 | ``` 229 | ___ 230 | 1. On successful startup, if you are not connected to your AWS cluster, please enter your AWS credentials, otherwise the app will proceed to the cluster map view. 231 | drawing 232 | 233 | 2. To view a list of traces click on the table tabat the bottom of the screen. 234 | drawing 235 | 236 | 3. To view a specific trace, click on the trace ID on the table. 237 | drawing 238 | 239 | 4. To view span details, double-click on a pod in the trace map view. 240 | drawing 241 | 242 | 5. To return to the clusterView click on the clusterView button. 243 | drawing 244 | 245 | 5. To view a specific trace, if the trace Id is known, enter the traceId at the search bar on the top and click submit. 246 | 247 | # Known Issues 248 | - There is a known issue regarding apple silicon Macbooks causing incompatibility with the 'canvas' package. 249 | The issue can be resolved by running the following command: 250 | ``` 251 | arch -arm64 brew install pkg-config cairo pango libpng jpeg giflib librsvg 252 | ``` 253 | Additional information on this issue can be found [here](https://github.com/Automattic/node-canvas/issues/1733#issuecomment-808916786) 254 | 255 | # Authors 256 | 257 | Jonathan Le [Github](https://github.com/lejon1220)/[LinkedIn](https://www.linkedin.com/in/jonathan-le-bb38a3256/) 258 | 259 | Matthew Antosiak [Github](https://github.com/Jaitnium)/[LinkedIn](https://linkedin.com/in/matthewantosiak) 260 | 261 | Kathryn Tsai [Github](https://github.com/katalystkat)/[LinkedIn](https://linkedin.com/in/ck-tsai/) 262 | 263 | Richard Roberts [Github](https://github.com/Richard-Roberts)/[LinkedIn](https://www.linkedin.com/in/richard--roberts/) 264 | 265 | Drew Dunne [Github](https://github.com/drewdunne)/[LinkedIn](https://www.linkedin.com/in/andrew-dunne-7620bb84/) 266 | -------------------------------------------------------------------------------- /client.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/client.zip -------------------------------------------------------------------------------- /client/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | 3 | DOMAIN="localhost" 4 | PORT="3000" 5 | 6 | # PRODUCTION 7 | # Testing Purposes Only - never save production environment variables in local files. 8 | DOMAIN_PD="example" 9 | PORT_PD="9999" -------------------------------------------------------------------------------- /client/Types/VisualizerType.ts: -------------------------------------------------------------------------------- 1 | export const VisualizerType = { 2 | cluster: Symbol('cluster'), 3 | trace: Symbol('trace') 4 | } -------------------------------------------------------------------------------- /client/assets/images/UserIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/client/assets/images/UserIcon.png -------------------------------------------------------------------------------- /client/assets/images/UserIconWhite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/client/assets/images/UserIconWhite.png -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Konstellation 5 | 6 | 7 | <%= htmlWebpackPlugin.options.title %> 8 | 9 | 10 |
11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../style.css'; 3 | import Home from './components/home'; 4 | 5 | /** 6 | * Parent-level Component. Designed to conditionally-render "central hub" pages from which many features can be accessed. 7 | */ 8 | const App = () => { 9 | 10 | return ( 11 |
12 | 13 |
14 | ); 15 | }; 16 | 17 | export default App; 18 | -------------------------------------------------------------------------------- /client/src/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { FormEvent, MouseEventHandler, useEffect, useState } from 'react'; 2 | import { useNavigate, useLocation } from 'react-router-dom'; 3 | import './styles/login.scss'; 4 | import LoadingScreen, { LoadingScreenType } from './components/loadingScreen'; 5 | import logo from '../../images/konstellation-logo.png' 6 | import background from './styles/images/login-background.png' 7 | import { response } from 'express'; 8 | /** 9 | * Catch their respective event from the electron main process 10 | * Need to be outside of the Login component, otherwise they will fire twice 11 | */ 12 | 13 | function Login() { 14 | let navigate = useNavigate(); 15 | // Message to send to the user on invalid input 16 | const badCredentials = "Invalid Credentials" 17 | // State will be null, but won't be if navigated from app 18 | // Don't autoload user into app if they navigated from app 19 | let { state } = useLocation(); 20 | const autoLoad = state === null ? true : false; 21 | const usingElectron = window.electronAPI === undefined ? false : true; 22 | // Input fields 23 | const [accessKey, setAccessKey] = useState(""); 24 | const [secretKey, setSecretKey] = useState(""); 25 | const [clusterName, setClusterName] = useState(""); 26 | const [regionName, setRegionName] = useState(""); 27 | // Toggling Password visiblity 28 | const [hideEye, toggleEye] = useState(true); 29 | const [hidePassword, togglePassword] = useState(true); 30 | // Toggle loading screen 31 | const [isLoading, setLoading] = useState(false); 32 | 33 | // Only fire on initial render 34 | // Executes twice because of React.Strict 35 | useEffect(() => { 36 | // Don't load user in if they just logged out 37 | if (autoLoad !== false) { 38 | setLoading(true); 39 | loginUser(true); 40 | } else { 41 | // Only send request to electron if using electron 42 | if (window.electronAPI) { 43 | setLoading(true); 44 | window.electronAPI.getConfig(); 45 | } 46 | } 47 | 48 | // Handles electron's response for setting config files 49 | if (usingElectron) { 50 | window.electronAPI.onConfigResp('onConfigResp', (event: any, data: any) => { 51 | // console.log("From server:", data) 52 | setLoading(false); 53 | // The fourth datapoint is whether the kube config was set properly 54 | // Only attempt to log the user in if kube config was created 55 | if (data[4]) { 56 | loginUser(false); 57 | } else { 58 | alert(badCredentials); 59 | } 60 | }) 61 | } 62 | 63 | // Handles electron's response for requesting config files 64 | if (usingElectron) { 65 | window.electronAPI.onSendConfig('onSendConfig', (event: any, data: [string, string, string, string]) => { 66 | // console.log("Parsed from local files:", data); 67 | setLoading(false); 68 | // Update each input field 69 | setAccessKey(data[0]); 70 | setSecretKey(data[1]); 71 | setClusterName(data[2]); 72 | setRegionName(data[3]); 73 | }) 74 | } 75 | 76 | return () => { 77 | setLoading(false); 78 | // Remove listeners 79 | if (usingElectron) { 80 | window.electronAPI.unMount(); 81 | } 82 | }; 83 | }, []) 84 | 85 | /** 86 | * Validates and transfers the user to the main app if they have 87 | * valid credentials. 88 | * @param loadConfig if true and using electron, will load any user 89 | * credentials from local files 90 | */ 91 | const loginUser = (loadConfig: boolean) => { 92 | 93 | let loginUser = false; 94 | fetch('http://localhost:3000/authenticate', { 95 | method: 'GET' 96 | }) 97 | .then(response => { 98 | //console.log('resp:', resp.json()); 99 | //console.log('typeof:', typeof response.json()); 100 | //console.log(response.json()); 101 | 102 | if (response.status !== 200) { 103 | console.log('bad credentials...'); 104 | alert(badCredentials) 105 | } 106 | else { 107 | console.log('Valid creds!'); 108 | navigate('/app'); 109 | loginUser = true; 110 | return; 111 | } 112 | }).catch(err => { 113 | alert('Unable to log in. Please ensure the server is running.') 114 | }).finally(() => { 115 | // Do nothing if the user is clear to log in 116 | if (!loginUser) { 117 | // Leverage Electron to parse any authentication data from local files 118 | if (usingElectron && loadConfig) { 119 | window.electronAPI.getConfig(); 120 | } 121 | // Else not using electron and the local credentials are bad 122 | else if (!usingElectron) { 123 | alert('Please configure your local kubeconfig file.') 124 | } 125 | } 126 | }) 127 | } 128 | 129 | // Return true if the given string is empty 130 | const emptyString = (input: string) => { 131 | if (input.trim() === '') return true; 132 | return false; 133 | } 134 | 135 | // Invoked when the user clicks the Login, submit button 136 | const buttonPressed = (event: FormEvent) => { 137 | event.preventDefault(); 138 | 139 | // if (emptyString(accessKey) || emptyString(secretKey) || emptyString(clusterName) || emptyString(regionName)) { 140 | // alert('Please fill out all fields'); 141 | // return; 142 | // } 143 | 144 | setLoading(true); 145 | if (usingElectron) { 146 | // Configure the user's local files with the input fields 147 | window.electronAPI.onConfig([accessKey, secretKey, clusterName, regionName]); 148 | } else { 149 | // Authenticate the user 150 | loginUser(false); 151 | } 152 | 153 | return; 154 | } 155 | 156 | const ConditionalLoadingScreen = () => { 157 | if (isLoading) { 158 | return (); 159 | } 160 | return (<>); 161 | } 162 | 163 | /** 164 | * Only render the input form if the user is using electron. If the user isn't using 165 | * electron, there isn't a way to configure the user's kube config nor aws creds. 166 | * @returns An input form or an empty JSX object 167 | */ 168 | const CredentialsForm = () => { 169 | return (
170 | 171 |
) 172 | return (<>); 173 | } 174 | 175 | return ( 176 | 177 |
178 | 179 |
180 | 181 | 182 |

Login

183 |
184 |
185 | 189 |
190 | 191 |
192 | 199 |
200 | 201 |
202 | 206 |
207 | 208 |
209 | 213 |
214 | 215 | 216 |
217 |
218 |
219 | ); 220 | } 221 | 222 | export default Login; -------------------------------------------------------------------------------- /client/src/colors.tsx: -------------------------------------------------------------------------------- 1 | const colors = { 2 | solid: { 3 | background: 'rgb(40, 42, 54)', 4 | currentLine: 'rgb(68, 71, 90)', 5 | foreground: 'rgb(248, 248, 242)', 6 | comment: 'rgb(98, 114, 164)', 7 | cyan: 'rgb(139, 233, 253)', 8 | green: 'rgb(80, 250, 123)', 9 | orange: 'rgb(255, 184, 108)', 10 | pink: 'rgb(255, 121, 198)', 11 | purple: 'rgb(189, 147, 249)', 12 | red: 'rgb(255, 85, 85)', 13 | yellow: 'rgb(241, 250, 140)', 14 | }, 15 | translucent: { 16 | background: 'rgba(40, 42, 54, 0.2)', 17 | currentLine: 'rgba(68, 71, 90, 0.2)', 18 | foreground: 'rgba(248, 248, 242, 0.2)', 19 | comment: 'rgba(98, 114, 164, 0.2)', 20 | cyan: 'rgba(139, 233, 253, 0.2)', 21 | green: 'rgba(80, 250, 123, 0.2)', 22 | orange: 'rgba(255, 184, 108, 0.2)', 23 | pink: 'rgba(255, 121, 198, 0.2)', 24 | purple: 'rgba(189, 147, 249, 0.2)', 25 | red: 'rgba(255, 85, 85, 0.2)', 26 | yellow: 'rgba(241, 250, 140, 0.2)', 27 | }, 28 | pastel: { 29 | solid: { 30 | envy: 'rgb(145, 172, 154)', 31 | opal: 'rgb(169, 195, 182)', 32 | geyser: 'rgb(206, 223, 223)', 33 | junglemist: 'rgb(183, 209, 211)', 34 | casper: 'rgb(166, 195, 206)', 35 | glacier: 'rgb(143,184,202)', 36 | polo: 'rgb(141, 159, 198)', 37 | martini: 'rgb(185, 162, 170)', 38 | }, 39 | translucent: { 40 | envy: 'rgba(145, 172, 154, 0.2)', 41 | opal: 'rgba(169, 195, 182, 0.2)', 42 | geyser: 'rgba(206, 223, 223, 0.2)', 43 | junglemist: 'rgba(183, 209, 211, 0.2)', 44 | casper: 'rgba(166, 195, 206, 0.2)', 45 | glacier: 'rgba(143,184,202, 0.2)', 46 | polo: 'rgba(141, 159, 198, 0.2)', 47 | martini: 'rgba(185, 162, 170, 0.2)', 48 | }, 49 | }, 50 | }; 51 | 52 | export const kubesColors = { 53 | purple: 'rgba(81,77,110, 1)', 54 | blue: 'rgba(95,121,156,0.6)', 55 | }; 56 | 57 | export default colors; 58 | -------------------------------------------------------------------------------- /client/src/components/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, {useState, useEffect } from 'react' 3 | import { useAppSelector, useAppDispatch } from '../lib/hooks'; 4 | import { selectSourceMap, ViewType } from './sourceMapSlice' 5 | import SearchBarTrace from './SearchBarTrace'; 6 | import SearchBarCluster from './SearchBarCluster'; 7 | import '../styles/searchBar.scss'; 8 | 9 | const SearchBar = (props:any) => { 10 | 11 | const dispatch = useAppDispatch(); 12 | const viewType = useAppSelector(selectSourceMap); 13 | 14 | const view = viewType.type === ViewType.cluster ? : 15 | 16 | 17 | return ( 18 | <> 19 |
20 | {view} 21 |
22 | 23 | ) 24 | } 25 | 26 | 27 | 28 | export default SearchBar; 29 | -------------------------------------------------------------------------------- /client/src/components/SearchBarCluster.tsx: -------------------------------------------------------------------------------- 1 | import React, { JSXElementConstructor, useEffect } from 'react'; 2 | import { selectSourceMap, ViewType, changeView } from './sourceMapSlice' 3 | import {getTraceViewInfo, selectNameSpace} from './searchBarSlice' 4 | import { useAppSelector, useAppDispatch } from '../lib/hooks'; 5 | import { trace } from 'console'; 6 | import { getTraceDataAsync } from './traceViewSlice' 7 | import { selectCluster, updateNameSpace } from './clusterViewSlice'; 8 | import '../styles/searchBar.scss' 9 | import logo from '../../../images/konstellation-logo.png' 10 | 11 | const SearchBarCluster = ():JSX.Element => { 12 | 13 | const dispatch = useAppDispatch(); 14 | 15 | //fetches the namespaces 16 | const { data } = useAppSelector(selectCluster); 17 | 18 | 19 | const submitTrace = (traceId:string):any => { 20 | //changes the state from cluster to trace view 21 | //update view so that the data property is updated to the inputted trace view and changes the view type 22 | dispatch(changeView({type: 1})) 23 | //dispatches an action to call on the asynchoronous funciton of getting tracedata 24 | dispatch(getTraceDataAsync(traceId)) 25 | dispatch(getTraceViewInfo(traceId)) 26 | return 27 | } 28 | 29 | //handles changing the namespace state 30 | const changeNameSpace = (e:any) => { 31 | dispatch(updateNameSpace(e.target.value)) 32 | } 33 | 34 | //populates the dropdown menu with namespaces 35 | //adds only the namespace elements to the array 36 | const DropDownOptions: React.ReactElement[] =[] 37 | const nameSpaceData = [] 38 | for (let i = 0; i < data.length;i++){ 39 | if (data[i].data.type === 'namespace'){ 40 | nameSpaceData.push (data[i].data.id) 41 | } 42 | } 43 | 44 | nameSpaceData.forEach(element => { 45 | DropDownOptions.push () 46 | }) 47 | 48 | return ( 49 |
50 | 51 | 52 |
53 |
54 | Namespace: 55 |
56 |
57 | 61 |
62 |
63 | 79 |
80 | ) 81 | } 82 | 83 | 84 | 85 | 86 | export default SearchBarCluster; -------------------------------------------------------------------------------- /client/src/components/SearchBarTrace.tsx: -------------------------------------------------------------------------------- 1 | import React, { JSXElementConstructor, useEffect } from 'react'; 2 | import { changeView, selectSourceMap } from './sourceMapSlice'; 3 | import { useAppSelector, useAppDispatch } from '../lib/hooks'; 4 | import { selectTraceView, getTraceDataAsync } from './traceViewSlice'; 5 | import { selectSearchTraceResult, SearchData, getTraceViewInfo } from './searchBarSlice'; 6 | import logo from '../../../images/konstellation-logo.png' 7 | 8 | const SearchBarTrace = (): JSX.Element => { 9 | 10 | 11 | let traceViewData = useAppSelector(selectSearchTraceResult); 12 | let exportedtraceViewData= traceViewData.data 13 | 14 | const dispatch = useAppDispatch(); 15 | const submitTrace = (traceID:string):any => { 16 | //dispatches an action to call on the asynchoronous funciton of getting tracedata 17 | dispatch(getTraceDataAsync(traceID)) 18 | dispatch(getTraceViewInfo(traceID)) 19 | return 20 | } 21 | 22 | const returnToCLusterView = ():any => { 23 | //returns the view to the cluster view 24 | dispatch( changeView({type: 0})) 25 | return 26 | } 27 | 28 | const handleClick = ():any => { 29 | //closes this trace details view? 30 | //the darrk bottom bar goes back up so that you can hide the details view so you can see more of the map 31 | } 32 | 33 | //create a use effect that upon render, grabs the actual trace data store in the store and display it on top 34 | //const traceData = useAppSelector(selectTraceViewData); 35 | 36 | console.log ('this is the exported trace view data', exportedtraceViewData) 37 | return ( 38 |
39 |
40 | 41 | 42 |
43 | 44 | Search: 45 | 46 | 52 | 55 | 56 |
57 |
58 |
59 |
60 |
61 |
62 | Trace Map 63 |
64 |
65 | Trace ID: 66 |
67 |
68 | {exportedtraceViewData?.traceID} 69 |
70 |
71 | Trace Start: 72 |
73 |
74 | {exportedtraceViewData?.traceStart} 75 |
76 |
77 | Total Duration: 78 |
79 |
80 | {exportedtraceViewData?.traceDuration} 81 |
82 |
83 | Services: 84 |
85 |
86 | {exportedtraceViewData?.serviceCount} 87 |
88 |
89 | Total Spans: 90 |
91 |
92 | {exportedtraceViewData?.spanCount} 93 |
94 |
95 |
96 |
97 |
98 | ) 99 | 100 | } 101 | 102 | export default SearchBarTrace; -------------------------------------------------------------------------------- /client/src/components/clusterView.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import CytoscapeComponent from 'react-cytoscapejs'; 3 | import { useAppSelector, useAppDispatch } from '../lib/hooks'; 4 | import { getClusterAsync, ClusterData, selectCluster } from './clusterViewSlice'; 5 | import styleSheet from '../styles/Stylesheet'; 6 | import options2 from '../constants/CytoscapeConfig'; 7 | import cluster from 'cluster'; 8 | import LoadingScreen, { LoadingScreenType } from './loadingScreen'; 9 | 10 | export interface Cluster { 11 | data: ClusterData 12 | status: 'idle' | 'loading' | 'failed'; 13 | namespace: string; 14 | } 15 | 16 | /** 17 | * A specific "View" of the Source Map used for presenting cluster data 18 | * @renders A Source Map which displays a view of the relevant Kubernetes Cluster 19 | */ 20 | export default function clusterView(): JSX.Element { 21 | 22 | const {data, status} = useAppSelector(selectCluster); 23 | const dispatch = useAppDispatch(); 24 | 25 | const layout = options2(); 26 | 27 | let myCyRef; 28 | 29 | useEffect(() => { 30 | if(data.length === 0) { 31 | dispatch(getClusterAsync()); 32 | } 33 | },[]) 34 | 35 | if (status === 'failed') { 36 |
Request Failed. Please confirm server is active.
37 | } 38 | if (status === 'loading'){ 39 | return ( 40 | 41 | ) 42 | } 43 | else { 44 | return ( 45 |
46 |
52 | 53 | { 67 | myCyRef = cy; 68 | cy.on("dblclick", "node", evt => { 69 | var node = evt.target; 70 | console.log("EVT", evt); 71 | console.log("TARGET", node.data()); 72 | console.log("TARGET TYPE", typeof node[0]); 73 | cy.fit( cy.$(':selected'), 50 ); 74 | setTimeout( function(){ 75 | cy.panBy({ 76 | x: -100, 77 | y: 0 78 | }) 79 | }, 10) 80 | setTimeout( function(){ 81 | cy.$('').unselect(); 82 | cy.fit(cy.$(''),50); 83 | }, 5000 ); 84 | 85 | }); 86 | }} 87 | > 88 |
89 |
90 | ) 91 | } 92 | } 93 | 94 | -------------------------------------------------------------------------------- /client/src/components/clusterViewSlice.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../store"; 3 | import coseBilkent from 'cytoscape-cose-bilkent'; 4 | import cytoscape from 'cytoscape'; 5 | import { config } from '../constants/config' 6 | import { Cluster } from './clusterView' 7 | import { stat } from "fs"; 8 | 9 | export type ClusterData = ClusterElement[]; 10 | 11 | 12 | export interface ClusterElement { 13 | data: { 14 | id: string, 15 | label: string, 16 | type: string, 17 | } 18 | } 19 | 20 | 21 | 22 | const initialState: Cluster = { 23 | data: [], 24 | status: 'idle', 25 | namespace: 'all' 26 | } 27 | 28 | // Included as a critical first step for troubleshooting: 29 | console.log("Fetching Data From: ") 30 | console.log(config.url + '/api/cluster') 31 | 32 | // The function below is called a thunk and allows us to perform async logic. It 33 | // can be dispatched like a regular action: `dispatch(getClusterData())`. This 34 | // will call the thunk with the `getClusterData` function as the first argument. Async 35 | // code can then be executed and other actions can be dispatched. Thunks are 36 | // typically used to make async requests. 37 | export const getClusterAsync = createAsyncThunk( 38 | 'clusterView/getCluster', 39 | async () => { 40 | const response = await fetch(config.url + '/api/cluster') 41 | const data = await response.json(); 42 | return data; 43 | } 44 | ) 45 | 46 | /** 47 | * Handles reducer logic related to the Cluster View 48 | */ 49 | export const clusterViewSlice = createSlice({ 50 | name: 'clusterView', 51 | initialState: initialState, 52 | reducers: { 53 | updateData: (state, action: PayloadAction) => { 54 | state.data = action.payload; 55 | }, 56 | updateNameSpace: (state, action: PayloadAction) => { 57 | state.namespace = action.payload; 58 | } 59 | }, 60 | 61 | // The `extraReducers` field lets the slice handle actions defined elsewhere, 62 | // including actions generated by createAsyncThunk or in other slices. 63 | extraReducers: (builder) => { 64 | builder 65 | .addCase(getClusterAsync.pending, (state) => { 66 | state.status = 'loading'; 67 | state.data = []; 68 | }) 69 | .addCase(getClusterAsync.fulfilled, (state, action: PayloadAction) => { 70 | state.status = 'idle'; 71 | state.data = action.payload; 72 | }) 73 | .addCase(getClusterAsync.rejected, (state) => { 74 | state.status = 'failed'; 75 | }); 76 | } 77 | }) 78 | 79 | // The function below is called a selector and allows us to select a value from 80 | // the state. Selectors can also be defined inline where they're used instead of 81 | // in the slice file. For example: `useSelector((state: RootState) => state.getClusterAsnyc.data)` 82 | export const selectCluster = (state: RootState) => state.cluster; 83 | export const { updateNameSpace } = clusterViewSlice.actions; 84 | export default clusterViewSlice.reducer; -------------------------------------------------------------------------------- /client/src/components/home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SourceMap from './sourceMap' 3 | import SearchBar from './SearchBar'; 4 | import FooterDrawer from './trace-table/footerDrawer' 5 | import Logout from './logout' 6 | import '../styles/home.scss' 7 | import { useAppSelector } from '../lib/hooks' 8 | import { selectSourceMap, ViewType } from './sourceMapSlice' 9 | import { RenderType, selectSpanMap } from './span-table/spanMapSlice' 10 | import SpanMap from './span-table/spanMap' 11 | 12 | /** 13 | * Primary Application Page which hosts Source Map and essential navigation features 14 | * @Remarks Serves as an abstraction layer for core application features - can be swapped out app App level for other feature pages as/if needed. 15 | */ 16 | const home = () => { 17 | 18 | const sourceMap = useAppSelector(selectSourceMap) 19 | 20 | const spanList = useAppSelector(selectSpanMap) 21 | 22 | const uiElements: Array = []; 23 | 24 | // Make sure to overwite clickable elements with 'pointer-events: auto' 25 | if(sourceMap.type == ViewType.cluster){ 26 | uiElements.push(
) 27 | uiElements.push(
) 28 | } 29 | 30 | if(spanList.type == RenderType.render){ 31 | uiElements.push(
) 32 | } 33 | 34 | return ( 35 | <> 36 | 37 | 38 | {uiElements} 39 | 40 | ) 41 | } 42 | 43 | export default home -------------------------------------------------------------------------------- /client/src/components/loadingScreen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import '../styles/home.scss' 3 | 4 | export enum LoadingScreenType { percentage, cyclingStops } 5 | 6 | type LoadingScreenProps = { 7 | type: LoadingScreenType 8 | } 9 | 10 | /** 11 | * Reusable component which renders a loading screen 12 | * @param type determines the type of loading screen which will render 13 | * @remarks Percentage calculation is mocked and for demonstration purposes only 14 | */ 15 | const loadingScreen = ({type = LoadingScreenType.cyclingStops}: LoadingScreenProps) => { 16 | if(type === LoadingScreenType.percentage) { 17 | const [progressPercent, setProgressPercent] = useState(0); 18 | 19 | useEffect(()=> { 20 | console.log(progressPercent); 21 | const updateProgress = () => setProgressPercent(Math.min(100, progressPercent + Math.max(0, Math.floor(Math.random() * 24)))) 22 | if (progressPercent < 96) { 23 | setTimeout(updateProgress, 100) 24 | } 25 | }, [progressPercent]) 26 | return ( 27 | <> 28 |
29 |
30 |
31 |
{progressPercent}%
32 |
33 |
34 | 35 | ) 36 | } 37 | else { 38 | const [currentProgress, setCurrentProgress] = useState('Loading'); 39 | 40 | const updateProgress = () => setCurrentProgress(currentProgress.indexOf("...") === -1 ? currentProgress + '.' : currentProgress.replace('...', '')); 41 | setTimeout(updateProgress, 150); 42 | 43 | return ( 44 | <> 45 |
46 |
47 |
48 |
{currentProgress}
49 |
50 |
51 | 52 | ) 53 | } 54 | } 55 | 56 | 57 | export default loadingScreen 58 | -------------------------------------------------------------------------------- /client/src/components/logout.tsx: -------------------------------------------------------------------------------- 1 | import React, { FormEvent, useEffect, useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import '../styles/logout.scss'; 4 | 5 | const logout = () => { 6 | let navigate = useNavigate(); 7 | 8 | const buttonClicked = () => { 9 | console.log('button clicked!'); 10 | navigate('/', {state : { autoLoad : false}}); 11 | } 12 | 13 | return ( 14 | 15 | ); 16 | 17 | } 18 | 19 | export default logout; -------------------------------------------------------------------------------- /client/src/components/searchBarSlice.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../store"; 3 | import { config } from '../constants/config' 4 | import { stat } from "fs"; 5 | 6 | export type NamespaceData = NamespaceElement[]; 7 | 8 | 9 | export interface NamespaceElement { 10 | name: string 11 | } 12 | 13 | export interface SearchData { 14 | id: string, 15 | type: string, 16 | traceID?: string, 17 | traceStart?: string, 18 | traceDuration?: string, 19 | serviceCount?:number, 20 | spanCount?:number, 21 | label: any 22 | } 23 | 24 | export interface SearchResult { 25 | data?: SearchData, 26 | classes?: any 27 | } 28 | 29 | export interface Search { 30 | type: 'cluster' | 'trace', 31 | status: 'idle' | 'loading' | 'failed', 32 | namespace: NamespaceData | undefined, 33 | data: SearchResult 34 | } 35 | 36 | const initialState: Search = { 37 | type: 'cluster', 38 | status: 'idle', 39 | namespace: undefined, 40 | data: {} 41 | } 42 | 43 | 44 | //sends an async call to the backend to get the trace data that would show up on the search bar in the trace view screen 45 | export const getTraceViewInfo = createAsyncThunk( 46 | 'searchBar/traceInfo', 47 | async (traceId:string) => { 48 | //Endpoint is a work in progress 49 | const response = await fetch(config.url + `/api/traces/getSearchbarTraceView/${traceId}`) 50 | const data = await response.json(); 51 | return data; 52 | } 53 | ) 54 | 55 | const searchReducer = createSlice({ 56 | name: 'searchBar', 57 | initialState: initialState, 58 | reducers: { 59 | updateData: (state, action: PayloadAction) => { 60 | state.namespace = action.payload; 61 | } 62 | }, 63 | 64 | extraReducers: (builder) => { 65 | builder 66 | .addCase(getTraceViewInfo.pending, (state) => { 67 | state.status = 'loading'; 68 | }) 69 | .addCase(getTraceViewInfo.fulfilled, (state, action: PayloadAction) => { 70 | state.status = 'idle'; 71 | state.data = action.payload; 72 | }) 73 | .addCase(getTraceViewInfo.rejected, (state) => { 74 | state.status = 'failed'; 75 | }); 76 | } 77 | }) 78 | 79 | 80 | export const selectNameSpace = (state: RootState) => state.search.namespace; 81 | export const selectSearchTraceResult = (state: RootState) => state.search.data; 82 | 83 | export default searchReducer.reducer; -------------------------------------------------------------------------------- /client/src/components/sourceMap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAppDispatch, useAppSelector } from '../lib/hooks'; 3 | import ClusterView from './clusterView' 4 | import TraceView from './traceView' 5 | import { selectSourceMap, ViewType } from './sourceMapSlice' 6 | import '../styles/home.scss' 7 | 8 | /** 9 | * Renders a specific Source Map "View" (utilizing Cytoscape) 10 | * @Views : Currently Supports both Cluster View and Trace View 11 | */ 12 | const sourceMap = () => { 13 | 14 | const viewType = useAppSelector(selectSourceMap); 15 | 16 | const view = viewType.type === ViewType.cluster ? : 17 | 18 | return ( 19 | <> 20 |
21 | {view} 22 |
23 | 24 | ) 25 | }; 26 | 27 | export default sourceMap; 28 | -------------------------------------------------------------------------------- /client/src/components/sourceMapSlice.ts: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../store"; 3 | import { Cluster } from './clusterView'; 4 | 5 | export enum ViewType {cluster, trace} 6 | 7 | const initialState: View = { 8 | type: ViewType.cluster, 9 | data: undefined 10 | } 11 | 12 | export interface View { 13 | type: ViewType, 14 | data?: string 15 | } 16 | 17 | /** 18 | * Handles reducer logic related to Source Map View Type Updates 19 | */ 20 | export const sourceMapSlice = createSlice({ 21 | name: 'sourceMap', 22 | initialState: initialState, 23 | reducers: { 24 | changeView: (state, action: PayloadAction) => { 25 | state.type = action.payload.type, 26 | state.data = action.payload.data 27 | } 28 | } 29 | }) 30 | 31 | export const { changeView } = sourceMapSlice.actions; 32 | export const selectSourceMap = (state: RootState) => state.sourceMap; 33 | export default sourceMapSlice.reducer; 34 | -------------------------------------------------------------------------------- /client/src/components/span-table/spanData.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux'; 3 | import { useAppDispatch } from '../../lib/hooks'; 4 | import { selectSpanDataList } from './spanDataSlice'; 5 | import '../../styles/spanTable.scss' 6 | 7 | const spanData= (props: any) => { 8 | 9 | 10 | const dispatch = useAppDispatch(); 11 | 12 | const jsxElements = (() => { 13 | const result: Array> = [] 14 | 15 | const tags = props.spanData.tags 16 | 17 | for (let i = 0; i < tags.length; i++) { 18 | const e = tags[i]; 19 | 20 | const entryKey = `span-data-tag-entry-${i}` 21 | 22 | 23 | const key = tags[i].key 24 | const type = tags[i].type 25 | const value = tags[i].value 26 | 27 | result.push([ 28 |
29 |
30 | {i}: [Key: {key}, Type: {type}, Value: {value}] 31 |
32 |
33 | ]) 34 | } 35 | 36 | return result})() 37 | 38 | const jsxWarnings = (() => { 39 | const result: Array> = [] 40 | 41 | const warnings = props.spanData.warnings 42 | 43 | if (warnings === null) {return 'null'} 44 | 45 | for (let i = 0; i < warnings.length; i++) { 46 | const e = warnings[i]; 47 | 48 | const entryKey = `span-data-tag-entry-${i}` 49 | 50 | result.push([ 51 |
52 |
53 | {e} 54 |
55 |
56 | ]) 57 | } 58 | return result})() 59 | 60 | const startTime = props.spanData.startTime 61 | 62 | const time: any = new Date(startTime/1000).toString() 63 | 64 | 65 | return ( 66 |
67 |
ProcessID: {props.spanData.processID}
68 |
Duration: {props.spanData.duration} μs
69 |
Operation Name: {props.spanData.operationName}
70 |
Start Time: {time}
71 |
Warnings: {jsxWarnings}
72 |
Tags: {jsxElements}
73 |
74 | ) 75 | } 76 | 77 | export default spanData; -------------------------------------------------------------------------------- /client/src/components/span-table/spanDataSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../../store"; 3 | import { config } from '../../constants/config' 4 | import { useSelector } from "react-redux"; 5 | import sourceMapSlice, { selectSourceMap } from "../sourceMapSlice"; 6 | 7 | 8 | const initialState: SingleSpanData = { 9 | status: 'idle', 10 | data: [], 11 | } 12 | 13 | export interface SingleSpanData { 14 | status: string 15 | data: OneSpanData 16 | } 17 | 18 | type SpanNames = String 19 | 20 | 21 | type OneSpanData = SpanNames[] 22 | 23 | 24 | 25 | export const getSpanDataAsync = createAsyncThunk( 26 | 'spanData/getSpanData', 27 | async (spanId: String) => { 28 | 29 | const url = config.url + '/api/traces/getIndivSpanDetails/' + spanId 30 | 31 | const response = await fetch(url) 32 | const data = await response.json(); 33 | 34 | return data; 35 | } 36 | ) 37 | /** 38 | * Handles reducer logic related to Span Data View Type Updates 39 | */ 40 | export const spanDataSlice = createSlice({ 41 | name: 'spanData', 42 | initialState: initialState, 43 | reducers: { 44 | }, 45 | extraReducers: (builder) => { 46 | builder 47 | .addCase(getSpanDataAsync.pending, (state) => { 48 | state.status = 'loading'; 49 | }) 50 | .addCase(getSpanDataAsync.fulfilled, (state, action: PayloadAction) => { 51 | state.status = 'idle'; 52 | state.data = action.payload; 53 | }) 54 | .addCase(getSpanDataAsync.rejected, (state) => { 55 | state.status = 'failed'; 56 | }); 57 | } 58 | }) 59 | 60 | export const selectSpanDataList = (state: RootState) => state.spanData; 61 | export default spanDataSlice.reducer; 62 | -------------------------------------------------------------------------------- /client/src/components/span-table/spanHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux'; 3 | import { useAppDispatch } from '../../lib/hooks'; 4 | import '../../styles/spanTable.scss' 5 | import { selectSourceMap } from '../sourceMapSlice'; 6 | import { selectSpanMap } from './spanMapSlice'; 7 | import { changeRenderView, RenderType } from './spanMapSlice'; 8 | import { useAppSelector } from '../../lib/hooks'; 9 | import { selectSearchTraceResult } from '../searchBarSlice'; 10 | 11 | const spanHeader = () => { 12 | 13 | const { data, id } = useSelector(selectSpanMap) 14 | console.log('spanHeaderData: ', data) 15 | const traceId = useSelector(selectSourceMap) 16 | 17 | const dispatch = useAppDispatch(); 18 | 19 | let traceData = useAppSelector(selectSearchTraceResult); 20 | let exportedtraceViewData: any= traceData.data 21 | let currentTraceId = 'placeholder' 22 | if (exportedtraceViewData) { 23 | currentTraceId = exportedtraceViewData.traceID 24 | } 25 | 26 | function loadNewSpanTable(type: RenderType) { 27 | dispatch(changeRenderView({type: RenderType.noRender})) 28 | } 29 | 30 | return ( 31 |
32 |
33 |
Span Details
34 | 35 |
36 |
Pod Name: {data}
37 |
TraceID: {currentTraceId}
38 |
39 | ) 40 | } 41 | 42 | export default spanHeader -------------------------------------------------------------------------------- /client/src/components/span-table/spanList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux'; 3 | import { useAppDispatch } from '../../lib/hooks'; 4 | import { changeSpanDataView, selectSpanResultsMap, spanViewType } from './spanResultsMapSlice'; 5 | import { getSpanDataAsync } from './spanDataSlice'; 6 | import { selectSpanTableList } from './spanListSlice'; 7 | import '../../styles/spanTable.scss' 8 | import SpanData from './spanData' 9 | import SpanResultsMap from './spanResultsMap'; 10 | import { ViewType } from '../sourceMapSlice'; 11 | 12 | const spanList = () => { 13 | 14 | const { data } = useSelector(selectSpanTableList) 15 | const dispatch = useAppDispatch(); 16 | 17 | function loadNewSpanResults(type: spanViewType, spanId: String) { 18 | console.log('in loadNewSpanResults') 19 | dispatch(changeSpanDataView({type})) 20 | } 21 | 22 | const { type } = useSelector(selectSpanResultsMap) 23 | 24 | const jsxElements = (() => { 25 | const result: Array> = [] 26 | 27 | result.push([
]) 28 | 29 | for (let i = 0; i < data.length; i++) { 30 | const e = data[i]; 31 | 32 | const entryKey = `span-table-entry-${i}` 33 | console.log(e) 34 | console.log('duration', data[i].spanData.duration) 35 | 36 | const spanData = 'hello' 37 | const spanID = e.spanIds 38 | 39 | result.push([ 40 |
41 |
42 | 43 |
SpanID: {data[i].spanIds}
44 |
45 |
46 | 47 |
48 |
49 | ]) 50 | } 51 | 52 | return result; 53 | })() 54 | 55 | return ( 56 |
57 | {jsxElements} 58 |
59 | ) 60 | } 61 | 62 | 63 | export default spanList -------------------------------------------------------------------------------- /client/src/components/span-table/spanListSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../../store"; 3 | import { config } from '../../constants/config' 4 | import { useSelector } from "react-redux"; 5 | import sourceMapSlice, { selectSourceMap } from "../sourceMapSlice"; 6 | 7 | 8 | const initialState: SpanTable = { 9 | status: 'idle', 10 | data: [], 11 | } 12 | 13 | export interface SpanTable { 14 | status: string 15 | data: SpanTableData 16 | } 17 | 18 | //changed to any for passing props 19 | type SpanNames = any 20 | 21 | type SpanTableData = SpanNames[] 22 | 23 | export const getSpanTableAsync = createAsyncThunk( 24 | 'spanList/getSpanNames', 25 | async (traceData: any) => { 26 | const { processTarget, traceId } = traceData 27 | const url = config.url + '/api/traces/getSpansInProcess/' + traceId + '/' + processTarget 28 | 29 | //Use these logs as a first step towards troubleshooting trace fetch requests: 30 | console.log("Fetching Data From: ") 31 | console.log(url) 32 | 33 | const response = await fetch(url) 34 | const data = await response.json(); 35 | console.log(data); 36 | return data; 37 | } 38 | ) 39 | 40 | export const spanListSlice = createSlice({ 41 | name: 'spanDataTable', 42 | initialState: initialState, 43 | reducers: { 44 | }, 45 | extraReducers: (builder) => { 46 | builder 47 | .addCase(getSpanTableAsync.pending, (state) => { 48 | state.status = 'loading'; 49 | state.data = []; 50 | }) 51 | .addCase(getSpanTableAsync.fulfilled, (state, action: PayloadAction) => { 52 | state.status = 'idle'; 53 | state.data = action.payload; 54 | }) 55 | .addCase(getSpanTableAsync.rejected, (state) => { 56 | state.status = 'failed'; 57 | }); 58 | } 59 | }) 60 | 61 | export const selectSpanTableList = (state: RootState) => state.spanTable; 62 | export default spanListSlice.reducer; 63 | -------------------------------------------------------------------------------- /client/src/components/span-table/spanMap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAppDispatch, useAppSelector } from '../../lib/hooks'; 3 | import SpanTableContent from './spanTableContent' 4 | import { selectSpanMap, RenderType } from './spanMapSlice' 5 | import '../../styles/home.scss' 6 | 7 | const spanMap = () => { 8 | 9 | const renderType = useAppSelector(selectSpanMap); 10 | 11 | const render = renderType.type === RenderType.render ? : null 12 | 13 | return ( 14 | <> 15 |
16 | {render} 17 |
18 | 19 | ) 20 | }; 21 | 22 | export default spanMap; -------------------------------------------------------------------------------- /client/src/components/span-table/spanMapSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from '../../store'; 3 | 4 | 5 | export enum RenderType {noRender, render} 6 | 7 | const initialState: Render = { 8 | type: RenderType.noRender, 9 | data: 'placeholder', 10 | id: 'placeholder' 11 | } 12 | 13 | export interface Render { 14 | type: RenderType, 15 | data?: String, 16 | id?: String 17 | } 18 | 19 | 20 | export const spanMapSlice = createSlice({ 21 | name: 'spanMap', 22 | initialState: initialState, 23 | reducers: { 24 | changeRenderView: (state, action: PayloadAction) => { 25 | state.type = action.payload.type, 26 | state.data = action.payload.data, 27 | state.id = action.payload.id 28 | } 29 | } 30 | }) 31 | 32 | export const { changeRenderView } = spanMapSlice.actions; 33 | export const selectSpanMap = (state: RootState) => state.spanMap; 34 | export default spanMapSlice.reducer; 35 | -------------------------------------------------------------------------------- /client/src/components/span-table/spanResultsMap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAppDispatch, useAppSelector } from '../../lib/hooks'; 3 | import SpanData from './spanData' 4 | import { selectSpanResultsMap, spanViewType } from './spanResultsMapSlice' 5 | import '../../styles/home.scss' 6 | 7 | 8 | interface Props { 9 | spanData?: any; 10 | } 11 | 12 | const spanResultsMap = (Props: Props) => { 13 | 14 | const renderType = useAppSelector(selectSpanResultsMap); 15 | 16 | console.log('Props in spanResultsMap', Props) 17 | 18 | const render = renderType.type === spanViewType.render ? : null 19 | 20 | return ( 21 | <> 22 |
23 | {render} 24 |
25 | 26 | ) 27 | }; 28 | 29 | export default spanResultsMap; -------------------------------------------------------------------------------- /client/src/components/span-table/spanResultsMapSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from '../../store'; 3 | 4 | 5 | export enum spanViewType {noRender, render} 6 | 7 | const initialState: Render = { 8 | type: spanViewType.noRender, 9 | 10 | } 11 | 12 | export interface Render { 13 | type: spanViewType, 14 | 15 | } 16 | 17 | export const spanResultsMapSlice = createSlice({ 18 | name: 'spanResultsMap', 19 | initialState: initialState, 20 | reducers: { 21 | changeSpanDataView: (state, action: PayloadAction) => { 22 | state.type = action.payload.type 23 | } 24 | } 25 | }) 26 | 27 | export const { changeSpanDataView } = spanResultsMapSlice.actions; 28 | export const selectSpanResultsMap = (state: RootState) => state.spanResultsMap; 29 | export default spanResultsMapSlice.reducer; 30 | -------------------------------------------------------------------------------- /client/src/components/span-table/spanTableContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../styles/spanTable.scss' 3 | import SpanList from './spanList' 4 | import SpanHeader from './spanHeader' 5 | 6 | 7 | /** 8 | * Renders the content held within the Trace Table Drawer 9 | * @Remarks Renders all content for the main portion of the Trace Table Drawer 10 | */ 11 | const traceTableContent = () => { 12 | return ( 13 |
14 | 15 | 16 |
17 | ) 18 | } 19 | 20 | export default traceTableContent -------------------------------------------------------------------------------- /client/src/components/trace-table/drawerSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../../store"; 3 | import { config } from '../../constants/config' 4 | 5 | /** 6 | * Handles redux store management related to the opening and closing of drawers. 7 | * @remarks If you intend to add additional Drawers, you will need to add additional selectors and reducers here and in the store. 8 | */ 9 | export enum DrawerType { traceView } 10 | 11 | export type Drawer = { 12 | type: DrawerType 13 | } 14 | 15 | interface DrawerData { 16 | id: string, 17 | label: string, 18 | type: string, 19 | duration: number, 20 | response: number, 21 | method: string, 22 | } 23 | 24 | export interface DrawerState { 25 | isOpen: boolean, 26 | data: DrawerData | undefined 27 | } 28 | 29 | /** 30 | * Retrieves the Trace Data for the TraceTable Drawer 31 | * @remarks Asynchronously fetches data. 32 | * @returns A promise which yields a collection of Trace Data in the following format: {@link DrawerData} 33 | */ 34 | export const getTraceListAsync = createAsyncThunk( 35 | 'traceTable/getTraceData', 36 | async () => { 37 | const response = await fetch(config.url + '/api/traces') 38 | const data = await response.json(); 39 | return data.data; 40 | } 41 | ) 42 | 43 | const initialState: DrawerState = { 44 | isOpen: false, 45 | data: undefined, 46 | } 47 | 48 | export const drawerSlice = createSlice({ 49 | name: 'drawer', 50 | initialState: initialState, 51 | reducers: { 52 | toggleIsOpen: (state) => { 53 | state.isOpen = !state.isOpen; 54 | } 55 | }, 56 | }) 57 | 58 | export const selectTraceTableDrawerIsOpen = (state: RootState) => state.traceDrawer.isOpen; 59 | 60 | export const { toggleIsOpen: toggleIsOpen } = drawerSlice.actions; 61 | 62 | export default drawerSlice.reducer; -------------------------------------------------------------------------------- /client/src/components/trace-table/durationSelector.tsx: -------------------------------------------------------------------------------- 1 | import e from 'express' 2 | import React, {useEffect, useRef, useState} from 'react' 3 | import { useAppDispatch, useAppSelector } from '../../lib/hooks'; 4 | import { getTraceTableDataAsync, selectService, updateLookback, Lookback} from './tableListSlice'; 5 | 6 | 7 | type Props = {} 8 | 9 | /** 10 | * Allows for the selection of a single element from a collection of elements. 11 | * @remarks Currently features a hard-coded implementation for a TraceTable but can be repurposed for reusability via prop-drilling. 12 | * @renders A "duration selector" with five individual elements inside which can be selected via mouse click. 13 | */ 14 | const durationSelector = (props: Props) => { 15 | 16 | const dispatch = useAppDispatch(); 17 | const [activeDuration, setActiveDuration] = useState(-1) 18 | const activeService = useAppSelector(selectService); 19 | const {all, long, medium, short, micro} = { 20 | all: 'All', 21 | long: '15m', 22 | medium: '5m', 23 | short: '2m', 24 | micro: '1m', 25 | } 26 | 27 | const {allId, longId, mediumId, shortId, microId} = { 28 | allId: `duration-selector-${all}`, 29 | longId: `duration-selector-${long}`, 30 | mediumId: `duration-selector-${medium}`, 31 | shortId: `duration-selector-${short}`, 32 | microId: `duration-selector-${micro}` 33 | } 34 | 35 | const refs = [ 36 | useRef(null), 37 | useRef(null), 38 | useRef(null), 39 | useRef(null), 40 | useRef(null), 41 | ] 42 | 43 | useEffect(() => { 44 | if(activeDuration === -1) { 45 | updateActiveDuration(4); 46 | } 47 | }); 48 | 49 | const durations = [ 50 |
handleClick(0)} key={allId}>{all}
, 51 |
handleClick(1)} key={longId}>{long}
, 52 |
handleClick(2)} key={mediumId}>{medium}
, 53 |
handleClick(3)} key={shortId}>{short}
, 54 |
handleClick(4)} key={microId}>{micro}
, 55 | ] 56 | 57 | function handleClick(index: number): any { 58 | updateActiveDuration(index); 59 | fetchDurationTraceData(index); 60 | } 61 | function updateActiveDuration(index: number) { 62 | setActiveDuration(index); 63 | console.log("INDEX IS" + index + " in updateActiveDuration") 64 | refs.forEach((ref, i)=> { 65 | if (i === index) { 66 | ref.current?.setAttribute('class', 'duration-selector-active') 67 | } 68 | else { 69 | ref.current?.setAttribute('class', 'duration-selector') 70 | } 71 | }) 72 | } 73 | 74 | function fetchDurationTraceData(index: number) { 75 | let lookback:Lookback; 76 | console.log(`INDEX = ${index}`) 77 | switch(index) { 78 | case 0: lookback = "2d"; break; 79 | case 1: lookback = "15m"; break; 80 | case 2: lookback = "5m"; break; 81 | case 3: lookback = "2m"; break; 82 | case 4: lookback = "1m"; break; 83 | default: lookback = "2d"; break; 84 | } 85 | dispatch(updateLookback(lookback)) 86 | dispatch(getTraceTableDataAsync({activeService:activeService, lookback:lookback})) 87 | } 88 | 89 | return ( 90 |
91 | {durations} 92 |
93 | ) 94 | } 95 | 96 | export default durationSelector -------------------------------------------------------------------------------- /client/src/components/trace-table/footerDrawer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react' 2 | import '../../styles/home.scss' 3 | import { useAppDispatch, useAppSelector } from '../../lib/hooks' 4 | import { selectTraceTableDrawerIsOpen, toggleIsOpen } from './drawerSlice' 5 | import FooterDrawerHandle, {DrawerTabProps} from './footerDrawerTab' 6 | import TraceTable from './traceTableContent' 7 | import { getTraceTableDataAsync, selectService } from './tableListSlice' 8 | 9 | /** 10 | * Parent level component for managing "Drawer"-type elements in the window's Footer. 11 | * @renders Either a "Tab" at the bottom of the page when closed OR an expanded window when opened. 12 | * @remarks Currently features a hard-coded implementation of a TraceTable but can be quickly repurposed for reusability via prop-drilling. 13 | */ 14 | export const footerDrawer = () => { 15 | const dispatch = useAppDispatch(); 16 | const drawerIsOpen = useAppSelector(selectTraceTableDrawerIsOpen); 17 | const activeService = useAppSelector(selectService); 18 | 19 | const data: DrawerTabProps = getDrawerTabData(); 20 | 21 | /** 22 | * Updates when drawer's @see {drawerIsOpen} is opened or closed. Will subsequently trigger the drawer to open or close via CSS Animation. 23 | * @Reference See home.scss for additional context 24 | */ 25 | let cssId = drawerIsOpen ? 'drawer-opened' : 'drawer-closed' 26 | 27 | const handleClick = () => { 28 | const defaultLookback = "1m" 29 | 30 | dispatch(toggleIsOpen()) 31 | dispatch(getTraceTableDataAsync({activeService: activeService, lookback: defaultLookback})) 32 | } 33 | 34 | return ( 35 |
36 | 37 | 38 |
39 | ) 40 | 41 | /** 42 | * Helper function which assigns data based on drawer's opened or closed state 43 | */ 44 | function getDrawerTabData(): DrawerTabProps { 45 | if(drawerIsOpen) { 46 | return { 47 | iconId: 'expand_more', 48 | handleText: 'Hide View', 49 | } 50 | } 51 | else { 52 | return { 53 | iconId: 'expand_less', 54 | handleText: 'Trace Table', 55 | } 56 | } 57 | } 58 | } 59 | 60 | export default footerDrawer 61 | -------------------------------------------------------------------------------- /client/src/components/trace-table/footerDrawerTab.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export type DrawerTabProps = { 4 | handleClick?: () => void, 5 | handleText: string, 6 | iconId: string, 7 | } 8 | /** 9 | * Renders a single Tab for a Footer Drawer Component 10 | * @remarks This component is currently hard-coded to render a tab for the Trace Table 11 | * @beta This component should be refactored for reusability 12 | */ 13 | const footerDrawerTab = ({handleClick, handleText, iconId}: DrawerTabProps) => { 14 | return ( 15 |
{handleText} 16 | 17 | {iconId} 18 |
19 | ) 20 | } 21 | 22 | export default footerDrawerTab -------------------------------------------------------------------------------- /client/src/components/trace-table/tableDropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | import React, { JSXElementConstructor, useEffect } from 'react'; 2 | import { useAppSelector, useAppDispatch } from '../../lib/hooks'; 3 | import { getTraceTableDataAsync, updateService, selectService, getServicesAsync, getAllServices, selectLookback } from './tableListSlice'; 4 | 5 | const tableDropDownMenu = ():JSX.Element => { 6 | 7 | const dispatch = useAppDispatch(); 8 | const activeLookback = useAppSelector(selectLookback); 9 | //hands changing the services state 10 | const changeService = (e:any) => { 11 | 12 | //this should call the dispatch to change the active service 13 | dispatch(updateService(e.target.value)); 14 | dispatch(getTraceTableDataAsync({activeService:e.target.value, lookback:activeLookback})) 15 | //then call the async function to get table data passing in the new service value. 16 | return 17 | } 18 | 19 | //populates the dropdown menu with services 20 | const DropDownOptions: React.ReactElement[] =[] 21 | const servicesData = useAppSelector(getAllServices) 22 | 23 | useEffect(() => { 24 | if(servicesData.length === 0) { 25 | dispatch(getServicesAsync()); 26 | } 27 | },[]) 28 | 29 | useEffect(() => { 30 | //update the active service to servicesdata[0]; 31 | dispatch(updateService(servicesData[0])); 32 | },[servicesData]) 33 | 34 | servicesData.forEach(element => { 35 | DropDownOptions.push () 36 | }) 37 | 38 | 39 | return ( 40 |
41 |
42 | 45 |
46 |
47 | ) 48 | } 49 | 50 | 51 | export default tableDropDownMenu; -------------------------------------------------------------------------------- /client/src/components/trace-table/tableHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | /** 4 | * Defines the contents of the Trace Table Headers 5 | * @Remarks Manages spacing between elements via a CSS Grid defined in traceTable.scss 6 | */ 7 | const tableHeader = () => { 8 | return ( 9 |
10 |
Timestamp
11 |
Trace Id
12 |
Response Time
13 |
Response
14 |
Method
15 |
URL
16 |
Namespaces
17 |
18 | ) 19 | } 20 | 21 | export default tableHeader -------------------------------------------------------------------------------- /client/src/components/trace-table/tableList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'react-dom'; 3 | import { useSelector } from 'react-redux'; 4 | import { useAppDispatch } from '../../lib/hooks'; 5 | import { changeView, ViewType } from '../sourceMapSlice'; 6 | import { getTraceDataAsync } from '../traceViewSlice'; 7 | import { selectTableList, TraceTableEntry } from './tableListSlice'; 8 | import { getTraceViewInfo } from '../searchBarSlice'; 9 | 10 | /** 11 | * Renders the list of elements contained within the Trace Table Drawer 12 | * @Remarks Pre-fetching when hovering over drawerTabHandle will greatly improve performance 13 | */ 14 | const tableList = () => { 15 | 16 | const { data } = useSelector(selectTableList) 17 | const dispatch = useAppDispatch(); 18 | 19 | function loadNewTraceSourceMap(type: ViewType, traceId: string) { 20 | dispatch(changeView({type: ViewType.trace})) 21 | dispatch(getTraceDataAsync(traceId)); 22 | dispatch(getTraceViewInfo(traceId)) 23 | } 24 | 25 | const jsxElements = (() => { 26 | const result: Array> = [] 27 | 28 | result.push([
]) 29 | 30 | const validMethods = ['GET', 'POST', 'CREATE', 'DELETE'] 31 | for (let i = 0; i < data.length; i++) { 32 | const e = data[i]; 33 | const maxUrlLength = 40; 34 | let renderedUrl = e.data.url; 35 | if (e.data.url== 'unknown'){ 36 | renderedUrl = 'N/A' 37 | } 38 | 39 | //NOTE: Some data being filtered due to issues with JaegerQuery. 40 | //Temporary band-aid to hide bugged results. 41 | const trimFrontLength = 20; 42 | // if(e.data.url == 'unknown' || !validMethods.includes(e.data.method)) { 43 | // continue; 44 | // } 45 | renderedUrl = renderedUrl.indexOf('http://') != -1 ? renderedUrl.slice(trimFrontLength, renderedUrl.length) : renderedUrl 46 | if(renderedUrl.length > maxUrlLength) { 47 | renderedUrl = renderedUrl.slice(0, maxUrlLength) + '...' 48 | } 49 | 50 | const entryKey = `trace-table-entry-${i}` 51 | const fieldKeys = []; 52 | for(let j = 0; j < 7; j++) {fieldKeys.push(`trace-table-entry-${i}-field-${j}`)} 53 | 54 | result.push([ 55 |
56 |
{e.data.timestamp.slice(0, e.data.timestamp.indexOf("-") +3)}
57 |
loadNewTraceSourceMap(ViewType.trace, e.data.id)}>{e.data.id}
58 |
{e.data.duration}ms
59 |
{e.data.response}
60 |
{e.data.method}
61 |
{renderedUrl}
62 |
{e.data.namespaces}
63 |
64 | ]) 65 | } 66 | 67 | return result; 68 | })() 69 | 70 | return ( 71 |
72 | {jsxElements} 73 |
74 | ) 75 | } 76 | 77 | export default tableList -------------------------------------------------------------------------------- /client/src/components/trace-table/tableListSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../../store"; 3 | import { config } from '../../constants/config' 4 | 5 | 6 | const initialState: TraceTable = { 7 | status: 'idle', 8 | data: [], 9 | service: [], 10 | activeService: undefined, 11 | lookback: '1m' 12 | } 13 | 14 | export type TraceTableEntry = { 15 | data: { 16 | timestamp: string, 17 | id: string, 18 | duration: string, 19 | response: string, 20 | method: string, 21 | url: string, 22 | namespaces: string 23 | } 24 | } 25 | 26 | export type TraceDataTableParameters = { 27 | activeService: string | undefined, 28 | lookback: string 29 | } 30 | 31 | type TraceTableData = TraceTableEntry[] 32 | 33 | type AllServices = Services[] 34 | 35 | export type Services = string 36 | 37 | export type Lookback = '2d'| '15m'| '5m'| '2m'| '1m' 38 | 39 | export interface TraceTable { 40 | status: string 41 | data: TraceTableData 42 | service: AllServices 43 | activeService: Services | undefined 44 | lookback: Lookback 45 | } 46 | 47 | export const getServicesAsync = createAsyncThunk( 48 | 'traceTable/getServices', 49 | async () => { 50 | const url = config.url + `/api/traces/getTraceViewServices` 51 | //Use these logs as a first step towards troubleshooting trace fetch requests: 52 | const response = await fetch(url) 53 | if (!response.ok){ 54 | return []; 55 | } 56 | const data = await response.json(); 57 | const sortedData = data.sort();; 58 | return data; 59 | } 60 | ) 61 | 62 | export const getTraceTableDataAsync = createAsyncThunk( 63 | 'traceTable/getTraceTableData', 64 | async (parameters:TraceDataTableParameters) => { 65 | //enters the if statement if an async call is made to retrieve data from the backend before an active service is received 66 | if (parameters.activeService === undefined){ 67 | console.log('no active service available') 68 | return []; 69 | } 70 | 71 | const url = config.url + `/api/traces/getAll/${parameters.activeService}/${parameters.lookback}` 72 | 73 | //Use these logs as a first step towards troubleshooting trace fetch requests: 74 | console.log("Fetching Data From: ") 75 | console.log(url) 76 | 77 | const response = await fetch(url) 78 | const data = await response.json(); 79 | console.log(data); 80 | return data; 81 | } 82 | ) 83 | /** 84 | * Handles reducer logic related to Source Map View Type Updates 85 | */ 86 | export const tableListSlice = createSlice({ 87 | name: 'traceTable', 88 | initialState: initialState, 89 | reducers: { 90 | updateService: (state, action: PayloadAction) => { 91 | state.activeService = action.payload; 92 | }, 93 | updateLookback: (state, action: PayloadAction) => { 94 | state.lookback = action.payload; 95 | } 96 | }, 97 | extraReducers: (builder) => { 98 | builder 99 | .addCase(getTraceTableDataAsync.pending, (state) => { 100 | state.status = 'loading'; 101 | }) 102 | .addCase(getTraceTableDataAsync.fulfilled, (state, action: PayloadAction) => { 103 | state.status = 'idle'; 104 | state.data = action.payload; 105 | }) 106 | .addCase(getTraceTableDataAsync.rejected, (state) => { 107 | state.status = 'failed'; 108 | }) 109 | .addCase(getServicesAsync.pending, (state) => { 110 | state.status = 'loading'; 111 | }) 112 | .addCase(getServicesAsync.fulfilled, (state, action: PayloadAction) => { 113 | state.status = 'idle'; 114 | state.service = action.payload; 115 | }) 116 | .addCase(getServicesAsync.rejected, (state) => { 117 | state.status = 'failed'; 118 | }) 119 | } 120 | }) 121 | 122 | export const selectTableList = (state: RootState) => state.traceTable; 123 | export const getAllServices = (state: RootState) => state.traceTable.service; 124 | export const selectService = (state: RootState) => state.traceTable.activeService; 125 | export const selectLookback= (state: RootState) => state.traceTable.lookback; 126 | export const { updateLookback } = tableListSlice.actions; 127 | export const { updateService } = tableListSlice.actions; 128 | export default tableListSlice.reducer; 129 | -------------------------------------------------------------------------------- /client/src/components/trace-table/traceTableContent.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import '../../styles/traceTable.scss' 3 | import TableContent from './tableList' 4 | import TableHeader from './tableHeader' 5 | import WindowHeader from './windowHeader' 6 | 7 | /** 8 | * Renders the content held within the Trace Table Drawer 9 | * @Remarks Renders all content for the main portion of the Trace Table Drawer 10 | */ 11 | const traceTableContent = () => { 12 | return ( 13 |
14 | 15 | 16 | 17 |
18 | ) 19 | } 20 | 21 | export default traceTableContent 22 | -------------------------------------------------------------------------------- /client/src/components/trace-table/windowHeader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useSelector } from 'react-redux' 3 | import '../../styles/traceTable.scss' 4 | import DurationSelector from './durationSelector' 5 | import { selectTableList } from './tableListSlice' 6 | import TableDropDownMenu from './tableDropdownMenu' 7 | 8 | /** 9 | * @WIP Not yet implemented 10 | */ 11 | const windowHeader = () => { 12 | 13 | const tableList = useSelector(selectTableList) 14 | 15 | return ( 16 |
17 |

Trace View - {tableList.data.length} Results

18 |
19 | Services: 20 | 21 |
22 |
23 | Show: 24 | 25 |
26 |
27 | ) 28 | } 29 | 30 | export default windowHeader 31 | -------------------------------------------------------------------------------- /client/src/components/traceView.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import CytoscapeComponent from 'react-cytoscapejs'; 3 | import cytoscape from 'cytoscape'; 4 | import coseBilkent from 'cytoscape-cose-bilkent'; 5 | import styleSheet from '../styles/Stylesheet' 6 | import options from '../constants/CytoscapeConfig' 7 | import { useSelector } from 'react-redux'; 8 | import { selectTraceView, TraceData } from './traceViewSlice'; 9 | import { useAppDispatch } from '../lib/hooks'; 10 | import { changeRenderView, RenderType } from './span-table/spanMapSlice'; 11 | import { getSpanTableAsync } from './span-table/spanListSlice'; 12 | import { node } from 'webpack'; 13 | import { selectSourceMap } from './sourceMapSlice'; 14 | import { useAppSelector } from '../lib/hooks'; 15 | import { selectSearchTraceResult } from './searchBarSlice'; 16 | 17 | export interface Trace { 18 | data: TraceData 19 | status: 'idle' | 'loading' | 'failed'; 20 | } 21 | 22 | cytoscape.use(coseBilkent); 23 | 24 | /** 25 | * Renders trace data by fetching it from the Redux Store 26 | * @Remarks Trace Data requests should be dispatched by the components handling the event which caused a trace to load. 27 | */ 28 | const TraceView = () => { 29 | 30 | const traceViewData = useSelector(selectTraceView); 31 | const layout = options(); 32 | 33 | const dispatch = useAppDispatch(); 34 | 35 | 36 | let traceData = useAppSelector(selectSearchTraceResult); 37 | let exportedtraceViewData: any= traceData.data 38 | 39 | 40 | function loadNewSpanTable(type: RenderType, data: string, id: string, traceData: any) { 41 | dispatch(changeRenderView({type: RenderType.render, data, id})) 42 | dispatch(getSpanTableAsync(traceData)); 43 | } 44 | 45 | const trace = useSelector(selectSourceMap) 46 | const traceId = trace.data 47 | 48 | let myCyRef; 49 | 50 | if (traceViewData.data.length === 0){ 51 | return ( 52 |
Loading...
53 | ) 54 | } 55 | else { 56 | return ( 57 |
63 | { 79 | myCyRef = cy; 80 | const reset = () => {cy.elements().remove()} 81 | 82 | cy.on("dblclick", "node", evt => { 83 | var node = evt.target; 84 | 85 | cy.fit( cy.$(':selected'), 50 ); 86 | setTimeout( function(){ 87 | cy.panBy({ 88 | x: -300, 89 | y: 0 90 | }) 91 | }, 10) 92 | const nodeData = node.data() 93 | 94 | let currentTraceId = 'placeholder' 95 | if (exportedtraceViewData) { 96 | currentTraceId = exportedtraceViewData.traceID 97 | } 98 | 99 | const traceData = { 100 | processTarget : nodeData.id, 101 | traceId : currentTraceId 102 | } 103 | 104 | loadNewSpanTable(RenderType.render, nodeData.label, nodeData.id, traceData) 105 | }); 106 | }} 107 | 108 | > 109 |
110 | ) 111 | } 112 | } 113 | 114 | export default TraceView; 115 | -------------------------------------------------------------------------------- /client/src/components/traceViewSlice.tsx: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { RootState, AppThunk } from "../store"; 3 | import { config } from '../constants/config' 4 | import { Trace } from "./traceView"; 5 | 6 | const initialState: Trace = { 7 | data: [], 8 | status: 'idle' 9 | } 10 | 11 | export type TraceData = Data[] 12 | 13 | export type Data = { 14 | data: { 15 | id: string, 16 | label: string, 17 | type: string, 18 | } 19 | classes: string, 20 | } 21 | 22 | export const getTraceDataAsync = createAsyncThunk( 23 | 'traceView/getTraceData', 24 | async (traceId: string) => { 25 | const url = config.url + '/api/traces/getTraceView/' + traceId 26 | 27 | //Use these logs as a first step towards troubleshooting trace fetch requests: 28 | console.log("Fetching Data From: ") 29 | console.log(url) 30 | 31 | const response = await fetch(url) 32 | const data = await response.json(); 33 | console.log(data); 34 | return data; 35 | } 36 | ) 37 | 38 | export const traceViewSlice = createSlice({ 39 | name: 'traceView', 40 | initialState: initialState, 41 | reducers: { 42 | updateData: (state, action: PayloadAction) => { 43 | state.data = action.payload; 44 | } 45 | }, 46 | extraReducers: (builder) => { 47 | builder 48 | .addCase(getTraceDataAsync.pending, (state) => { 49 | state.status = 'loading'; 50 | state.data = []; 51 | }) 52 | .addCase(getTraceDataAsync.fulfilled, (state, action: PayloadAction) => { 53 | state.status = 'idle'; 54 | state.data = action.payload; 55 | }) 56 | .addCase(getTraceDataAsync.rejected, (state) => { 57 | state.status = 'failed'; 58 | }); 59 | } 60 | }) 61 | 62 | export const selectTraceView = (state: RootState) => state.traceView; 63 | 64 | export default traceViewSlice.reducer; -------------------------------------------------------------------------------- /client/src/components/traceViewTester.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | 3 | /** 4 | * Returns sample data for the Trace View for testing purposes 5 | * @temp This function can soon be deleted 6 | */ 7 | const TraceTester = () => { 8 | const [traceData, setTraceData] = useState({data: ""}); 9 | 10 | useEffect( () => { 11 | getTraceData(); 12 | }); 13 | 14 | async function getTraceData(): Promise { 15 | console.log("Weback conquered"); 16 | if(traceData.data) return; 17 | 18 | const httpHeaders = {'ngrok-skip-browser-warning': 'true'} 19 | const requestHeaders: HeadersInit = new Headers(httpHeaders); 20 | 21 | return ( 22 | fetch('https://e2de-136-52-47-115.ngrok.io') 23 | .then((response) => response.json()) 24 | .then(data => { 25 | setTraceData(data) 26 | }) 27 | .catch((err) => {"Unable to fetch Trace Data: " + err }) 28 | ); 29 | } 30 | 31 | return ( 32 | <> 33 |

Welcome to Konstellation TraceTester!

34 |
{Object.values(traceData as object)}
35 | 36 | ) 37 | } 38 | 39 | type TraceData = { 40 | data: string | void, 41 | } 42 | 43 | export default TraceTester -------------------------------------------------------------------------------- /client/src/constants/CytoscapeConfig.tsx: -------------------------------------------------------------------------------- 1 | const options2 = () => { return { 2 | name: 'cose-bilkent', 3 | // name: 'breadthfirst', 4 | ready: function () { 5 | }, 6 | // Called on `layoutstop` 7 | stop: function () { 8 | }, 9 | // 'draft', 'default' or 'proof" 10 | // - 'draft' fast cooling rate 11 | // - 'default' moderate cooling rate 12 | // - "proof" slow cooling rate 13 | quality: 'default', 14 | // Whether to include labels in node dimensions. Useful for avoiding label overlap 15 | nodeDimensionsIncludeLabels: false, 16 | // number of ticks per frame; higher is faster but more jerky 17 | refresh: 30, 18 | // Whether to fit the network view after when done 19 | fit: true, 20 | // Padding on fit 21 | padding: 50, 22 | // Whether to enable incremental mode 23 | randomize: true, 24 | // Node repulsion (non overlapping) multiplier 25 | nodeRepulsion: 7500, 26 | // Ideal (intra-graph) edge length 27 | idealEdgeLength: 200, 28 | // Divisor to compute edge forces 29 | edgeElasticity: 0.45, 30 | // Nesting factor (multiplier) to compute ideal edge length for inter-graph edges 31 | nestingFactor: 0.1, 32 | // Gravity force (constant) 33 | gravity: 0.25, 34 | // Maximum number of iterations to perform 35 | numIter: 2500, 36 | // Whether to tile disconnected nodes 37 | tile: true, 38 | // Type of layout animation. The option set is {'during', 'end', false} 39 | animate: 'end', 40 | // Duration for animate:end 41 | animationDuration: 500, 42 | // Amount of vertical space to put between degree zero nodes during tiling (can also be a function) 43 | tilingPaddingVertical: 10, 44 | // Amount of horizontal space to put between degree zero nodes during tiling (can also be a function) 45 | tilingPaddingHorizontal: 10, 46 | // Gravity range (constant) for compounds 47 | gravityRangeCompound: 1.5, 48 | // Gravity force (constant) for compounds 49 | gravityCompound: 1.0, 50 | // Gravity range (constant) 51 | gravityRange: 3.8, 52 | // Initial cooling factor for incremental layout 53 | initialEnergyOnIncremental: 0.5} 54 | } 55 | 56 | export const options = () => { return { 57 | name: 'cose-bilkent', 58 | // name: 'breadthfirst', 59 | ready: function () { 60 | }, 61 | // Called on `layoutstop` 62 | stop: function () { 63 | }, 64 | // 'draft', 'default' or 'proof" 65 | // - 'draft' fast cooling rate 66 | // - 'default' moderate cooling rate 67 | // - "proof" slow cooling rate 68 | quality: 'default', 69 | // Whether to include labels in node dimensions. Useful for avoiding label overlap 70 | nodeDimensionsIncludeLabels: false, 71 | // number of ticks per frame; higher is faster but more jerky 72 | refresh: 30, 73 | // Whether to fit the network view after when done 74 | fit: true, 75 | // Padding on fit 76 | padding: 50, 77 | // Whether to enable incremental mode 78 | randomize: true, 79 | // Node repulsion (non overlapping) multiplier 80 | nodeRepulsion: 4500, 81 | // Ideal (intra-graph) edge length 82 | idealEdgeLength: 100, 83 | // Divisor to compute edge forces 84 | edgeElasticity: 0.45, 85 | // Nesting factor (multiplier) to compute ideal edge length for inter-graph edges 86 | nestingFactor: 0.1, 87 | // Gravity force (constant) 88 | gravity: 0.25, 89 | // Maximum number of iterations to perform 90 | numIter: 2500, 91 | // Whether to tile disconnected nodes 92 | tile: true, 93 | // Type of layout animation. The option set is {'during', 'end', false} 94 | animate: 'end', 95 | // Duration for animate:end 96 | animationDuration: 500, 97 | // Amount of vertical space to put between degree zero nodes during tiling (can also be a function) 98 | tilingPaddingVertical: 10, 99 | // Amount of horizontal space to put between degree zero nodes during tiling (can also be a function) 100 | tilingPaddingHorizontal: 10, 101 | // Gravity range (constant) for compounds 102 | gravityRangeCompound: 1.5, 103 | // Gravity force (constant) for compounds 104 | gravityCompound: 1.0, 105 | // Gravity range (constant) 106 | gravityRange: 3.8, 107 | // Initial cooling factor for incremental layout 108 | initialEnergyOnIncremental: 0.5} 109 | } 110 | 111 | export default options2; 112 | -------------------------------------------------------------------------------- /client/src/constants/config.ts: -------------------------------------------------------------------------------- 1 | 2 | const production = { 3 | // url: `http://${process.env.DOMAIN}:${process.env.PORT}`, //For testing purposes only 4 | url: `http://${process.env.DOMAIN}:${process.env.PORT}`, 5 | } 6 | 7 | const development = { 8 | url: `http://${process.env.DOMAIN}:${process.env.PORT}`, 9 | } 10 | 11 | export const config = (() => { 12 | if (process.env.NODE_ENV === "production") { 13 | return production; 14 | } 15 | else { 16 | return development; 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import App from './App'; 4 | import { BrowserRouter, HashRouter, Route, Routes } from 'react-router-dom'; 5 | import { Provider } from 'react-redux'; 6 | import { store } from './store'; 7 | import Login from './Login'; 8 | 9 | // Global interface so any react component can access Electron's API 10 | // specified in /electron/preload.js 11 | declare global { 12 | interface Window { 13 | electronAPI: any; 14 | } 15 | } 16 | 17 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 18 | const root = createRoot(document.getElementById('root')!); 19 | root.render( 20 | 21 | 22 | 23 | } /> 24 | } /> 25 | 26 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /client/src/lib/hooks.ts: -------------------------------------------------------------------------------- 1 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux' 2 | import type { RootState, AppDispatch } from '../store' 3 | 4 | // Use throughout your app instead of plain `useDispatch` and `useSelector` 5 | export const useAppDispatch: () => AppDispatch = useDispatch 6 | 7 | // Allows you to extract data from the Redux store state, using a selector function. 8 | export const useAppSelector: TypedUseSelectorHook = useSelector -------------------------------------------------------------------------------- /client/src/store.ts: -------------------------------------------------------------------------------- 1 | import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit' 2 | import clusterReducer from './components/clusterViewSlice' 3 | import sourceMapReducer from './components/sourceMapSlice' 4 | import searchReducer from './components/searchBarSlice' 5 | import drawerReducer from './components/trace-table/drawerSlice' 6 | import traceViewReducer from './components/traceViewSlice' 7 | import traceTableReducer from './components/trace-table/tableListSlice' 8 | import spanTableReducer from './components/span-table/spanListSlice' 9 | import spanMapReducer from './components/span-table/spanMapSlice' 10 | import spanResultsMapReducer from './components/span-table/spanResultsMapSlice' 11 | import spanDataReducer from './components/span-table/spanDataSlice' 12 | 13 | export const store = configureStore({ 14 | reducer: { 15 | sourceMap: sourceMapReducer, 16 | cluster: clusterReducer, 17 | search: searchReducer, 18 | traceDrawer: drawerReducer, 19 | traceView: traceViewReducer, 20 | traceTable: traceTableReducer, 21 | spanTable: spanTableReducer, 22 | spanMap: spanMapReducer, 23 | spanResultsMap: spanResultsMapReducer, 24 | spanData: spanDataReducer 25 | }, 26 | }) 27 | 28 | export type RootState = ReturnType 29 | export type AppDispatch = typeof store.dispatch; 30 | export type AppThunk = ThunkAction< 31 | ReturnType, 32 | RootState, 33 | unknown, 34 | Action 35 | >; -------------------------------------------------------------------------------- /client/src/styles/Stylesheet.tsx: -------------------------------------------------------------------------------- 1 | import colors from '../colors'; 2 | import { kubesColors } from '../colors'; 3 | 4 | const uri = 'https://www.kindpng.com/picc/m/74-746008_icon-for-user-user-icon-png-white-transparent.png'; 5 | const encoded = encodeURIComponent(uri) 6 | 7 | const styleSheet = [ 8 | { 9 | selector: "node[type='root']", 10 | style: { 11 | //color is text color of label 12 | color: 'white', 13 | 14 | //label is displayed above node 15 | label: 'data(label)', 16 | //"background-image": 'data(data)', 17 | //width and height can also be assigned as percentage values. 18 | "background-width": ["100px", "100px", "100px"], 19 | "background-height": ["100px", "100px", "100px"], 20 | "background-position-x": ["0px", "0px"], 21 | "background-position-y": ["0px", "0px"], 22 | "background-image-smoothing": 'yes', 23 | "background-fit": "contain", 24 | shape: 'rectangle', 25 | 'background-color': 'whitesmoke', 26 | 'border-width': '5px', 27 | 'text-outline-color': 'black', 28 | 'text-outline-width': 1, 29 | height: 50, 30 | width: 50, 31 | }, 32 | }, 33 | { 34 | selector: "node[type='pod']", 35 | style: { 36 | label: 'data(label)', 37 | color: 'white', 38 | 'border-width': '5px', 39 | 'border-style': 'solid', 40 | 'border-color': '#EFEFEF', 41 | 'border-opacity': '1', 42 | // 'text-wrap': 'ellipsis', 43 | // 'text-max-width': '10px', 44 | height: 50, 45 | width: 50, 46 | 'font-family': 'SF Pro', 47 | 'background-color': '#EFEFEF', 48 | 'underlay-color': '#ffffff', 49 | 'underlay-opacity': 0.3, 50 | 'underlay-padding': 3, 51 | 'underlay-shape': 'ellipse', 52 | 'text-outline-color': 'black', 53 | 'text-outline-width': 1 54 | }, 55 | css: { 56 | 'text-wrap': 'ellipsis', 57 | 'text-max-width': '10px', 58 | 'text-outline-color': 'black', 59 | } 60 | }, 61 | { 62 | selector: "node[type='trace']", 63 | style: { 64 | label: 'data(label)', 65 | color: 'white', 66 | 'border-width': '5px', 67 | 'border-style': 'solid', 68 | 'border-color': '#A4C3D2', 69 | 'border-opacity': '1', 70 | 'font-family': 'SF Pro', 71 | height: 50, 72 | width: 50, 73 | 'background-color': '#A4C3D2', 74 | 'text-outline-color': 'black', 75 | 'text-outline-width': 1 76 | } 77 | }, 78 | { 79 | selector: "node[type='client']", 80 | style: { 81 | label: 'data(label)', 82 | color: 'white', 83 | 'border-width': '5px', 84 | 'border-color': 'lightgrey', 85 | 'background-color': 'lightgrey', 86 | 'font-family': 'SF Pro', 87 | 'background-image': 'data:text/html;charset=utf-8' + encoded, 88 | "background-width": ["100px", "100px", "100px"], 89 | "background-height": ["100px", "100px", "100px"], 90 | "background-image-smoothing": 'yes', 91 | "background-fit": "contain", 92 | height: 50, 93 | width: 50, 94 | 'text-outline-color': 'black', 95 | 'text-outline-width': 1 96 | } 97 | }, 98 | { 99 | selector: "node[type='namespace']", 100 | style: { 101 | label: 'data(label)', 102 | color: 'white', 103 | 'border-width': '5px', 104 | 'border-color': '#FFFAA0', 105 | height: 50, 106 | width: 50, 107 | shape: 'triangle', 108 | 'background-color': '#FFFAA0', 109 | 'font-family': 'SF Pro', 110 | 'text-outline-color': 'black', 111 | 'text-outline-width': 1 112 | }, 113 | }, 114 | { 115 | selector: "node[type='root']", 116 | style: { 117 | 'border-width': '5px', 118 | 'border-color': '#006994', 119 | 'font-family': 'SF Pro', 120 | 'text-outline-color': 'black', 121 | 'text-outline-width': 1, 122 | height: 50, 123 | width: 50, 124 | shape: 'rectangle', 125 | 'background-color': '#006994', 126 | }, 127 | }, 128 | { 129 | selector: 'edge', 130 | style: { 131 | width: 1, 132 | 'curve-style': 'bezier' 133 | }, 134 | }, 135 | { 136 | selector: "edge[type='arrow']", 137 | style: { 138 | width: 1, 139 | 'line-color': 'white', 140 | 'font-size': '13px', 141 | // 'target-arrow-color': '#ccc', 142 | 'target-arrow-shape': 'triangle', 143 | 'target-arrow-color': 'white', 144 | 'curve-style': 'bezier', 145 | 'label': 'data(label)', 146 | 'color': 'black', 147 | 'text-background-color': 'white', 148 | 'font-family': 'SF Pro', 149 | }, 150 | }, 151 | { 152 | selector: ".background", 153 | style: { 154 | "text-background-opacity": 1, 155 | "color": "white", 156 | "text-background-color": "#161820", 157 | 'font-family': 'SF Pro', 158 | } 159 | }, 160 | { 161 | selector: ":selected", 162 | css: { 163 | // 'background-color':'green', 164 | 'border-color': 'green', 165 | // 'border-width': '5px' 166 | } 167 | } 168 | ]; 169 | 170 | export default styleSheet -------------------------------------------------------------------------------- /client/src/styles/home.scss: -------------------------------------------------------------------------------- 1 | @use "./theme" as theme; 2 | 3 | @import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200'); 4 | 5 | body { 6 | overflow: hidden; 7 | } 8 | 9 | .footer-drawer-tab { 10 | display: flex; 11 | flex-direction: row; 12 | background: #3C3C3C; 13 | font-size: 1.48vh; 14 | width: 14.479vw; 15 | height: 4.166vh; 16 | border-radius: 12px 12px 0px 0px; 17 | align-items: center; 18 | justify-content: center; 19 | text-align: center; 20 | } 21 | 22 | .footer-drawer-tab:hover { 23 | cursor: pointer; 24 | background: #4a4a4a; 25 | pointer-events: auto; 26 | } 27 | 28 | .home { 29 | display: grid; 30 | grid-template-columns: repeat(5, 1fr); 31 | grid-template-rows: 154fr 881fr 45fr; 32 | grid-column-gap: 0px; 33 | grid-row-gap: 0px; 34 | 35 | .home__pod-details { grid-area: 2 / 3 / 3 / 6; } 36 | .home__trace-table { grid-area: 2 / 1 / 4 / 6; } 37 | .home__source-map { grid-area: 1 / 1 / 4 / 6; } 38 | .home__header { grid-area: 1 / 1 / 2 / 6; } 39 | 40 | .overlay { 41 | pointer-events: none; 42 | z-index: 1; 43 | } 44 | 45 | #trace-table-overlay { 46 | grid-area: 1 / 1 / 4 / 6; 47 | 48 | display: grid; 49 | grid-template-columns: 1fr; 50 | grid-template-rows: 154fr 926fr; 51 | grid-column-gap: 0px; 52 | grid-row-gap: 0px; 53 | 54 | #drawer-closed { 55 | grid-area: 2 / 1 / 2 / 2; 56 | 57 | display: flex; 58 | flex-direction: column; 59 | pointer-events: auto; 60 | transform: translateY(80.5vh); 61 | transition-property: transform; 62 | transition-duration: .2s; 63 | align-items: center; 64 | } 65 | 66 | #drawer-opened { 67 | grid-area: 2 / 1 / 2 / 2; 68 | 69 | display: flex; 70 | flex-direction: column; 71 | pointer-events: auto; 72 | transform: translateY(0vh); 73 | transition-property: transform; 74 | transition-duration: .2s; 75 | align-items: center; 76 | } 77 | } 78 | } 79 | 80 | #loading-message { 81 | justify-content: center; 82 | align-items: center; 83 | text-align: center; 84 | z-index: 2; 85 | } 86 | 87 | #loading-screen { 88 | display: flex; 89 | flex-direction: column; 90 | height: 100vh; 91 | } 92 | @keyframes spinner { 93 | 0% { 94 | transform: rotate(0deg); 95 | } 96 | 100% { 97 | transform: rotate(360deg); 98 | } 99 | } 100 | 101 | .spinner-container { 102 | display: flex; 103 | flex-direction: column; 104 | justify-content: center; 105 | align-items: center; 106 | height: 100vh; 107 | width: 100vw; 108 | gap: 3vh; 109 | } 110 | 111 | .loading-spinner { 112 | width: 50px; 113 | height: 50px; 114 | border: 10px solid #2F80ED; /* Light grey */ 115 | border-top: 10px solid #4a4a4a; /* Black */ 116 | border-radius: 50%; 117 | animation: spinner .9s linear infinite; 118 | } 119 | 120 | .material-symbols-outlined { 121 | margin-left: 1vw; 122 | font-variation-settings: 123 | 'wght' 700, 124 | } 125 | 126 | .overlaydata{ 127 | grid-area: 2 / 3 / 2 / 5; 128 | z-index: 1; 129 | display: grid; 130 | padding-top: 10%; 131 | 132 | } 133 | -------------------------------------------------------------------------------- /client/src/styles/images/Screen_Shot_2022-11-08_at_9.29.34_PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/client/src/styles/images/Screen_Shot_2022-11-08_at_9.29.34_PM.png -------------------------------------------------------------------------------- /client/src/styles/images/eye-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/client/src/styles/images/eye-closed.png -------------------------------------------------------------------------------- /client/src/styles/images/eye-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/client/src/styles/images/eye-open.png -------------------------------------------------------------------------------- /client/src/styles/images/login-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/client/src/styles/images/login-background.png -------------------------------------------------------------------------------- /client/src/styles/login.scss: -------------------------------------------------------------------------------- 1 | // @use "./theme" as theme; 2 | $font-input-size: 1.48vh; 3 | $color-font: #F4F4F4; 4 | $font-family: SF Pro; 5 | $font-weight: 700; 6 | $eye-width: 24px; 7 | $eye-height: 24px; 8 | $letter-spacing: 0em; 9 | $line-height: 19px; 10 | 11 | #login { 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | position: absolute; 16 | width: 100%; 17 | height: 100%; 18 | background-position: center; 19 | margin: 0px; 20 | padding:0px; 21 | } 22 | 23 | // * { 24 | // font-family: SF Pro; 25 | // font-size: 16px; 26 | // font-weight: 700; 27 | // line-height: 19px; 28 | // letter-spacing: 0em; 29 | // text-align: left; 30 | // color: #F4F4F4; 31 | // } 32 | 33 | #login-container { 34 | background: #3C3C3C; 35 | box-shadow: 0px 10px 24px rgba(0, 0, 0, 0.25); 36 | border-radius: 12px; 37 | padding: 30px; 38 | display: flex; 39 | flex-direction: column; 40 | justify-content: center; 41 | align-items: center; 42 | 43 | min-width: 420px; 44 | padding: 8px; 45 | width: 26.04vw; 46 | height: 640.08px; 47 | } 48 | 49 | #login-logo { 50 | // width: 400px; 51 | // height: 53px; 52 | height: auto; 53 | min-width: 20.83vw; 54 | left: 0px; 55 | top: 0px; 56 | 57 | font-family: 'Noto Serif'; 58 | font-style: normal; 59 | font-weight: 500; 60 | font-size: 39.0261px; 61 | line-height: 53px; 62 | letter-spacing: 0.15em; 63 | 64 | color: #EFEFEF; 65 | } 66 | 67 | #login-label { 68 | font-family: $font-family; 69 | font-size: 22px; 70 | font-weight: 510; 71 | line-height: 26px; 72 | letter-spacing: $letter-spacing; 73 | text-align: left; 74 | } 75 | 76 | form { 77 | display: flex; 78 | flex-direction: column; 79 | align-items: center; 80 | justify-content: center; 81 | gap: 32px; 82 | } 83 | 84 | .login-font { 85 | font-family: $font-family; 86 | font-size: $font-input-size; 87 | font-weight: $font-weight; 88 | line-height: $line-height; 89 | letter-spacing: $letter-spacing; 90 | text-align: left; 91 | color: $color-font; 92 | } 93 | 94 | 95 | .login-input { 96 | box-sizing: border-box; 97 | background-color: #3C3C3C; 98 | display: flex; 99 | flex-direction: row; 100 | align-items: center; 101 | padding: 16px; 102 | gap: 8px; 103 | border: 1px solid #919191; 104 | border-radius: 8px; 105 | flex: none; 106 | order: 1; 107 | align-self: stretch; 108 | flex-grow: 0; 109 | width: 0.2271vw; 110 | min-width: 350px; 111 | 112 | font-family: $font-family; 113 | font-size: $font-input-size; 114 | font-weight: $font-weight; 115 | line-height: 19px; 116 | letter-spacing: $letter-spacing; 117 | text-align: left; 118 | color: $color-font; 119 | } 120 | 121 | .passwordContainer { 122 | display: flex; 123 | align-items: center; 124 | justify-content: end; 125 | } 126 | 127 | .eye-close { 128 | background: url(images/eye-closed.png); 129 | width: $eye-width; 130 | height: $eye-height; 131 | cursor: pointer; 132 | background-repeat: no-repeat; 133 | position: absolute; 134 | z-index: 10; 135 | padding-right: 10px; 136 | } 137 | 138 | .eye-open { 139 | background: url(images/eye-open.png); 140 | width: $eye-width; 141 | height: $eye-height; 142 | cursor: pointer; 143 | background-repeat: no-repeat; 144 | position: absolute; 145 | z-index: 10; 146 | padding-right: 10px; 147 | } 148 | 149 | #login-button { 150 | display: flex; 151 | flex-direction: row; 152 | justify-content: center; 153 | align-items: center; 154 | padding: 16px 8px; 155 | 156 | background: #2F80ED; 157 | border-radius: 8px; 158 | border: 0px; 159 | 160 | /* Inside auto layout */ 161 | margin-top: 10px; 162 | 163 | &:hover { 164 | background: #438be9; 165 | } 166 | &:active { 167 | background: #1a66c9; 168 | } 169 | width: 0.2271vw; 170 | min-width: 350px; 171 | height: 56px; 172 | font-family: $font-family; 173 | font-size: $font-input-size; 174 | font-weight: $font-weight; 175 | line-height: $line-height; 176 | letter-spacing: $letter-spacing; 177 | text-align: left; 178 | color: $color-font; 179 | } 180 | 181 | #loading-screen { 182 | z-index: 10; 183 | position: absolute; 184 | } -------------------------------------------------------------------------------- /client/src/styles/logout.scss: -------------------------------------------------------------------------------- 1 | @use "./theme" as theme; 2 | 3 | .button-container { 4 | grid-area: 3 / 1 / 3 / 1; 5 | display: flex; 6 | align-items: flex-start; 7 | z-index: 5; 8 | } 9 | 10 | .logout-button { 11 | // @include theme.button-style; 12 | z-index: 6; 13 | height: auto; 14 | // width: 80px; 15 | height: 3vh; 16 | max-width: 200px; 17 | min-width: 50px; 18 | width: 2.8vw; 19 | color: white; 20 | border-radius: 6px; 21 | font-size: 0.9vh; 22 | background: #3C3C3C; 23 | pointer-events: auto; 24 | 25 | &:hover { 26 | cursor: pointer; 27 | background: #4a4a4a; 28 | pointer-events: auto; 29 | } 30 | } -------------------------------------------------------------------------------- /client/src/styles/searchBar.scss: -------------------------------------------------------------------------------- 1 | .searchBarContainer { 2 | display: grid; 3 | z-index: 1; 4 | grid-area: 1 /1 / 1 / 6; 5 | align-items: center; 6 | } 7 | 8 | .searchBar { 9 | background-color: #3C3C3C; 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: space-around; 13 | align-items: center; 14 | width: 93.33vw; 15 | height: 8.33vh; 16 | border-radius: 12px; 17 | z-index: 1000; 18 | margin: auto; 19 | 20 | img { 21 | /*position: absolute;*/ 22 | width: 15.52vw; 23 | height: auto; 24 | margin-left: .833vw; 25 | 26 | font-family: 'Noto Serif'; 27 | font-style: normal; 28 | font-weight: 500; 29 | font-size: 39.0261px; 30 | line-height: 53px; 31 | letter-spacing: 0.15em; 32 | } 33 | 34 | #namespaceDropDown { 35 | display: flex; 36 | align-items: center; 37 | width: 21.718vw; 38 | 39 | #namespaceText { 40 | display: flex; 41 | align-items:center; 42 | border: 1px solid #919191; 43 | color: #919191; 44 | height: 5.15vh; 45 | width: 80px; 46 | border-radius: 8px 0px 0px 8px; 47 | padding: 0px 16px; 48 | } 49 | .dropdown{ 50 | width: 132px; 51 | 52 | .dropDownOptions { 53 | background-color: #242424; 54 | color: white; 55 | border: 1px solid #919191; 56 | border-radius: 0px 8px 8px 0px; 57 | width: 15.844vw; 58 | height: 5.37vh; 59 | padding-right: 10px; 60 | padding-left: 10px; 61 | text-indent: 10px; 62 | } 63 | } 64 | } 65 | } 66 | 67 | #searchBar { 68 | color: #919191; 69 | align-items: center; 70 | width: 51vw; 71 | display: flex; 72 | justify-content: flex-end; 73 | margin-right: .8333vw; 74 | } 75 | 76 | #searchBarTrace { 77 | margin-left: .833vw; 78 | color: #919191; 79 | align-items: center; 80 | width: 74.6vw; 81 | display: flex; 82 | justify-content: flex-end; 83 | margin-right: .8333vw; 84 | } 85 | 86 | #searchText { 87 | display: flex; 88 | 89 | align-items: center; 90 | color: #919191; 91 | border: 1px solid #919191; 92 | height: 5.2vh; 93 | border-radius: 8px 0px 0px 8px; 94 | padding-left: 10px; 95 | padding-right: 10px; 96 | } 97 | 98 | #searchBarInput{ 99 | background-color: #3C3C3C; 100 | color: #919191; 101 | width: 46.2vw; 102 | height: 5vh; 103 | border: 1px solid #919191; 104 | font-weight: 400; 105 | text-indent: 15px; 106 | } 107 | 108 | #searchBarInputTrace{ 109 | background-color: #3C3C3C; 110 | color: #919191; 111 | width: 69.7vw; 112 | height: 5vh; 113 | border: 1px solid #919191; 114 | font-weight: 400; 115 | text-indent: 15px; 116 | } 117 | 118 | 119 | #searchBarInput:focus{ 120 | outline: none; 121 | } 122 | 123 | #submitButton { 124 | background-color: #242424; 125 | color:#919191; 126 | border: 1px solid #919191; 127 | height: 5.4vh; 128 | border-radius: 0px 8px 8px 0px; 129 | padding: 0px 16px; 130 | } 131 | 132 | #submitButton:hover { 133 | opacity: 0.6; 134 | } 135 | 136 | #submitButton:active { 137 | opacity: 0.3; 138 | } 139 | 140 | #traceSearchBar { 141 | height: 16vh; 142 | flex-direction: column; 143 | justify-content: center; 144 | gap: 18px; 145 | margin-top: 1vh; 146 | } 147 | 148 | #traceSearchBarTopHalf{ 149 | width: 91.66vw; 150 | height: 5.37vh; 151 | align-items: center; 152 | display: flex; 153 | justify-content: flex-start; 154 | } 155 | 156 | #traceSearchBarTraceDetails{ 157 | background-color: #242424; 158 | width: 98%; 159 | border-radius: 6px; 160 | } 161 | 162 | #traceSearchBar #submitButton { 163 | border-radius: 0px 0px 0px 0px; 164 | } 165 | 166 | #returnButton { 167 | background-color: #3C3C3C; 168 | color:#919191; 169 | border: 1px solid #919191; 170 | height: 5.4vh; 171 | border-radius: 0px 8px 8px 0px; 172 | } 173 | 174 | #returnButton:hover { 175 | opacity: 0.6; 176 | } 177 | 178 | #returnButton:active { 179 | opacity: 0.3; 180 | } 181 | 182 | #traceInfoBarContainer { 183 | display: flex; 184 | align-items: center; 185 | justify-content: space-between; 186 | padding: 16px; 187 | height: 3.02vh; 188 | } 189 | 190 | #traceInfoBar { 191 | display: flex; 192 | align-items: center; 193 | gap: 12px; 194 | #traceMapSearchBar { 195 | font-weight: 510; 196 | font-size: 28px; 197 | line-height: 33px; 198 | } 199 | .searchTextPrefix { 200 | font-weight: 400; 201 | font-size: 16px; 202 | line-height: 19px; 203 | height: 19px; 204 | } 205 | .searchData { 206 | font-weight: 700; 207 | font-size: 16px; 208 | line-height: 19px; 209 | height: 19px; 210 | } 211 | } 212 | 213 | .close { 214 | padding-right: 8px; 215 | color: #EFEFEF 216 | } 217 | .close:hover { 218 | opacity: 0.6; 219 | cursor: pointer; 220 | } 221 | .close:active { 222 | opacity: 0.3; 223 | cursor: pointer; 224 | } -------------------------------------------------------------------------------- /client/src/styles/spanTable.scss: -------------------------------------------------------------------------------- 1 | .boldSpan { 2 | font-weight: bold; 3 | } 4 | 5 | .boldSpanTag { 6 | font-weight: bold; 7 | font-size: 1.4vh; 8 | } 9 | 10 | .boldSpanName { 11 | font-weight: bold; 12 | font-size: 1.7; 13 | } 14 | 15 | .boldItalicsSpan { 16 | font-weight: bold; 17 | font-style: italic; 18 | font-size: 1.5vh 19 | } 20 | 21 | .button-close { 22 | background: #3C3C3C; 23 | color: white; 24 | border: none; 25 | font-size: 2vh; 26 | font-weight: 10; 27 | text-align: center; 28 | 29 | } 30 | 31 | .button-close:hover { 32 | cursor: pointer 33 | } 34 | 35 | .header { 36 | display: flex; 37 | flex: inline-block; 38 | justify-content: space-between; 39 | padding-top: 1.1vh; 40 | } 41 | 42 | .span-map { 43 | background: #3C3C3C; 44 | height: 48.5vh; 45 | width: 55.8vw; 46 | border-radius: 12px; 47 | filter: drop-shadow(0px 10px 24px rgba(0, 0, 0, 0.25)); 48 | z-index: 1; 49 | grid-template-columns: 1fr; 50 | grid-template-rows: 154fr 926fr; 51 | grid-column-gap: 0px; 52 | grid-row-gap: 0px; 53 | } 54 | 55 | .spanButton { 56 | background: #3C3C3C; 57 | color: white; 58 | font-weight: 100; 59 | border: none; 60 | font-size: 1.6vh; 61 | display: flex; 62 | align-items: center; 63 | } 64 | 65 | .spanButton:hover { 66 | cursor: pointer 67 | } 68 | 69 | .span-label { 70 | margin-right: 8px; 71 | } 72 | .span-data-entry { 73 | padding-left: 3vw; 74 | font-weight: 200; 75 | } 76 | 77 | .span-data-entry-indent { 78 | padding-left: 4vw; 79 | font-weight: 200; 80 | } 81 | 82 | .span-details { 83 | display: flex; 84 | overflow-y: hidden; 85 | } 86 | 87 | .span-entry { 88 | display: flex; 89 | } 90 | 91 | .spanHeader { 92 | font-size: 2.5vh; 93 | } 94 | 95 | .spanHeaderPodName { 96 | font-size: 1.4vh; 97 | font-weight: 100; 98 | padding-top: 0.7vh; 99 | } 100 | 101 | .span-pod-entry { 102 | padding-top: 1vh; 103 | padding-bottom: 1vh; 104 | overflow-y: hidden; 105 | border-bottom: 1px solid white; 106 | } 107 | 108 | #span-table-list { 109 | overflow-y: scroll; 110 | height:1fr; 111 | max-height: 38vh; 112 | margin-left: 2vw; 113 | margin-right: 2vw; 114 | } 115 | 116 | .span-name { 117 | display: flex; 118 | font-size: 1.6vh; 119 | font-weight: 100; 120 | 121 | align-items: center; 122 | 123 | } 124 | 125 | .span-data-entry { 126 | font-size: 1.4vh 127 | } 128 | 129 | .divider { 130 | border-bottom: 1px solid white; 131 | } 132 | 133 | #span-table-header { 134 | border-bottom: 1px solid white; 135 | padding-bottom: 1vh; 136 | margin-top: 1.3vh; 137 | margin-left: 2vw; 138 | margin-right: 2vw; 139 | } 140 | 141 | .span-map { 142 | overflow-y: hidden; 143 | } 144 | 145 | #span-table-content { 146 | max-height:100vh; 147 | } 148 | 149 | #span-table-overlay { 150 | max-height: 100vh; 151 | } -------------------------------------------------------------------------------- /client/src/styles/theme.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Generic dark-gray container for holding the login elements, search bar, trace table, etc. 3 | */ 4 | @mixin container { 5 | background: #3C3C3C; 6 | box-shadow: 0px 10px 24px rgba(0, 0, 0, 0.25); 7 | border-radius: 12px; 8 | padding: 30px; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: center; 12 | align-items: center; 13 | 14 | /* Make sure to specify width and height! */ 15 | } 16 | 17 | /* Background color for main screen */ 18 | @mixin background { 19 | background-color: #161820; 20 | } 21 | 22 | /* Form used on login */ 23 | @mixin form-style { 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | justify-content: center; 28 | gap: 32px; 29 | } 30 | 31 | @mixin placeholder-style { 32 | height: 19px; 33 | 34 | font-family: 'SF Pro'; 35 | font-style: italic; 36 | font-weight: 400; 37 | font-size: 16px; 38 | line-height: 19px; 39 | /* identical to box height */ 40 | 41 | /* Mid Gray */ 42 | 43 | color: #919191; 44 | 45 | /* Inside auto layout */ 46 | 47 | flex: none; 48 | order: 0; 49 | flex-grow: 0; 50 | } 51 | 52 | @mixin button-style { 53 | /* Blue 1 */ 54 | 55 | display: flex; 56 | flex-direction: row; 57 | justify-content: center; 58 | align-items: center; 59 | padding: 16px 8px; 60 | 61 | background: #2F80ED; 62 | border-radius: 8px; 63 | border: 0px; 64 | 65 | /* Inside auto layout */ 66 | margin-top: 10px; 67 | 68 | &:hover { 69 | background: #438be9; 70 | } 71 | &:active { 72 | background: #1a66c9; 73 | } 74 | } 75 | 76 | /* 77 | Formats all font 78 | */ 79 | * { 80 | // styleName: Body Default Bold; 81 | font-family: SF Pro; 82 | // font-size: 16px; 83 | font-weight: 700; 84 | // line-height: 19px; 85 | // letter-spacing: 0em; 86 | // text-align: left; 87 | // color: #F4F4F4; 88 | } 89 | -------------------------------------------------------------------------------- /client/src/styles/traceTable.scss: -------------------------------------------------------------------------------- 1 | #trace-table-content { 2 | background: #3C3C3C; 3 | height: 82vh; 4 | width: 93.3vw; 5 | border-radius: 12px; 6 | filter: drop-shadow(0px 10px 24px rgba(0, 0, 0, 0.25)); 7 | } 8 | 9 | #trace-table-window-header { 10 | $margin: 16px; 11 | 12 | display: flex; 13 | flex-direction: row; 14 | justify-content: space-between; 15 | width: calc(100% - 2*$margin); 16 | margin: $margin; 17 | 18 | h1 { 19 | font-size: 3.05vh; 20 | font-weight: 500; 21 | margin-left: 10px; 22 | } 23 | } 24 | 25 | #trace-table-header { 26 | display: flex; 27 | flex-direction: row; 28 | justify-content: space-between; 29 | 30 | h1 { 31 | font-size: 3.05vh; 32 | } 33 | } 34 | 35 | $selector-font-size: 1.48vh; 36 | $selector-size: 4.72vh; 37 | 38 | #trace-table-window-header__duration-container { 39 | display: flex; 40 | flex-direction: row; 41 | font-size: $selector-font-size; 42 | 43 | align-items: center; 44 | gap: 5px; 45 | } 46 | 47 | #duration-selector { 48 | display: flex; 49 | flex-direction: row; 50 | padding: 0px .83vw; 51 | font-size: $selector-font-size; 52 | 53 | width: 15.6vw; 54 | gap: .83vw; 55 | height: $selector-size; 56 | border: 1px solid #919191; 57 | border-radius: 12px; 58 | align-items: center; 59 | justify-content: space-around; 60 | } 61 | 62 | .duration-selector { 63 | cursor: pointer; 64 | font-size: $selector-font-size; 65 | 66 | display: flex; 67 | width: 100%; 68 | align-items: center; 69 | justify-content: center; 70 | } 71 | 72 | .duration-selector-active { 73 | cursor: pointer; 74 | font-size: $selector-font-size; 75 | display: flex; 76 | width: 100%; 77 | align-items: center; 78 | justify-content: center; 79 | background-color: #242424; 80 | text-justify: center; 81 | height: calc($selector-size - 1px); 82 | } 83 | 84 | $trace-table-grid-gap: 1.66vw; 85 | $trace-table-long-width: 15.3646vw; 86 | $trace-table-medium-width: 7.4479vw; 87 | $trace-table-slim-width: 5.156vw; 88 | 89 | #trace-table-header { 90 | background: #242424; 91 | border-radius: 6px; 92 | width: 91.666vw; 93 | height: 3.703vh; 94 | padding: .074vh; 95 | margin: auto; 96 | align-items: center; 97 | 98 | display: grid; 99 | grid-template-columns: repeat(2, 15.3646fr) 7.4479fr repeat(2, 5.156fr) repeat(2, 15.3646fr); 100 | grid-template-rows: 1fr; 101 | grid-column-gap: 0px; 102 | grid-row-gap: 0px; 103 | gap: $trace-table-grid-gap; 104 | 105 | div { 106 | color: #FFFFFF; 107 | font-size: 10px; 108 | font-weight: 700; 109 | } 110 | 111 | .timestamp { grid-area: 1 / 1 / 2 / 2; text-align: center;} 112 | .traceId { grid-area: 1 / 2 / 2 / 3; text-align: center;} 113 | .response-time { grid-area: 1 / 3 / 2 / 4; text-align: center;} 114 | .response { grid-area: 1 / 4 / 2 / 5; text-align: center;} 115 | .method { grid-area: 1 / 5 / 2 / 6; text-align: center;} 116 | .url { grid-area: 1 / 6 / 2 / 7; text-align: center;} 117 | .namespaces { grid-area: 1 / 7 / 2 / 8; text-align: center;} 118 | } 119 | 120 | #trace-table-list { 121 | overflow-y: scroll; 122 | display: flex; 123 | gap: 32px; 124 | 125 | flex-direction: column; 126 | width: 100%; 127 | height: 100%; 128 | } 129 | 130 | .entry-spacer { 131 | height: 32px; 132 | } 133 | .trace-table-entry { 134 | 135 | display: grid; 136 | grid-template-columns: repeat(2, 15.3646fr) 7.4479fr repeat(2, 5.156fr) repeat(2, 15.3646fr); 137 | grid-template-rows: 1fr; 138 | grid-column-gap: 0px; 139 | grid-row-gap: 0px; 140 | gap: $trace-table-grid-gap; 141 | 142 | div { 143 | color: #FFFFFF; 144 | font-size: 10px; 145 | font-weight: 300; 146 | } 147 | 148 | .timestamp { grid-area: 1 / 1 / 2 / 2;text-align: center; } 149 | .traceId { 150 | cursor:pointer; 151 | color: rgb(142, 142, 254); 152 | text-decoration: underline; 153 | grid-area: 1 / 2 / 2 / 3; 154 | text-align: center; 155 | } 156 | .response-time { grid-area: 1 / 3 / 2 / 4; text-align: center;} 157 | .response { grid-area: 1 / 4 / 2 / 5; text-align: center;} 158 | .method { grid-area: 1 / 5 / 2 / 6; text-align: center;} 159 | .url { grid-area: 1 / 6 / 2 / 7; text-align: center;} 160 | .namespaces { grid-area: 1 / 7 / 2 / 8; text-align: center;} 161 | 162 | } 163 | 164 | #trace-table-dropdown-menu { 165 | font-size: 1.48vh; 166 | display: flex; 167 | flex-direction: row; 168 | align-items: center; 169 | gap: 5px; 170 | .dropDownOptions{ 171 | background-color: #242424; 172 | color: white; 173 | border: 1px solid #919191; 174 | border-radius: 8px 8px 8px 8px; 175 | width: 15.844vw; 176 | height: 5.37vh; 177 | padding-right: 10px; 178 | padding-left: 10px; 179 | text-indent: 10px; 180 | } 181 | 182 | } -------------------------------------------------------------------------------- /client/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(25, 25, 25); 3 | color: white; 4 | } 5 | #title{ 6 | font-size: large; 7 | height: auto; 8 | } -------------------------------------------------------------------------------- /client/style.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(25, 25, 25); 3 | color: white; 4 | } 5 | 6 | #title{ 7 | font-size: xx-large; 8 | height: auto; 9 | text-align: center; 10 | padding: 5px; 11 | } 12 | 13 | #topology { 14 | display: flex; 15 | width: 98%; 16 | justify-content: center; 17 | } -------------------------------------------------------------------------------- /cluster/auth/drew.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapUsers: | 8 | - userarn: arn:aws:iam::569182268976:user/Admin_Drew 9 | username: Admin_Drew 10 | groups: 11 | - system:masters -------------------------------------------------------------------------------- /cluster/auth/jon.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapUsers: | 8 | - userarn: arn:aws:iam::569182268976:user/Admin_Jon 9 | username: Admin_Jon 10 | groups: 11 | - system:masters -------------------------------------------------------------------------------- /cluster/auth/kat.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapUsers: | 8 | - userarn: arn:aws:iam::569182268976:user/Admin_Kat 9 | username: Admin_Kat 10 | groups: 11 | - system:masters -------------------------------------------------------------------------------- /cluster/auth/richard.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapUsers: | 8 | - userarn: arn:aws:iam::569182268976:user/Admin_Richard 9 | username: Admin_Richard 10 | groups: 11 | - system:masters -------------------------------------------------------------------------------- /cluster/auth/team.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: aws-auth 5 | namespace: kube-system 6 | data: 7 | mapUsers: | 8 | - userarn: arn:aws:iam::569182268976:user/Admin_Drew 9 | username: Admin_Drew 10 | groups: 11 | - system:masters 12 | - userarn: arn:aws:iam::569182268976:user/Admin_Jon 13 | username: Admin_Jon 14 | groups: 15 | - system:masters 16 | - userarn: arn:aws:iam::569182268976:user/Admin_Kat 17 | username: Admin_Kat 18 | groups: 19 | - system:masters 20 | - userarn: arn:aws:iam::569182268976:user/Admin_Richard 21 | username: Admin_Richard 22 | groups: 23 | - system:masters 24 | - userarn: arn:aws:iam::569182268976:user/Admin_Testo 25 | username: Admin_Testo 26 | groups: 27 | - system:masters 28 | -------------------------------------------------------------------------------- /cluster/iam_policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "iam:CreateServiceLinkedRole" 8 | ], 9 | "Resource": "*", 10 | "Condition": { 11 | "StringEquals": { 12 | "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com" 13 | } 14 | } 15 | }, 16 | { 17 | "Effect": "Allow", 18 | "Action": [ 19 | "ec2:DescribeAccountAttributes", 20 | "ec2:DescribeAddresses", 21 | "ec2:DescribeAvailabilityZones", 22 | "ec2:DescribeInternetGateways", 23 | "ec2:DescribeVpcs", 24 | "ec2:DescribeVpcPeeringConnections", 25 | "ec2:DescribeSubnets", 26 | "ec2:DescribeSecurityGroups", 27 | "ec2:DescribeInstances", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:DescribeTags", 30 | "ec2:GetCoipPoolUsage", 31 | "ec2:DescribeCoipPools", 32 | "elasticloadbalancing:DescribeLoadBalancers", 33 | "elasticloadbalancing:DescribeLoadBalancerAttributes", 34 | "elasticloadbalancing:DescribeListeners", 35 | "elasticloadbalancing:DescribeListenerCertificates", 36 | "elasticloadbalancing:DescribeSSLPolicies", 37 | "elasticloadbalancing:DescribeRules", 38 | "elasticloadbalancing:DescribeTargetGroups", 39 | "elasticloadbalancing:DescribeTargetGroupAttributes", 40 | "elasticloadbalancing:DescribeTargetHealth", 41 | "elasticloadbalancing:DescribeTags" 42 | ], 43 | "Resource": "*" 44 | }, 45 | { 46 | "Effect": "Allow", 47 | "Action": [ 48 | "cognito-idp:DescribeUserPoolClient", 49 | "acm:ListCertificates", 50 | "acm:DescribeCertificate", 51 | "iam:ListServerCertificates", 52 | "iam:GetServerCertificate", 53 | "waf-regional:GetWebACL", 54 | "waf-regional:GetWebACLForResource", 55 | "waf-regional:AssociateWebACL", 56 | "waf-regional:DisassociateWebACL", 57 | "wafv2:GetWebACL", 58 | "wafv2:GetWebACLForResource", 59 | "wafv2:AssociateWebACL", 60 | "wafv2:DisassociateWebACL", 61 | "shield:GetSubscriptionState", 62 | "shield:DescribeProtection", 63 | "shield:CreateProtection", 64 | "shield:DeleteProtection" 65 | ], 66 | "Resource": "*" 67 | }, 68 | { 69 | "Effect": "Allow", 70 | "Action": [ 71 | "ec2:AuthorizeSecurityGroupIngress", 72 | "ec2:RevokeSecurityGroupIngress" 73 | ], 74 | "Resource": "*" 75 | }, 76 | { 77 | "Effect": "Allow", 78 | "Action": [ 79 | "ec2:CreateSecurityGroup" 80 | ], 81 | "Resource": "*" 82 | }, 83 | { 84 | "Effect": "Allow", 85 | "Action": [ 86 | "ec2:CreateTags" 87 | ], 88 | "Resource": "arn:aws:ec2:*:*:security-group/*", 89 | "Condition": { 90 | "StringEquals": { 91 | "ec2:CreateAction": "CreateSecurityGroup" 92 | }, 93 | "Null": { 94 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 95 | } 96 | } 97 | }, 98 | { 99 | "Effect": "Allow", 100 | "Action": [ 101 | "ec2:CreateTags", 102 | "ec2:DeleteTags" 103 | ], 104 | "Resource": "arn:aws:ec2:*:*:security-group/*", 105 | "Condition": { 106 | "Null": { 107 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 108 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 109 | } 110 | } 111 | }, 112 | { 113 | "Effect": "Allow", 114 | "Action": [ 115 | "ec2:AuthorizeSecurityGroupIngress", 116 | "ec2:RevokeSecurityGroupIngress", 117 | "ec2:DeleteSecurityGroup" 118 | ], 119 | "Resource": "*", 120 | "Condition": { 121 | "Null": { 122 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 123 | } 124 | } 125 | }, 126 | { 127 | "Effect": "Allow", 128 | "Action": [ 129 | "elasticloadbalancing:CreateLoadBalancer", 130 | "elasticloadbalancing:CreateTargetGroup" 131 | ], 132 | "Resource": "*", 133 | "Condition": { 134 | "Null": { 135 | "aws:RequestTag/elbv2.k8s.aws/cluster": "false" 136 | } 137 | } 138 | }, 139 | { 140 | "Effect": "Allow", 141 | "Action": [ 142 | "elasticloadbalancing:CreateListener", 143 | "elasticloadbalancing:DeleteListener", 144 | "elasticloadbalancing:CreateRule", 145 | "elasticloadbalancing:DeleteRule" 146 | ], 147 | "Resource": "*" 148 | }, 149 | { 150 | "Effect": "Allow", 151 | "Action": [ 152 | "elasticloadbalancing:AddTags", 153 | "elasticloadbalancing:RemoveTags" 154 | ], 155 | "Resource": [ 156 | "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*", 157 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*", 158 | "arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*" 159 | ], 160 | "Condition": { 161 | "Null": { 162 | "aws:RequestTag/elbv2.k8s.aws/cluster": "true", 163 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 164 | } 165 | } 166 | }, 167 | { 168 | "Effect": "Allow", 169 | "Action": [ 170 | "elasticloadbalancing:AddTags", 171 | "elasticloadbalancing:RemoveTags" 172 | ], 173 | "Resource": [ 174 | "arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*", 175 | "arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*", 176 | "arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*", 177 | "arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*" 178 | ] 179 | }, 180 | { 181 | "Effect": "Allow", 182 | "Action": [ 183 | "elasticloadbalancing:ModifyLoadBalancerAttributes", 184 | "elasticloadbalancing:SetIpAddressType", 185 | "elasticloadbalancing:SetSecurityGroups", 186 | "elasticloadbalancing:SetSubnets", 187 | "elasticloadbalancing:DeleteLoadBalancer", 188 | "elasticloadbalancing:ModifyTargetGroup", 189 | "elasticloadbalancing:ModifyTargetGroupAttributes", 190 | "elasticloadbalancing:DeleteTargetGroup" 191 | ], 192 | "Resource": "*", 193 | "Condition": { 194 | "Null": { 195 | "aws:ResourceTag/elbv2.k8s.aws/cluster": "false" 196 | } 197 | } 198 | }, 199 | { 200 | "Effect": "Allow", 201 | "Action": [ 202 | "elasticloadbalancing:RegisterTargets", 203 | "elasticloadbalancing:DeregisterTargets" 204 | ], 205 | "Resource": "arn:aws:elasticloadbalancing:*:*:targetgroup/*/*" 206 | }, 207 | { 208 | "Effect": "Allow", 209 | "Action": [ 210 | "elasticloadbalancing:SetWebAcl", 211 | "elasticloadbalancing:ModifyListener", 212 | "elasticloadbalancing:AddListenerCertificates", 213 | "elasticloadbalancing:RemoveListenerCertificates", 214 | "elasticloadbalancing:ModifyRule" 215 | ], 216 | "Resource": "*" 217 | } 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /cluster/ingress/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | namespace: default 5 | name: fruit-ingress 6 | annotations: 7 | alb.ingress.kubernetes.io/scheme: internet-facing 8 | alb.ingress.kubernetes.io/target-type: ip 9 | spec: 10 | ingressClassName: alb 11 | rules: 12 | - http: 13 | paths: 14 | - path: / 15 | pathType: Prefix 16 | backend: 17 | service: 18 | name: landing-page-service 19 | port: 20 | number: 8080 21 | - path: /apple 22 | pathType: Prefix 23 | backend: 24 | service: 25 | name: apple-service 26 | port: 27 | number: 8080 28 | - path: /banana 29 | pathType: Prefix 30 | backend: 31 | service: 32 | name: banana-service 33 | port: 34 | number: 8080 35 | - path: /durian 36 | pathType: Prefix 37 | backend: 38 | service: 39 | name: durian-service 40 | port: 41 | number: 8080 -------------------------------------------------------------------------------- /cluster/service/apple.yaml: -------------------------------------------------------------------------------- 1 | kind: Pod 2 | apiVersion: v1 3 | metadata: 4 | name: apple-service 5 | labels: 6 | app: apple 7 | spec: 8 | # -- Specify the node this service must run on 9 | nodeSelector: 10 | nodeNum: "1" 11 | containers: 12 | - name: apple-service 13 | image: hashicorp/http-echo 14 | args: 15 | - "-text=apple" 16 | 17 | --- 18 | 19 | kind: Service 20 | apiVersion: v1 21 | metadata: 22 | name: apple-service 23 | spec: 24 | selector: 25 | app: apple 26 | ports: 27 | - port: 8080 28 | targetPort: 5678 29 | protocol: TCP -------------------------------------------------------------------------------- /cluster/service/banana.yaml: -------------------------------------------------------------------------------- 1 | kind: Pod 2 | apiVersion: v1 3 | metadata: 4 | name: banana-service 5 | labels: 6 | app: banana 7 | spec: 8 | nodeSelector: 9 | nodeNum: "3" 10 | containers: 11 | - name: banana-service 12 | image: hashicorp/http-echo 13 | args: 14 | - "-text=banana" 15 | 16 | --- 17 | 18 | kind: Service 19 | apiVersion: v1 20 | metadata: 21 | name: banana-service 22 | spec: 23 | selector: 24 | app: banana 25 | ports: 26 | - port: 8080 27 | targetPort: 5678 -------------------------------------------------------------------------------- /cluster/service/durian.yaml: -------------------------------------------------------------------------------- 1 | kind: Pod 2 | apiVersion: v1 3 | metadata: 4 | name: durian-service 5 | labels: 6 | app: durian 7 | spec: 8 | # -- Specify the node this service must run on 9 | nodeSelector: 10 | nodeNum: "2" 11 | containers: 12 | - name: durian-service 13 | image: hashicorp/http-echo 14 | args: 15 | - "-text=durian" 16 | 17 | --- 18 | 19 | kind: Service 20 | apiVersion: v1 21 | metadata: 22 | name: durian-service 23 | spec: 24 | selector: 25 | app: durian 26 | ports: 27 | - port: 8080 28 | targetPort: 5678 29 | protocol: TCP -------------------------------------------------------------------------------- /cluster/service/hello-landing-page.yaml: -------------------------------------------------------------------------------- 1 | kind: Pod 2 | apiVersion: v1 3 | metadata: 4 | name: landing-page 5 | labels: 6 | app: landing-page 7 | spec: 8 | # -- Specify the node this service must run on 9 | containers: 10 | - image: gcr.io/google-samples/node-hello:1.0 11 | name: hello-world 12 | ports: 13 | - containerPort: 8080 14 | --- 15 | 16 | kind: Service 17 | apiVersion: v1 18 | metadata: 19 | name: landing-page-service 20 | spec: 21 | selector: 22 | app: landing-page 23 | ports: 24 | - port: 8080 25 | targetPort: 8080 26 | protocol: TCP -------------------------------------------------------------------------------- /cluster/service/hello-world.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: load-balancer-example 6 | name: hello-world 7 | spec: 8 | selector: 9 | matchLabels: 10 | app.kubernetes.io/name: load-balancer-example 11 | template: 12 | metadata: 13 | labels: 14 | app.kubernetes.io/name: load-balancer-example 15 | spec: 16 | containers: 17 | - image: gcr.io/google-samples/node-hello:1.0 18 | name: hello-world 19 | ports: 20 | - containerPort: 8080 -------------------------------------------------------------------------------- /cluster/small_node_group.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: eksctl.io/v1alpha5 2 | kind: ClusterConfig 3 | 4 | metadata: 5 | name: Konstellation-Cluster 6 | region: us-west-2 7 | 8 | managedNodeGroups: 9 | - name: kon-small-nodes 10 | instanceType: t2.small 11 | desiredCapacity: 3 12 | -------------------------------------------------------------------------------- /cluster/v2_4_4_ingclass.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: elbv2.k8s.aws/v1beta1 2 | kind: IngressClassParams 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: aws-load-balancer-controller 6 | name: alb 7 | --- 8 | apiVersion: networking.k8s.io/v1 9 | kind: IngressClass 10 | metadata: 11 | labels: 12 | app.kubernetes.io/name: aws-load-balancer-controller 13 | name: alb 14 | spec: 15 | controller: ingress.k8s.aws/alb 16 | parameters: 17 | apiGroup: elbv2.k8s.aws 18 | kind: IngressClassParams 19 | name: alb 20 | -------------------------------------------------------------------------------- /data.txt: -------------------------------------------------------------------------------- 1 | Request from 2607:fb90:580:2929:858a:8a25:eb11:e262 @ 2022-10-24 at 16:33:40 2 | Included Data: Example Trace Data From Server 3 | 4 | Request from 2607:fb90:580:2929:858a:8a25:eb11:e262 @ 2022-10-24 at 16:33:58 5 | Included Data: Example Trace Data From Server 6 | 7 | Request from 2607:fb90:580:2929:858a:8a25:eb11:e262 @ 2022-10-24 at 16:35:25 8 | Included Data: Example Trace Data From Server 9 | 10 | Request from 136.52.47.115 @ 2022-10-24 at 16:35:35 11 | Included Data: Example Trace Data From Server 12 | 13 | Request from 136.52.47.115 @ 2022-10-24 at 16:35:41 14 | Included Data: Example Trace Data From Server 15 | 16 | Request from 136.52.47.115 @ 2022-10-24 at 16:35:51 17 | Included Data: Example Trace Data From Server 18 | 19 | Request from 136.52.47.115 @ 2022-10-24 at 16:36:6 20 | Included Data: Example Trace Data From Server 21 | 22 | Request from 136.52.47.115 @ 2022-10-24 at 16:37:50 23 | Included Data: Example Trace Data From Server 24 | 25 | Request from 136.52.47.115 @ 2022-10-24 at 16:38:53 26 | Included Data: Example Trace Data From Server 27 | 28 | Request from 136.52.47.115 @ 2022-10-24 at 16:38:58 29 | Included Data: Example Trace Data From Server 30 | 31 | Request from 136.52.47.115 @ 2022-10-24 at 16:39:7 32 | Included Data: Example Trace Data From Server 33 | 34 | Request from 136.52.47.115 @ 2022-10-24 at 16:39:40 35 | Included Data: Example Trace Data From Server 36 | 37 | Request from 136.52.47.115 @ 2022-10-24 at 16:39:57 38 | Included Data: Example Trace Data From Server 39 | 40 | Request from 136.52.47.115 @ 2022-10-24 at 16:41:9 41 | Included Data: Example Trace Data From Server 42 | 43 | Request from 136.52.47.115 @ 2022-10-24 at 16:41:10 44 | Included Data: Example Trace Data From Server 45 | 46 | Request from 136.52.47.115 @ 2022-10-24 at 16:41:22 47 | Included Data: Example Trace Data From Server 48 | 49 | Request from 136.52.47.115 @ 2022-10-24 at 16:41:30 50 | Included Data: Example Trace Data From Server 51 | 52 | Request from 136.52.47.115 @ 2022-10-24 at 18:41:26 53 | Included Data: Example Trace Data From Server 54 | 55 | Request from 136.52.47.115 @ 2022-10-24 at 18:43:1 56 | Included Data: Example Trace Data From Server 57 | 58 | Request from 136.52.47.115 @ 2022-10-24 at 18:44:18 59 | Included Data: Example Trace Data From Server 60 | 61 | -------------------------------------------------------------------------------- /dist/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(25, 25, 25); 3 | color: white; 4 | } 5 | #title{ 6 | font-size: large; 7 | height: auto; 8 | } -------------------------------------------------------------------------------- /electron/electron.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, ipcMain } = require("electron"); 2 | const path = require("path"); 3 | // const { electron } = require("process"); 4 | const url = require("url"); 5 | // This makes the CLI commands asynchronous 6 | const util = require('util'); 7 | const exec = util.promisify(require('child_process').exec); 8 | const { config } = require("dotenv"); 9 | const fs = require('fs'); 10 | const readline = require('readline'); 11 | const k8Config = require('@kubernetes/client-node/dist/config'); 12 | let win; 13 | 14 | /** 15 | * @return Return the name of the Cluster declared in .kube/config 16 | * Otherwise return empty 17 | */ 18 | async function getConfigClusterName() { 19 | 20 | try { 21 | 22 | console.log('gettingConfigClusterName...', __dirname) 23 | const home = k8Config.findHomeDir(); 24 | 25 | // Leverage K8's functions to find the config file 26 | 27 | const absolutePath = path.join(home, '.kube/config'); 28 | 29 | // console.log('absolute Path:', absolutePath); 30 | const fileStream = fs.createReadStream(absolutePath); 31 | 32 | const rl = readline.createInterface({ 33 | input: fileStream, 34 | crlfDelay: Infinity 35 | }); 36 | 37 | // Read the file line by line 38 | for await (const line of rl) { 39 | console.log(`Line from file: ${line}`); 40 | if(line.includes('current-context:')) { 41 | // Return name of cluster 42 | return line.split('/')[1]; 43 | } 44 | } 45 | } catch (e) { 46 | console.log('error!', e); 47 | return ''; 48 | } 49 | } 50 | 51 | /** 52 | * 53 | * @remarks 54 | * This method executes a aws CLI command to retrieve the specified field 55 | * @param field The field to retrieve 56 | * @returns a string 57 | */ 58 | async function setAWSField(field, value) { 59 | try { 60 | const { stdout, stderr } = await exec(`aws configure set ${field} ${value}`); 61 | // Should be empty 62 | return stdout; 63 | } catch (e) { 64 | return e; 65 | } 66 | } 67 | 68 | /** 69 | * 70 | * @remarks 71 | * This method executes a aws CLI command to retrieve the specified field 72 | * @param field The field to retrieve 73 | * @returns a string 74 | */ 75 | async function getAWSField(field) { 76 | try { 77 | const { stdout, stderr } = await exec(`aws configure get ${field}`); 78 | console.log('exec:', stdout); 79 | // Strip '\n' from the end of the response string 80 | return stdout.slice(0, stdout.length - 1); 81 | } catch (e) { 82 | console.log('err:', e); 83 | return ''; 84 | } 85 | } 86 | 87 | function createWindow() { 88 | // Create the browser window 89 | // Sets the dimensions and contextBridge between main and renderer processes 90 | win = new BrowserWindow({ 91 | width: 1600, height: 900, 92 | webPreferences: { 93 | preload: path.join(__dirname, 'preload.js') 94 | } 95 | }); 96 | // In production, set the initial browser path to the local bundle generated 97 | // by the Create React App build process. 98 | // In development, set it to localhost to allow live/hot-reloading. 99 | const prodMode = process.env.npm_lifecycle_script.includes('production'); 100 | //const prodMode = process.env. 101 | const appURL = (prodMode) 102 | ? url.format({ 103 | pathname: path.join(__dirname, "../dist/index.html"), 104 | protocol: "file:", 105 | slashes: true, 106 | }) 107 | : "http://localhost:8080"; 108 | console.log('appURL:', appURL); 109 | win.loadURL(appURL); 110 | 111 | 112 | // Automatically open Chrome's DevTools in development mode. 113 | if (!prodMode) { 114 | win.webContents.openDevTools(); 115 | } 116 | } 117 | 118 | /** 119 | * 120 | * @param {*} region 121 | * @param {*} myCluster 122 | * @returns True if created successfully, otherwise false 123 | */ 124 | async function updateKubeConfig(region, myCluster) { 125 | 126 | try { 127 | const { stdout, stderr } = await exec(`aws eks update-kubeconfig --region ${region} --name ${myCluster}`); 128 | 129 | if(stdout.includes('Updated context')) { 130 | return true; 131 | } else { 132 | return false; 133 | } 134 | } catch (e) { 135 | return false; 136 | } 137 | } 138 | 139 | app.whenReady().then(() => { 140 | /** 141 | * @remarks 142 | * Invoked by Login.tsx when the Login button is pressed 143 | * Configures the client's auth using CLI commands 144 | * @param event - event that triggered the 145 | * @param arg - how to configure the client's local files 146 | * @returns True if success, otherwise False 147 | */ 148 | ipcMain.on('on-config', async (event, arg) => { 149 | 150 | // If .kube/config was updated or threw an error 151 | let kubeconfigResp = false; 152 | 153 | // First create a backup of the local AWS fields 154 | const keyBack = await getAWSField('aws_access_key_id'); 155 | const secretBack = await getAWSField('aws_secret_access_key'); 156 | const regionBack = await getAWSField('region'); 157 | // let user change 'output' from json here 158 | 159 | // Then update each field 160 | const keyResp = await setAWSField('aws_access_key_id', arg[0]) 161 | const secretResp = await setAWSField('aws_secret_access_key', arg[1]) 162 | const regionResp = await setAWSField('region', arg[3]) 163 | const outputResp = await setAWSField('output', 'json') 164 | 165 | // Reload the backup AWS fields 166 | const reloadBackups = async () => { 167 | await setAWSField('aws_access_key_id', keyBack); 168 | await setAWSField('aws_secret_access_key', secretBack); 169 | await setAWSField('region', regionBack); 170 | } 171 | 172 | // If no bad responses, then set the Kube Config file 173 | if (!keyResp && !secretResp && !regionResp && !outputResp) { 174 | kubeconfigResp = await updateKubeConfig(arg[3], arg[2]); 175 | // If the kube config couldn't be updated reload backups 176 | if(!kubeconfigResp) { 177 | reloadBackups(); 178 | } 179 | } 180 | // If one of the fields was invalid reload backups 181 | else { 182 | reloadBackups(); 183 | } 184 | 185 | // Trigger another IPC event back to the render process 186 | // Sending individual results in case we want input-specific error messages 187 | event.sender.send('onConfigResp', [keyResp, secretResp, regionResp, outputResp, kubeconfigResp]); 188 | }); 189 | 190 | /** 191 | * @remarks 192 | * Invoked by Login.tsx when the Login button is pressed 193 | * Configures the client's auth using CLI commands 194 | * @param event - event that triggered the 195 | * @returns True if success, otherwise False 196 | */ 197 | ipcMain.on('get-config', async (event) => { 198 | console.log("get-config"); 199 | // Retrieve the user's Access Key, Secret Key, and Region from their local files 200 | const access_key = await getAWSField('aws_access_key_id'); 201 | const secret_key = await getAWSField('aws_secret_access_key'); 202 | const region = await getAWSField('region'); 203 | const cluster_name = await getConfigClusterName(); 204 | 205 | const data = [access_key, secret_key, cluster_name, region]; 206 | console.log('sending config:', data); 207 | 208 | // Send the information to Login.tsx 209 | event.sender.send('onSendConfig', data); 210 | }); 211 | 212 | createWindow(); 213 | }); -------------------------------------------------------------------------------- /electron/preload.js: -------------------------------------------------------------------------------- 1 | const { contextBridge, ipcRenderer } = require('electron') 2 | 3 | /** 4 | * This file bridges the main process, electron.js, with the render process, Login.tsx, 5 | * by exposing specific API using contextBridge. 6 | */ 7 | 8 | const exposedAPI = { 9 | /** 10 | * @remarks 11 | * Invoked by Login button to configure the user's local files 12 | * 13 | * @param channel - listener in electron will receive this message if listening to this channel 14 | * @param func - Callback from login containing the event and response data 15 | */ 16 | onConfig: (func) => ipcRenderer.send('on-config', func), 17 | 18 | /** 19 | * @remarks 20 | * Invoked by electron to send response to Login's button press 21 | * 22 | * @param channel - listeners will receive this message if listening to this channel 23 | * @param func - Callback from electron containing the event and response data 24 | */ 25 | onConfigResp: (channel, func) => { 26 | ipcRenderer.on(channel, func); 27 | }, 28 | 29 | /** 30 | * @remarks 31 | * Invoked by Login to retrieve any locally-stored configurations/credentials 32 | * located in the user's aws and kubeconfig files. 33 | * @param channel - listener in electron will receive this message if listening to this channel 34 | */ 35 | getConfig: () => ipcRenderer.send('get-config'), 36 | 37 | /** 38 | * @remarks 39 | * Invoked by electron to give Login any found user credentials/configurations 40 | * 41 | * @param channel - listeners will receive this message if listening to this channel 42 | * @param func - Callback from electron containing the event and response data 43 | */ 44 | onSendConfig: (channel, func) => { 45 | ipcRenderer.on(channel, func); 46 | }, 47 | 48 | /** 49 | * @remarks 50 | * Invoked by Login on unMount to avoid the accumulation of multiple listeners 51 | */ 52 | unMount: () => ipcRenderer.removeAllListeners() 53 | 54 | }; 55 | 56 | // Exposes API key:value functions to the renderer process 57 | contextBridge.exposeInMainWorld("electronAPI", exposedAPI); -------------------------------------------------------------------------------- /examples/comment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the average of two numbers. 3 | * 4 | * @remarks 5 | * This method is part of the {@link core-library#Statistics | Statistics subsystem}. 6 | * 7 | * @param x - The first input number 8 | * @param y - The second input number 9 | * @returns The arithmetic mean of `x` and `y` 10 | * 11 | * @beta 12 | */ -------------------------------------------------------------------------------- /images/JaegerUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/images/JaegerUI.png -------------------------------------------------------------------------------- /images/KonstellationCluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/images/KonstellationCluster.png -------------------------------------------------------------------------------- /images/konstellation-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/images/konstellation-login.png -------------------------------------------------------------------------------- /images/konstellation-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/images/konstellation-logo.png -------------------------------------------------------------------------------- /images/konstellation-span-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/images/konstellation-span-details.png -------------------------------------------------------------------------------- /images/konstellation-trace-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/images/konstellation-trace-table.png -------------------------------------------------------------------------------- /images/trace-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/images/trace-view.png -------------------------------------------------------------------------------- /konstellation-yaml/setup/00-namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: observability -------------------------------------------------------------------------------- /konstellation-yaml/setup/03-opentelemetry-collector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: opentelemetry.io/v1alpha1 2 | kind: OpenTelemetryCollector 3 | metadata: 4 | name: otel 5 | spec: 6 | config: | 7 | receivers: 8 | otlp: 9 | protocols: 10 | grpc: 11 | http: 12 | processors: 13 | memory_limiter: 14 | check_interval: 1s 15 | limit_percentage: 75 16 | spike_limit_percentage: 15 17 | batch: 18 | send_batch_size: 10000 19 | timeout: 10s 20 | 21 | exporters: 22 | logging: 23 | jaeger: 24 | endpoint: jaeger-collector:14250 25 | tls: 26 | insecure: true 27 | 28 | service: 29 | pipelines: 30 | traces: 31 | receivers: [otlp] 32 | processors: [] 33 | exporters: [logging, jaeger] -------------------------------------------------------------------------------- /konstellation-yaml/setup/04-jaegerconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: jaegertracing.io/v1 2 | kind: Jaeger 3 | metadata: 4 | name: jaeger -------------------------------------------------------------------------------- /konstellation-yaml/setup/05-autoinstrumentation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: opentelemetry.io/v1alpha1 2 | kind: Instrumentation 3 | metadata: 4 | name: my-instrumentation 5 | spec: 6 | exporter: 7 | endpoint: http://otel-collector:4317 8 | propagators: 9 | - tracecontext 10 | - baggage 11 | - b3 12 | sampler: 13 | type: parentbased_traceidratio 14 | argument: "0.25" -------------------------------------------------------------------------------- /konstellation-yaml/setup/06-default-annotation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: default 5 | annotations: 6 | instrumentation.opentelemetry.io/inject-nodejs: "true" -------------------------------------------------------------------------------- /konstellation-yaml/setup/runSecond.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: opentelemetry.io/v1alpha1 2 | kind: OpenTelemetryCollector 3 | metadata: 4 | name: otel 5 | spec: 6 | config: | 7 | receivers: 8 | otlp: 9 | protocols: 10 | grpc: 11 | http: 12 | processors: 13 | memory_limiter: 14 | check_interval: 1s 15 | limit_percentage: 75 16 | spike_limit_percentage: 15 17 | batch: 18 | send_batch_size: 10000 19 | timeout: 10s 20 | 21 | exporters: 22 | logging: 23 | jaeger: 24 | endpoint: jaeger-collector:14250 25 | tls: 26 | insecure: true 27 | 28 | service: 29 | pipelines: 30 | traces: 31 | receivers: [otlp] 32 | processors: [] 33 | exporters: [logging, jaeger] 34 | --- 35 | apiVersion: jaegertracing.io/v1 36 | kind: Jaeger 37 | metadata: 38 | name: jaeger 39 | --- 40 | apiVersion: opentelemetry.io/v1alpha1 41 | kind: Instrumentation 42 | metadata: 43 | name: my-instrumentation 44 | spec: 45 | exporter: 46 | endpoint: http://otel-collector:4317 47 | propagators: 48 | - tracecontext 49 | - baggage 50 | - b3 51 | sampler: 52 | type: parentbased_traceidratio 53 | argument: "0.25" 54 | --- 55 | apiVersion: v1 56 | kind: Namespace 57 | metadata: 58 | name: default 59 | annotations: 60 | instrumentation.opentelemetry.io/inject-nodejs: "true" -------------------------------------------------------------------------------- /otel-related-ymls/instrumentation.yml: -------------------------------------------------------------------------------- 1 | apiVersion: opentelemetry.io/v1alpha1 2 | kind: Instrumentation 3 | metadata: 4 | name: my-instrumentation 5 | spec: 6 | exporter: 7 | endpoint: 8 | propagators: 9 | - tracecontext 10 | - baggage 11 | - b3 12 | sampler: 13 | type: parentbased_traceidratio 14 | argument: "0.25" -------------------------------------------------------------------------------- /otel-related-ymls/namespace-setup.yml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: aiait 5 | labels: 6 | name: test 7 | annotations: 8 | "sidecar.opentelemetry.io/inject" : "true" 9 | "instrumentation.opentelemetry.io/inject-nodejs": "true" 10 | -------------------------------------------------------------------------------- /otel-related-ymls/otel-collector-jaeger.yml: -------------------------------------------------------------------------------- 1 | apiVersion: opentelemetry.io/v1alpha1 2 | kind: OpenTelemetryCollector 3 | metadata: 4 | name: sidecar-for-my-app 5 | spec: 6 | image: public.ecr.aws/aws-observability/aws-otel-collector:latest 7 | mode: sidecar 8 | config: | 9 | receivers: 10 | otlp: 11 | protocols: 12 | grpc: 13 | http: 14 | 15 | processors: 16 | 17 | exporters: 18 | logging: 19 | otlphttp: 20 | endpoint: http://jaeger-collector.observability:4318 21 | tls: 22 | insecure: true 23 | 24 | 25 | service: 26 | pipelines: 27 | traces: 28 | receivers: [otlp] 29 | processors: [] 30 | exporters: [logging, otlphttp] 31 | 32 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "konstellation", 3 | "version": "1.0.0", 4 | "description": "## readme in construction", 5 | "main": "electron/electron.js", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "webpack", 9 | "lint": "eslint . --ext .ts", 10 | "server": "cross-env NODE_ENV=development nodemon server/server.ts", 11 | "proxy": "cross-env NODE_ENV=development nodemon server/proxy.ts", 12 | "client": "npx webpack serve --mode development --progress --hot --color --stats-error-details --stats-children", 13 | "dev": "concurrently \"webpack-dev-server\" \"NODE_ENV=production nodemon server/server.ts\"", 14 | "proxy-prod": "cross-env NODE_ENV=development nodemon server/proxy.ts", 15 | "server-prod": "cross-env NODE_ENV=production nodemon server/server.ts", 16 | "client-prod": "npx webpack serve --mode production --progress --hot --color", 17 | "start": "concurrently \"npx webpack serve --mode production --progress --hot --color --stats-error-details --stats-children\" \"NODE_ENV=production nodemon server/proxy.ts\" \"NODE_ENV=production nodemon server/server.ts\"", 18 | "electron-start": "cross-env NODE_ENV=production && electron .", 19 | "electron-dev": "webpack-dev-server & nodemon --watch . --exec electron .", 20 | "dev-fullstack": "concurrently --kill-others --names \"webpack,electron\" \"npm run dev\" \"npm run electron-dev\"" 21 | }, 22 | "keywords": [], 23 | "author": "", 24 | "license": "ISC", 25 | "dependencies": { 26 | "16": "^0.0.2", 27 | "@emotion/react": "^11.10.4", 28 | "@emotion/styled": "^11.10.4", 29 | "@kubernetes/client-node": "^0.17.1", 30 | "axios": "^0.27.2", 31 | "babel-loader": "^9.1.0", 32 | "canvas": "^2.10.2", 33 | "chart.js": "^3.9.1", 34 | "chartjs-gauge": "^0.3.0", 35 | "chartjs-plugin-datalabels": "^2.1.0", 36 | "concurrently": "^7.5.0", 37 | "cors": "^2.8.5", 38 | "cytoscape": "^3.23.0", 39 | "cytoscape-cose-bilkent": "^4.1.0", 40 | "cytoscape-dom-node": "^1.1.0", 41 | "dotenv": "^16.0.3", 42 | "express": "^4.18.1", 43 | "node": "^19.0.1", 44 | "node-fetch": "^3.2.10", 45 | "nodemon": "^2.0.20", 46 | "prom-client": "^14.1.0", 47 | "react": "^18.2.0", 48 | "react-chartjs-2": "^4.3.1", 49 | "react-cytoscapejs": "^2.0.0", 50 | "react-dom": "^18.2.0", 51 | "react-redux": "^8.0.4", 52 | "react-router-dom": "^6.4.1", 53 | "redux": "^4.2.0", 54 | "sass": "^1.56.0", 55 | "text-to-image": "^5.2.0", 56 | "ts-node": "^10.9.1", 57 | "typescript": "^4.8.3", 58 | "uuid": "^9.0.0" 59 | }, 60 | "devDependencies": { 61 | "@babel/preset-env": "^7.19.3", 62 | "@babel/preset-typescript": "^7.18.6", 63 | "@reduxjs/toolkit": "^1.8.6", 64 | "@testing-library/react": "^13.4.0", 65 | "@testing-library/user-event": "^14.4.3", 66 | "@types/cors": "^2.8.12", 67 | "@types/cytoscape": "^3.19.9", 68 | "@types/express": "^4.17.14", 69 | "@types/jest": "^29.1.1", 70 | "@types/node": "^18.7.20", 71 | "@types/react": "^18.0.25", 72 | "@types/react-cytoscapejs": "^1.2.2", 73 | "@types/react-dom": "^18.0.6", 74 | "@types/uuid": "^8.3.4", 75 | "@types/webpack": "^5.28.0", 76 | "@types/webpack-env": "^1.18.0", 77 | "@typescript-eslint/eslint-plugin": "^5.40.0", 78 | "@typescript-eslint/parser": "^5.40.0", 79 | "copy-webpack-plugin": "^11.0.0", 80 | "cross-env": "^7.0.3", 81 | "css-loader": "^6.7.1", 82 | "dotenv-webpack": "^8.0.1", 83 | "electron": "^21.2.2", 84 | "enzyme": "^3.11.0", 85 | "eslint": "^8.26.0", 86 | "file-loader": "^6.2.0", 87 | "html-webpack-plugin": "^5.5.0", 88 | "img-loader": "^4.0.0", 89 | "jest": "^29.1.1", 90 | "postcss-loader": "^7.0.1", 91 | "prettier": "2.7.1", 92 | "puppeteer": "^18.0.5", 93 | "regenerator-runtime": "^0.13.9", 94 | "sass-loader": "^13.1.0", 95 | "style-loader": "^3.3.1", 96 | "supertest": "^6.2.4", 97 | "ts-jest": "^29.0.3", 98 | "ts-loader": "^9.4.1", 99 | "webpack": "^5.74.0", 100 | "webpack-cli": "^4.10.0", 101 | "webpack-dev-server": "^4.11.1" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /server.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/server.zip -------------------------------------------------------------------------------- /server/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | 3 | PROXY_DOMAIN="localhost" 4 | PROXY_PORT="3010" 5 | 6 | SERVER_DOMAIN="localhost" 7 | SERVER_PORT="3000" 8 | 9 | # PRODUCTION 10 | # Testing Purposes Only - never save production environment variables in local files. 11 | PROXY_DOMAIN_PD="example" 12 | PROXY_PORT="9999" 13 | 14 | SERVER_DOMAIN_PD="example" 15 | SERVER_PORT="9999" 16 | 17 | -------------------------------------------------------------------------------- /server/constants/config.ts: -------------------------------------------------------------------------------- 1 | const production = { 2 | proxyUrl: `http://${process.env.PROXY_DOMAIN}:${process.env.PROXY_URL}`, 3 | serverUrl: `http://${process.env.SERVER_DOMAIN}:${process.env.SERVER_PORT}` 4 | } 5 | 6 | const development = { 7 | proxyUrl: `http://${process.env.PROXY_DOMAIN}:${process.env.PROXY_URL}`, 8 | serverUrl: `http://${process.env.SERVER_DOMAIN}:${process.env.SERVER_PORT}` 9 | } 10 | 11 | export const config = (() => { 12 | if (process.env.NODE_ENV === "production") { 13 | return production; 14 | } 15 | else { 16 | return development; 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /server/controllers/AuthController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import AuthModel from '../models/AuthModel'; 3 | 4 | export default class AuthController { 5 | public static connect(req: Request, res: Response, next: NextFunction) { 6 | AuthModel.connect(req, res, next); 7 | } 8 | } -------------------------------------------------------------------------------- /server/controllers/ClusterController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import ClusterModel from '../models/ClusterModel'; 3 | 4 | class ClusterController { 5 | 6 | public static getCluster(req: Request, res: Response, next: NextFunction) { 7 | try { 8 | ClusterModel.getCluster(req, res, next); 9 | } 10 | catch (err) { 11 | return next({ 12 | log: `Error in ClusterController.getCluster: ${err}`, 13 | status: 500, 14 | message: { 15 | err: 'Error occured while retrieving networked cluster in ClusterController', 16 | }, 17 | }); 18 | } 19 | } 20 | } 21 | 22 | export default ClusterController; -------------------------------------------------------------------------------- /server/controllers/CookieController.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Konstellation/aa5c30c4f56dac78d857c438d950ab393bd37116/server/controllers/CookieController.ts -------------------------------------------------------------------------------- /server/controllers/ElemController.ts: -------------------------------------------------------------------------------- 1 | // // import { rootCertificates } from "tls"; 2 | // // import { ElemController, Elements, k8s } from "../../types"; 3 | // const k8s = require('@kubernetes/client-node'); 4 | // const kc = new k8s.KubeConfig(); 5 | // kc.loadFromDefault(); 6 | // const k8sApi = kc.makeApiClient(k8s.CoreV1Api); 7 | 8 | 9 | 10 | // const elemController = { 11 | // getElements: async (req, res, next) => { 12 | // try { 13 | // const elements = [] 14 | 15 | // elements.push({data: {id:'root', label: 'root', type: 'root'}}) 16 | 17 | // const namespaceData = await k8sApi.listNamespace(); 18 | // let namespaces = []; 19 | // namespaceData.body.items.forEach((space) => { 20 | // const name = space.metadata.name; 21 | // if (name !== 'kube-system') namespaces.push(name); 22 | // elements.push({ 23 | // data: { 24 | // id: name, 25 | // label: name, 26 | // type: 'namespace', 27 | // }}) 28 | // elements.push({ 29 | // data: { 30 | // source: 'root', 31 | // target: name 32 | // }, 33 | // }) 34 | // }) 35 | // console.log(elements) 36 | // res.locals.elements = elements 37 | // return next(); 38 | 39 | // } 40 | // catch (err) { 41 | // return next({ 42 | // log: `Error in ElemController.getElements: ${err}`, 43 | // status: 500, 44 | // message: { 45 | // err: 'Error occured while retrieving network elements in ElemController', 46 | // }, 47 | // }); 48 | // } 49 | // } 50 | // } 51 | 52 | // elemController.getElements() 53 | 54 | 55 | // // const nodeData = k8sApi.listNode('default').then((res) => { 56 | // // // console.log(res.body); 57 | // // // console.log(res.body.items[0].metadata) 58 | // // }); 59 | 60 | // // const podData = k8sApi.listNamespacedPod('default').then((res) => { 61 | // // // console.log(res.body); 62 | // // //metadata contains name and namespace with which it's in 63 | // // // console.log(res.body.items[0].metadata) 64 | // // //spec contains which node it is in: nodeName 65 | // // // console.log(res.body.items[0].spec) 66 | // // // console.log(res.body.items[0].status) 67 | // // }); 68 | 69 | // // const namespaceData = k8sApi.listNamespace().then((res) => { 70 | // // // console.log(res.body); 71 | // // console.log(res.body.items[3]) 72 | // // }); 73 | -------------------------------------------------------------------------------- /server/controllers/TraceController.ts: -------------------------------------------------------------------------------- 1 | import { resolveNaptr } from "dns"; 2 | import { appendFile} from 'fs'; 3 | import { Request, Response, NextFunction } from 'express'; 4 | import { TraceModel } from "../models/TraceModel"; 5 | import Utils from "../utils/Utils"; 6 | 7 | export class TraceController { 8 | 9 | public static getAggregateData(req: Request, res: Response, next: NextFunction) { 10 | try{ 11 | console.log("In Tracecontroller"); 12 | TraceModel.getTraceLogsFromJaeger(req, res, next); 13 | } 14 | catch(err) { 15 | return next({ 16 | log: `Error in TraceController.getCluster: ${err}`, 17 | status: 500, 18 | message: { 19 | err: 'Error occured while retrieving Aggregate Trace Data in TraceController', 20 | }, 21 | }); 22 | } 23 | } 24 | public static getSearchBarTraceView(req: Request, res: Response, next: NextFunction) { 25 | try{ 26 | console.log("In Tracecontroller"); 27 | TraceModel.getSearchBarTraceView(req, res, next); 28 | } 29 | catch(err) { 30 | return next({ 31 | log: `Error in TraceController.getCluster: ${err}`, 32 | status: 500, 33 | message: { 34 | err: 'Error occured while retrieving search bar trace data in TraceController', 35 | }, 36 | }); 37 | } 38 | } 39 | 40 | public static getTraceViewServices(req: Request, res: Response, next: NextFunction) { 41 | try{ 42 | console.log("In Tracecontroller"); 43 | TraceModel.getTraceViewServices(req, res, next); 44 | } 45 | catch(err) { 46 | return next({ 47 | log: `Error in TraceController.getCluster: ${err}`, 48 | status: 500, 49 | message: { 50 | err: 'Error occured while retrieving trace view services data in TraceController', 51 | }, 52 | }); 53 | } 54 | } 55 | 56 | 57 | public static getTraceViewData(req: Request, res: Response, next: NextFunction) { 58 | try{ 59 | console.log("In Tracecontroller"); 60 | TraceModel.getIndividualTraceView(req, res, next); 61 | } 62 | catch(err) { 63 | return next({ 64 | log: `Error in TraceController.getCluster: ${err}`, 65 | status: 500, 66 | message: { 67 | err: 'Error occured while retrieving Trace View Data in TraceController', 68 | }, 69 | }); 70 | } 71 | } 72 | public static getPodDetails(req: Request, res: Response, next: NextFunction) { 73 | try{ 74 | console.log("In Tracecontroller GetPodDetails"); 75 | TraceModel.getIndividualPodData(req, res, next); 76 | } 77 | catch(err) { 78 | return next({ 79 | log: `Error in TraceController.getCluster: ${err}`, 80 | status: 500, 81 | message: { 82 | err: 'Error occured while retrieving Trace Pod Data in TraceController', 83 | }, 84 | }); 85 | } 86 | } 87 | public static getSpanDetails(req: Request, res: Response, next: NextFunction) { 88 | 89 | console.log("In Tracecontroller getIndivSpanDetails"); 90 | TraceModel.getIndivSpanDetails(req, res, next); 91 | } 92 | } -------------------------------------------------------------------------------- /server/data/fakeTraceData.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default [ 4 | { 5 | data: { 6 | id: 'CLIENT', 7 | label: 'CLIENT', 8 | type: 'client', 9 | } 10 | }, 11 | { 12 | data: { 13 | id: 'recipes-landing-2048-598f6', 14 | label: 'recipes-landing-2048-598f6', 15 | type: 'trace', 16 | }, 17 | classes: 'label' 18 | }, 19 | { 20 | data: { 21 | id: 'recipes-server-m65321-321fs', 22 | label: 'recipes-server-m65321-321fs', 23 | type: 'trace', 24 | }, 25 | classes: 'label' 26 | }, 27 | { 28 | data: { 29 | id: 'database-321f-dzd2dmf', 30 | label: 'database-321f-dzd2dmf', 31 | type: 'trace', 32 | }, 33 | classes: 'label' 34 | }, 35 | { 36 | data: { 37 | id: 'dj2i1joiod-=ui9d201jido', 38 | source: 'CLIENT', 39 | target: 'recipes-landing-2048-598f6', 40 | type: 'arrow', 41 | label: '13ms' 42 | }, 43 | classes: 'background' 44 | }, 45 | { 46 | data: { 47 | id: 'jd21jkdlsjkal-dj2iodjskla', 48 | source: 'recipes-landing-2048-598f6', 49 | target: 'recipes-server-m65321-321fs', 50 | type: 'arrow', 51 | label: '9ms' 52 | }, 53 | classes: 'background' 54 | }, 55 | { 56 | data: { 57 | id: 'djkslajd2ijdklnvhjkcsx890', 58 | source: 'recipes-server-m65321-321fs', 59 | target: 'database-321f-dzd2dmf', 60 | type: 'arrow', 61 | label: '37ms' 62 | }, 63 | classes: 'background' 64 | }, 65 | { 66 | data: { 67 | id: 'djkslajd2ijdklnvhjkcsx890-fjiew0ojiod', 68 | source: 'database-321f-dzd2dmf', 69 | target: 'recipes-server-m65321-321fs', 70 | type: 'arrow', 71 | label: '6ms' 72 | }, 73 | classes: 'background' 74 | }, 75 | { 76 | data: { 77 | id: 'djkslajd2ijdklnvhjkcsx890-hur291hfdj2321', 78 | source: 'recipes-server-m65321-321fs', 79 | target: 'recipes-landing-2048-598f6', 80 | type: 'arrow', 81 | label: '14ms' 82 | }, 83 | classes: 'background' 84 | }, 85 | { 86 | data: { 87 | id: 'djkslajd2ijdklnvhjkcsx890-jfi09882bncxzki', 88 | source: 'recipes-server-m65321-321fs', 89 | target: 'CLIENT', 90 | type: 'arrow', 91 | label: '7ms' 92 | }, 93 | classes: 'background' 94 | } 95 | ] -------------------------------------------------------------------------------- /server/data/fakeTraceData2.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default [ 4 | { 5 | data: { 6 | id: 'CLIENT', 7 | label: 'CLIENT', 8 | type: 'client', 9 | } 10 | }, 11 | { 12 | data: { 13 | id: 'hot-sauce-shop-landing', 14 | label: 'hot-sauce-shop-landing', 15 | type: 'trace', 16 | }, 17 | classes: 'label' 18 | }, 19 | { 20 | data: { 21 | id: 'hot-sauce-browse', 22 | label: 'hot-sauce-browse', 23 | type: 'trace', 24 | }, 25 | classes: 'label' 26 | }, 27 | { 28 | data: { 29 | id: 'hot-sauce-challenge', 30 | label: 'hot-sauce-challenge', 31 | type: 'trace', 32 | }, 33 | classes: 'label' 34 | }, 35 | { 36 | data: { 37 | id: 'hot-sauce-cart', 38 | label: 'hot-sauce-cart', 39 | type: 'trace', 40 | }, 41 | classes: 'label' 42 | }, 43 | { 44 | data: { 45 | id: 'hot-sauce-favorites-db', 46 | label: 'hot-sauce-favorites-db', 47 | type: 'trace', 48 | }, 49 | classes: 'label' 50 | }, 51 | { 52 | data: { 53 | id: 'hot-sauce-hall-of-fame', 54 | label: 'hot-sauce-hall-of-fame', 55 | type: 'trace', 56 | }, 57 | classes: 'label' 58 | }, 59 | { 60 | data: { 61 | id: 'hot-sauce-dating', 62 | label: 'hot-sauce-dating', 63 | type: 'trace', 64 | }, 65 | classes: 'label' 66 | }, 67 | { 68 | data: { 69 | id: 'hot-sauce-addiction-helpline', 70 | label: 'hot-sauce-addiction-helpline', 71 | type: 'trace', 72 | }, 73 | classes: 'label' 74 | }, 75 | { 76 | data: { 77 | id: 'dj2i1joiod-=ui9d201jido', 78 | source: 'CLIENT', 79 | target: 'hot-sauce-shop-landing', 80 | type: 'arrow', 81 | label: '13ms' 82 | }, 83 | classes: 'background' 84 | }, 85 | { 86 | data: { 87 | id: 'jd21jkdlsjkal-dj2iodjskla', 88 | source: 'hot-sauce-shop-landing', 89 | target: 'hot-sauce-browse', 90 | type: 'arrow', 91 | label: '9ms' 92 | }, 93 | classes: 'background' 94 | }, 95 | { 96 | data: { 97 | id: 'djkslajd2ijdklnvhjkcsx890', 98 | source: 'hot-sauce-browse', 99 | target: 'hot-sauce-challenge', 100 | type: 'arrow', 101 | label: '37ms' 102 | }, 103 | classes: 'background' 104 | }, 105 | { 106 | data: { 107 | id: 'djkslajd2ijdklnvhjkcsx890-321dsa', 108 | source: 'hot-sauce-browse', 109 | target: 'hot-sauce-favorites-db', 110 | type: 'arrow', 111 | label: '7ms' 112 | }, 113 | classes: 'background' 114 | }, 115 | { 116 | data: { 117 | id: 'djkslajd2ijdklnvhjkcsx890', 118 | source: 'hot-sauce-favorites-db', 119 | target: 'hot-sauce-browse', 120 | type: 'arrow', 121 | label: '5ms' 122 | }, 123 | classes: 'background' 124 | }, 125 | { 126 | data: { 127 | id: 'djkslajd2ijdklnvhjkcsx890-fjiew0ojiod', 128 | source: 'hot-sauce-challenge', 129 | target: 'hot-sauce-hall-of-fame', 130 | type: 'arrow', 131 | label: '6ms' 132 | }, 133 | classes: 'background' 134 | }, 135 | { 136 | data: { 137 | id: 'djkslajd2ijdklnvhjkcsx890-fjiew0ojiod-fdsjk', 138 | source: 'hot-sauce-hall-of-fame', 139 | target: 'hot-sauce-dating', 140 | type: 'arrow', 141 | label: '6ms' 142 | }, 143 | classes: 'background' 144 | }, 145 | { 146 | data: { 147 | id: 'djkslajd2ijdklnvhjkcsx890-hur29z2z2321', 148 | source: 'hot-sauce-dating', 149 | target: 'hot-sauce-addiction-helpline', 150 | type: 'arrow', 151 | label: '14ms' 152 | }, 153 | classes: 'background' 154 | }, 155 | { 156 | data: { 157 | id: 'djkslajd2ijdklnvhjkcsx890-hur29132132121', 158 | source: 'hot-sauce-challenge', 159 | target: 'hot-sauce-dating', 160 | type: 'arrow', 161 | label: '14ms' 162 | }, 163 | classes: 'background' 164 | }, 165 | { 166 | data: { 167 | id: 'djkslajd2ijdklnvhjkcsx890-hur291hdsadsa', 168 | source: 'hot-sauce-browse', 169 | target: 'hot-sauce-cart', 170 | type: 'arrow', 171 | label: '14ms' 172 | }, 173 | classes: 'background' 174 | }, 175 | { 176 | data: { 177 | id: 'djkslajd2ijdklnvhjkcsx890-d21dsxaxsa', 178 | source: 'hot-sauce-browse', 179 | target: 'hot-sauce-favorites-db', 180 | type: 'arrow', 181 | label: '14ms' 182 | }, 183 | classes: 'background' 184 | }, 185 | { 186 | data: { 187 | id: 'djkslajd2dxaxzkcsx890-d21dsxaxsa', 188 | source: 'hot-sauce-dating', 189 | target: 'hot-sauce-favorites-db', 190 | type: 'arrow', 191 | label: '14ms' 192 | }, 193 | classes: 'background' 194 | }, 195 | { 196 | data: { 197 | id: 'djkd2dxz2dxaxzkcsx890-d21dsxaxsa', 198 | source: 'hot-sauce-favorites-db', 199 | target: 'hot-sauce-dating', 200 | type: 'arrow', 201 | label: '14ms' 202 | }, 203 | classes: 'background' 204 | }, 205 | { 206 | data: { 207 | id: 'djkslajd2ijdklnvhjkcsx890-jfi09882bncxzki', 208 | source: 'hot-sauce-addiction-helpline', 209 | target: 'CLIENT', 210 | type: 'arrow', 211 | label: '7ms' 212 | }, 213 | classes: 'background' 214 | }, 215 | { 216 | data: { 217 | id: 'djkslajd2ijdk2321fi09882bncxzki', 218 | source: 'hot-sauce-addiction-helpline', 219 | target: 'CLIENT', 220 | type: 'arrow', 221 | label: '748ms' 222 | }, 223 | classes: 'background' 224 | }, 225 | { 226 | data: { 227 | id: 'djkslajzxczx255x890-jfi09882bncxzki', 228 | source: 'hot-sauce-addiction-helpline', 229 | target: 'CLIENT', 230 | type: 'arrow', 231 | label: '189ms' 232 | }, 233 | classes: 'background' 234 | }, 235 | { 236 | data: { 237 | id: 'djkslajd2ijdklnvhjkcsxfdsfdscxzki', 238 | source: 'hot-sauce-addiction-helpline', 239 | target: 'CLIENT', 240 | type: 'arrow', 241 | label: '42ms' 242 | }, 243 | classes: 'background' 244 | } 245 | ] -------------------------------------------------------------------------------- /server/interfaces/IExpress.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | 3 | export interface IRequest extends Request { } 4 | 5 | export interface IResponse extends Response { 6 | locals: any; 7 | traceData: TraceData; 8 | } 9 | 10 | export interface INextFunction extends NextFunction {} -------------------------------------------------------------------------------- /server/models/AuthModel.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | 3 | const execSync = require('child_process').execSync; 4 | const k8s = require('@kubernetes/client-node'); 5 | 6 | const kc = new k8s.KubeConfig(); 7 | kc.loadFromDefault(); 8 | const k8sApi = kc.makeApiClient(k8s.CoreV1Api); 9 | 10 | console.log('in AuthModel'); 11 | 12 | export default class AuthModel { 13 | public static async connect(req: Request, res: Response, next: NextFunction) { 14 | 15 | console.log('trying to connect...'); 16 | try { 17 | 18 | //const output = execSync('kubectl get svc', {encoding: 'utf-8'}); 19 | const namespaceData = await k8sApi.listNamespace(); 20 | //const output = execSync('kubectl get node', {encoding: 'utf-8'}); 21 | 22 | //console.log('Output was:\n', namespaceData); 23 | next(); 24 | } 25 | catch(err) { 26 | console.log('Error in Auth model!'); 27 | // If there is an error, then the user doesn't have access to the EKS 28 | return next(err); 29 | } 30 | // return next(); 31 | //const output = execSync('ls', {encoding: 'utf-8'}); 32 | 33 | // const output = execSync('kubectl get node', {encoding: 'utf-8'}); 34 | // console.log('Output was:\n', output); 35 | } 36 | } -------------------------------------------------------------------------------- /server/models/ClusterModel.ts: -------------------------------------------------------------------------------- 1 | 2 | import {v4 as uuidv4} from 'uuid'; 3 | import { Request, Response, NextFunction } from "express"; 4 | const k8s = require('@kubernetes/client-node'); 5 | 6 | const kc = new k8s.KubeConfig(); 7 | kc.loadFromDefault(); 8 | const k8sApi = kc.makeApiClient(k8s.CoreV1Api); 9 | 10 | class ClusterModel { 11 | 12 | public static async getCluster (req: Request, res: Response, next: NextFunction) { 13 | const elements = [] 14 | const clusterName = 'Control Plane'; //await getClusterName() 15 | 16 | elements.push({data: {id: clusterName, label: clusterName, type: 'root'}}) 17 | 18 | const namespaceData = await k8sApi.listNamespace(); 19 | let namespaces: string[] = []; 20 | 21 | namespaceData.body.items.forEach((space: any) => { 22 | const name = space.metadata.name; 23 | if (name !== 'kube-system') namespaces.push(name); 24 | elements.push({ 25 | data: { 26 | id: name, 27 | label: name, 28 | type: 'namespace', 29 | }}) 30 | elements.push({ 31 | data: { 32 | source: clusterName, 33 | target: name, 34 | id: uuidv4() 35 | }, 36 | }) 37 | }) 38 | for (const namespace of namespaces) { 39 | const podData = await k8sApi.listNamespacedPod(namespace); 40 | podData.body.items.forEach((pod: any) => { 41 | const name = pod.metadata.name 42 | elements.push({ 43 | data: { 44 | id: name, 45 | label: name, 46 | type: 'pod', 47 | } 48 | }) 49 | elements.push({ 50 | data: { 51 | source: namespace, 52 | target: name, 53 | id: uuidv4() 54 | } 55 | }) 56 | }) 57 | } 58 | 59 | res.locals.elements = elements 60 | //console.log(elements) 61 | return next(); 62 | } 63 | } 64 | 65 | export default ClusterModel; -------------------------------------------------------------------------------- /server/models/test.tsx: -------------------------------------------------------------------------------- 1 | // const k8s = require('@kubernetes/client-node'); 2 | 3 | // const kc = new k8s.KubeConfig(); 4 | // kc.loadFromDefault(); 5 | 6 | // const k8sApi = kc.makeApiClient(k8s.CoreV1Api); 7 | 8 | // k8sApi.createNamespace().then((res) => { 9 | // console.log(res.body.items[0].status); 10 | // }); 11 | 12 | // //run this page to see what is available from the kubernetes api for a single pod with node client/server/models/test.tsx -------------------------------------------------------------------------------- /server/proxy.ts: -------------------------------------------------------------------------------- 1 | import { express } from '../types'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import cors from 'cors'; 4 | const app = express(); 5 | import dotenv from 'dotenv' 6 | import { config } from './constants/config' 7 | import path from 'path' 8 | 9 | dotenv.config({path: path.resolve(__dirname, '../.env')}) 10 | 11 | app.use(cors()); 12 | app.use(express.json()); 13 | app.use(express.urlencoded({ extended: true })); 14 | 15 | app.use((req: Request, res: Response, next: NextFunction) => { 16 | console.log("Proxy Server Received a Request"); 17 | 18 | res.set('Access-Control-Allow-Origin', ['*']); 19 | 20 | fetch(config.serverUrl + req.path) 21 | .then(response => response.json() ) 22 | .then(data => res.send(data)); 23 | }); 24 | 25 | app.listen(3010, () => console.log('Proxy Server is listening on port 3010')); -------------------------------------------------------------------------------- /server/routers/AuthRouter.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import express from 'express'; 3 | 4 | import AuthController from "../controllers/AuthController"; 5 | const router = express.Router(); 6 | 7 | router.get('/', AuthController.connect, (req: Request, res : Response, next : NextFunction) => { 8 | console.log('returning from AuthRouter...'); 9 | res.sendStatus(200); 10 | }); 11 | 12 | export default router; -------------------------------------------------------------------------------- /server/routers/ClusterRouter.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import express from 'express'; 3 | 4 | import ClusterController from '../controllers/ClusterController'; 5 | const router = express.Router(); 6 | 7 | router.get('/', ClusterController.getCluster, (req: Request, res : Response, next : NextFunction) => { 8 | console.log("Response Sent w/ Cluster Data!") 9 | res.status(200).json(res.locals.elements); 10 | }) 11 | 12 | export default router; -------------------------------------------------------------------------------- /server/routers/TraceRouter.ts: -------------------------------------------------------------------------------- 1 | import { TraceController } from "../controllers/TraceController"; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import express from 'express'; 4 | 5 | const router = express.Router(); 6 | 7 | router.get('/getAll/:service/:lookback', 8 | TraceController.getAggregateData, 9 | (req: Request, res: Response, next: NextFunction) => { 10 | console.log('router complete getAll'); 11 | res.status(200).json(res.locals.tracesArray); 12 | } 13 | ); 14 | 15 | router.get('/getTraceView/:traceId', 16 | TraceController.getTraceViewData, 17 | (req: Request, res: Response, next: NextFunction) => { 18 | res.status(200).json(res.locals.traceViewArray) 19 | } 20 | ); 21 | 22 | router.get('/getSearchBarTraceView/:traceId', 23 | TraceController.getTraceViewData, TraceController.getSearchBarTraceView, 24 | (req: Request, res: Response, next: NextFunction) => { 25 | res.status(200).json(res.locals.searchBarTraceView) 26 | } 27 | ); 28 | 29 | router.get('/getTraceViewServices', 30 | TraceController.getTraceViewServices, 31 | (req: Request, res: Response, next: NextFunction) => { 32 | console.log('router complete getAll'); 33 | res.status(200).json(res.locals.traceViewServices); 34 | } 35 | ) 36 | // Will need to pass in req.body the specific pod name, req.body.traceID 37 | router.get('/getSpansInProcess/:traceId/:processTarget', 38 | // router.get('/getSpansInProcess', 39 | TraceController.getPodDetails, 40 | (req: Request, res: Response, next: NextFunction) => { 41 | res.status(200).json(res.locals.processSpecificSpans) 42 | } 43 | ) 44 | 45 | router.get('/getIndivSpanDetails/:spanId', 46 | TraceController.getSpanDetails, 47 | (req: Request, res: Response, next: NextFunction) => { 48 | res.status(200).json(res.locals.spanDetails) 49 | } 50 | ) 51 | 52 | // router.post('/post', 53 | // TraceController.saveData, 54 | // (req: Request, res: Response, next: NextFunction) => {res.status(200).json("Trace Data Added")} 55 | // ); 56 | 57 | export default router; -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | import { ErrObject, express } from '../types'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import cors from 'cors'; 4 | import clusterRouter from './routers/ClusterRouter'; 5 | import traceRouter from './routers/TraceRouter'; 6 | import authRouter from './routers/AuthRouter'; 7 | import dotenv from 'dotenv' 8 | import path from 'path' 9 | dotenv.config({path: path.resolve(__dirname, '../.env')}) 10 | 11 | const app = express(); 12 | app.use(cors()); 13 | app.use(express.json()); 14 | app.use(express.urlencoded({ extended: true })); 15 | 16 | app.use('/api/cluster', clusterRouter); 17 | app.use('/api/traces', traceRouter); 18 | // Attempt to connect user to K8 control plane 19 | app.use('/authenticate', authRouter); 20 | 21 | // 500: Server Error 22 | app.use((err: ErrObject, req: Request, res: Response, next : NextFunction) => { 23 | const defaultErr = { 24 | log: 'Express error handler caught unknown middleware error', 25 | status: 500, 26 | message: { err: 'An error occurred' }, 27 | }; 28 | const errorObj = Object.assign({}, defaultErr, err); 29 | console.log(errorObj.log); 30 | console.log('res:', res); 31 | return res.status(errorObj.status).json(errorObj.message); 32 | }); 33 | 34 | // 404: Not found 35 | app.use((req: Request, res: Response) => { 36 | console.log("Server issued a 404 response") 37 | res.status(404).send('404 Page Not Found') 38 | }); 39 | 40 | app.listen(3000, () => console.log('SERVER is listening on port 3000')); 41 | 42 | export default app; -------------------------------------------------------------------------------- /server/types/EdgeData.ts: -------------------------------------------------------------------------------- 1 | type EdgeData = { 2 | data: { 3 | source: string, 4 | target: string, 5 | type: 'arrow', 6 | label: any, 7 | }, 8 | classes: 'background' 9 | } 10 | -------------------------------------------------------------------------------- /server/types/PodData.ts: -------------------------------------------------------------------------------- 1 | type PodData = { 2 | data: { 3 | id: string, 4 | label: string, 5 | type: string, 6 | } 7 | classes?: any 8 | } -------------------------------------------------------------------------------- /server/types/SpanData.ts: -------------------------------------------------------------------------------- 1 | type SpanData = { 2 | data: { 3 | operationName: string, 4 | references: string, 5 | startTime: string, 6 | duration: string, 7 | spanTags: [], 8 | httpHost: string, 9 | httpMethod: string, 10 | httpTarget: string, 11 | httpUrl: string, 12 | rpcMethod: string, 13 | rpcGrpcStatusCode: string 14 | } 15 | } -------------------------------------------------------------------------------- /server/types/TraceData.ts: -------------------------------------------------------------------------------- 1 | type TraceData = { 2 | data: { 3 | id: string, 4 | label: string, 5 | type: string 6 | } 7 | } 8 | 9 | 10 | 11 | // pod name; -------------------------------------------------------------------------------- /server/types/TraceViewData.ts: -------------------------------------------------------------------------------- 1 | export type TraceViewPodsData = { 2 | data: { 3 | id: string, 4 | type: string, 5 | traceID?: string, 6 | traceStart? : string, 7 | traceDuration? : string, 8 | serviceCount? : number, 9 | spanCount? : number, 10 | label: any, 11 | } 12 | classes: 'label' 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /server/types/Types.ts: -------------------------------------------------------------------------------- 1 | export type SpanCache = { 2 | processNum:string, 3 | spanIds: Array 4 | spanData: Array 5 | }; 6 | 7 | export type EdgeData = { 8 | data: { 9 | source: string, 10 | target: string, 11 | type: 'arrow', 12 | label: any, 13 | }, 14 | classes: 'background' 15 | }; 16 | 17 | export type SpanData = { 18 | data: { 19 | traceID: string, 20 | spanID: string, 21 | operationName: string, 22 | references: string, 23 | startTime: string, 24 | duration: string, 25 | spanTags: [], 26 | httpHost: string, 27 | httpMethod: string, 28 | httpTarget: string, 29 | httpUrl: string, 30 | rpcMethod: string, 31 | rpcGrpcStatusCode: string 32 | } 33 | }; 34 | 35 | export type TraceLogData = { 36 | data: { 37 | id: string, 38 | label: string, 39 | type: string, 40 | duration: number, 41 | response: number, 42 | method: string, 43 | url: string, 44 | } 45 | }; 46 | 47 | export type TraceViewPodsData = { 48 | data: { 49 | id: string, 50 | type: string, 51 | traceID?: string, 52 | traceStart? : string, 53 | traceDuration? : string, 54 | serviceCount? : number, 55 | spanCount? : number, 56 | label: any, 57 | } 58 | classes: 'label' 59 | } 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /server/types/traceLogData.ts: -------------------------------------------------------------------------------- 1 | type TraceLogData = { 2 | data: { 3 | id: string, 4 | label: string, 5 | type: string, 6 | duration: number, 7 | response: number, 8 | method: string, 9 | url: string, 10 | } 11 | } -------------------------------------------------------------------------------- /server/utils/Utils.ts: -------------------------------------------------------------------------------- 1 | 2 | class Utils { 3 | 4 | static getTimestamp() { 5 | let ts = Date.now(); 6 | 7 | let date_ob = new Date(ts); 8 | let hours = date_ob.getHours(); 9 | let minutes = date_ob.getMinutes(); 10 | let seconds = date_ob.getSeconds(); 11 | let date = date_ob.getDate(); 12 | let month = date_ob.getMonth() + 1; 13 | let year = date_ob.getFullYear(); 14 | 15 | // prints date & time in YYYY-MM-DD format 16 | return (year + "-" + month + "-" + date + " at " + hours + ":" + minutes + ":" + seconds); 17 | } 18 | 19 | } 20 | 21 | export default Utils; 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | "jsx": "react" /* Specify what JSX code is generated. */, 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | "sourceMap": true /* Create source map files for emitted JavaScript files. */, 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | "removeComments": true /* Disable emitting comments. */, 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 77 | 78 | /* Type Checking */ 79 | "strict": true /* Enable all strict type-checking options. */, 80 | "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | // "include": ["server/**/*", "client/**/*"], 104 | "exclude": ["node_modules"], 105 | "typeRoots": [ 106 | "./typings", 107 | "./node_modules/@types/" 108 | ] 109 | } 110 | 111 | -------------------------------------------------------------------------------- /types.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from 'express-serve-static-core'; 2 | export const client = require('prom-client'); 3 | export const express = require('express'); 4 | export const axios = require('axios'); 5 | export const k8s = require('@kubernetes/client-node'); 6 | export const start = new Date(Date.now() - 1440 * 60000).toISOString(); 7 | export const end = new Date(Date.now()).toISOString(); 8 | // export type NodeController = { 9 | // getInstantMetrics: RequestHandler; 10 | // getNetworkTransmitBytes: RequestHandler; 11 | // getNetworkReceiveBytes: RequestHandler; 12 | // }; 13 | export type ClusterController = { 14 | getNamespaces: RequestHandler; 15 | getPodsByNamespace: RequestHandler; 16 | getNodes: RequestHandler; 17 | }; 18 | // export type DashboardController = { 19 | // getNumberOf: RequestHandler; 20 | // getGeneralClusterInfo: RequestHandler; 21 | // getTotalMem: RequestHandler; 22 | // getTotalCpu: RequestHandler; 23 | // getTotalTransmit: RequestHandler; 24 | // getTotalReceive: RequestHandler; 25 | // }; 26 | // export type CustomQuery = { 27 | // query: string; 28 | // name: string; 29 | // yAxisType: string; 30 | // active: boolean; 31 | // }; 32 | // export type CustomController = { 33 | // customClusterQueries: CustomQuery[]; 34 | // customNodeQueries: CustomQuery[]; 35 | // customPodQueries: CustomQuery[]; 36 | // testCustomRoute: RequestHandler; 37 | // addCustomRoute: RequestHandler; 38 | // getCustomRoute: RequestHandler; 39 | // listCustomRoutes: RequestHandler; 40 | // deleteCustomRoute: RequestHandler; 41 | // changeRouteActive: RequestHandler; 42 | // }; 43 | // export type NumOfData = { 44 | // nodes: number; 45 | // pods: number; 46 | // namespaces: number; 47 | // deployments: number; 48 | // ingresses: number; 49 | // services: number; 50 | // }; 51 | // export type GeneralData = { 52 | // totalUserCpu: number; 53 | // totalSystemCpu: number; 54 | // totalUserSystemCpu: number; 55 | // residentMemBytes: number; 56 | // eventLoopLag: number; 57 | // totalActiveResources: number; 58 | // totalActiveHandles: number; 59 | // totalActiveRequests: number; 60 | // heapSizeBytes: number; 61 | // heapSizeUsed: number; 62 | // }; 63 | // export type PodController = { 64 | // getCpuUsage: RequestHandler; 65 | // getMemUsage: RequestHandler; 66 | // getInstantMetrics: RequestHandler; 67 | // }; 68 | export type ElemController = { 69 | getElements: RequestHandler; 70 | }; 71 | export type ErrObject = { 72 | log: string; 73 | status: number; 74 | message: { err: string }; 75 | }; 76 | export type Elements = { 77 | data: { 78 | id?: string; 79 | label?: string; 80 | source?: string; 81 | target?: string; 82 | type?: string; 83 | }; 84 | }; 85 | // export type node = string; 86 | 87 | -------------------------------------------------------------------------------- /typings/custom/import-png.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png" { 2 | const string: any; 3 | export default string; 4 | } -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'cytoscape-cose-bilkent'; -------------------------------------------------------------------------------- /webpack.config.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const CopyPlugin = require('copy-webpack-plugin'); 4 | const webpack = require('webpack'); 5 | const Dotenv = require('dotenv').config({path: path.resolve(__dirname, './client/.env')}); 6 | 7 | module.exports = (env: any, argv:any) => { 8 | 9 | const mode = argv.mode || 'development'; 10 | 11 | const dotEnv = new webpack.DefinePlugin({ 12 | "process.env": { 13 | 'DOMAIN': JSON.stringify(process.env.DOMAIN), 14 | 'PORT': JSON.stringify(process.env.PORT), 15 | 16 | // PRODUCTION 17 | // Testing Purposes Only - never save production environment variables in local files. 18 | // Tests will require updating config.ts as well 19 | 'DOMAIN_PD': JSON.stringify(process.env.DOMAIN_PD), 20 | 'PORT_PD': JSON.stringify(process.env.PORT_PD), 21 | } 22 | }); 23 | 24 | return { 25 | performance: { 26 | hints: false, 27 | maxEntrypointSize: 512000, 28 | maxAssetSize: 512000 29 | }, 30 | mode, 31 | stats: { 32 | errorDetails: true 33 | }, 34 | entry: { 35 | bundle: './client/src/index.tsx' 36 | }, 37 | output: { 38 | path: path.resolve(__dirname, 'dist'), 39 | filename: '[name].[contenthash].js', 40 | clean: true, // this will run dev from RAM rather than storing to HDD, and for npm run build it deletes old build files 41 | assetModuleFilename: '[name][ext]', // this makes sure that the name remains the same during compilation 42 | }, 43 | module: { 44 | 45 | rules: [ 46 | { 47 | test: /\.(png|jpe?g|gif|jp2|webp)$/, 48 | loader: 'file-loader', 49 | options: { 50 | name: 'images/[name].[ext]' 51 | }, 52 | }, 53 | { 54 | test: /\.(js|jsx)$/, 55 | exclude: /node_modules/, 56 | use: ['babel-loader'], 57 | }, 58 | { 59 | test: /\.(ts|tsx)$/, 60 | exclude: /node_modules/, 61 | use: ['ts-loader'], 62 | }, 63 | { 64 | test: /\.s?css$/, 65 | use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'], 66 | }, 67 | ], 68 | }, 69 | resolve: { 70 | extensions: ['.jsx', '.js', '.tsx', '.ts'], 71 | }, 72 | plugins: [ 73 | new HtmlWebpackPlugin({ 74 | title: 'Konstellation', 75 | template: './client/index.html', 76 | filename: './index.html', 77 | // favicon: './client/favicon.ico', 78 | }), 79 | new CopyPlugin({ 80 | patterns: [{ from: './client/style.css' }], 81 | }), 82 | dotEnv 83 | ], 84 | devtool: 'source-map', 85 | devServer: { 86 | static: { 87 | directory: path.join(__dirname, './dist'), 88 | publicPath: '/', 89 | }, 90 | proxy: { 91 | '/api': 'http://localhost:3000', 92 | secure: false, 93 | }, 94 | compress: false, 95 | host: 'localhost', 96 | port: 8080, 97 | hot: true, 98 | historyApiFallback: true, 99 | }, 100 | }; 101 | } 102 | --------------------------------------------------------------------------------