├── .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 | # 
2 | 
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 |
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 |
232 |
233 | 2. To view a list of traces click on the table tabat the bottom of the screen.
234 |
235 |
236 | 3. To view a specific trace, click on the trace ID on the table.
237 |
238 |
239 | 4. To view span details, double-click on a pod in the trace map view.
240 |
241 |
242 | 5. To return to the clusterView click on the clusterView button.
243 |
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 |
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 |
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 (
{element} )
46 | })
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
54 | Namespace:
55 |
56 |
57 | {changeNameSpace(e)}}>
58 | all
59 | {DropDownOptions}
60 |
61 |
62 |
63 |
64 |
65 | Search:
66 |
67 |
73 | {
74 | const input = document.getElementById('searchBarInput') as HTMLInputElement;
75 | submitTrace(input.value)
76 | }}> Submit
77 |
78 |
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 | {
53 | const input = document.getElementById('searchBarInputTrace') as HTMLInputElement;
54 | submitTrace(input.value)}}> Submit
55 | returnToCLusterView()}>Cluster View
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 | Log Out
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 |
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 |
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 |
{if (type === spanViewType.noRender) {loadNewSpanResults(spanViewType.render, data[i])} else {loadNewSpanResults(spanViewType.noRender, data[i].spanIds)}}}>{'>'}
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 ({element} )
36 | })
37 |
38 |
39 | return (
40 |
41 |
42 | {changeService(e)}}>
43 | {DropDownOptions}
44 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------