UI Splitting to common components (#883)
* initial commit * removing files * after pr * move StatusBar to common * last changes from dev * update common-ui * webSocket was added to TrafficViewerApi * useWS * actionButtons added to TrafficV * comment clean * api clean up * api clean up * statusbar position changed * Checkbox changed * AnalyzeButton exported to common * CustomModal added from Ent * oas modal exported to common * removed redundant * oasmodal usage * es6 function * api changed * removed react-scripts Co-authored-by: Leon <>
5
.gitignore
vendored
@ -48,3 +48,8 @@ cypress.env.json
|
||||
# Ignore test data in extensions
|
||||
tap/extensions/*/bin
|
||||
tap/extensions/*/expect
|
||||
|
||||
# UI folders to ignore
|
||||
**/node_modules/**
|
||||
**/dist/**
|
||||
*.editorconfig
|
9
ui-common/.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
6
ui-common/.eslintignore
Normal file
@ -0,0 +1,6 @@
|
||||
build/
|
||||
dist/
|
||||
src/
|
||||
node_modules/
|
||||
.snapshots/
|
||||
*.min.js
|
34
ui-common/.eslintrc
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"standard",
|
||||
"standard-react",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/standard",
|
||||
"prettier/react",
|
||||
"plugin:@typescript-eslint/eslint-recommended"
|
||||
],
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"ecmaFeatures": {
|
||||
"legacyDecorators": true,
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "16"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"space-before-function-paren": 0,
|
||||
"react/prop-types": 0,
|
||||
"react/jsx-handler-names": 0,
|
||||
"react/jsx-fragments": 0,
|
||||
"react/no-unused-prop-types": 0,
|
||||
"import/export": 0
|
||||
}
|
||||
}
|
10
ui-common/.prettierrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none"
|
||||
}
|
4
ui-common/.travis.yml
Normal file
@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 12
|
||||
- 10
|
30
ui-common/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# mizu-common
|
||||
|
||||
> Made with create-react-library
|
||||
|
||||
[](https://www.npmjs.com/package/liraz-test) [](https://standardjs.com)
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install --save @up9/mizu-common
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```tsx
|
||||
import React, { Component } from 'react'
|
||||
|
||||
import MyComponent from 'l@up9/mizu-common'
|
||||
import '@up9/mizu-common/dist/index.css'
|
||||
|
||||
class Example extends Component {
|
||||
render() {
|
||||
return <MyComponent />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT © [](https://github.com/)
|
5
ui-common/example/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
It is linked to the liraz-test package in the parent directory for development purposes.
|
||||
|
||||
You can run `npm install` and then `npm start` to test your package.
|
13
ui-common/example/craco.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
webpack: {
|
||||
configure: (webpackConfig) => {
|
||||
const instanceOfMiniCssExtractPlugin = webpackConfig.plugins.find(
|
||||
(plugin) => plugin.options && plugin.options.ignoreOrder != null,
|
||||
);
|
||||
if(instanceOfMiniCssExtractPlugin)
|
||||
instanceOfMiniCssExtractPlugin.options.ignoreOrder = true;
|
||||
|
||||
return webpackConfig;
|
||||
}
|
||||
}
|
||||
}
|
43491
ui-common/example/package-lock.json
generated
Normal file
48
ui-common/example/package.json
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@up9/mizu-common-example",
|
||||
"homepage": ".",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"comment-start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
|
||||
"build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
|
||||
"test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
|
||||
"eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "file:../node_modules/@testing-library/jest-dom",
|
||||
"@testing-library/react": "file:../node_modules/@testing-library/react",
|
||||
"@testing-library/user-event": "file:../node_modules/@testing-library/user-event",
|
||||
"@types/jest": "file:../node_modules/@types/jest",
|
||||
"@types/node": "file:../node_modules/@types/node",
|
||||
"@types/react": "file:../node_modules/@types/react",
|
||||
"@types/react-dom": "file:../node_modules/@types/react-dom",
|
||||
"@up9/mizu-common": "file:..",
|
||||
"react": "file:../node_modules/react",
|
||||
"react-dom": "file:../node_modules/react-dom"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||
"@craco/craco": "^6.4.3",
|
||||
"eslint": "^7.11.0",
|
||||
"node-sass": "^6.0.0",
|
||||
"recoil": "^0.5.2",
|
||||
"react-scripts": "^4.0.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
BIN
ui-common/example/public/favicon.ico
Normal file
After Width: | Height: | Size: 3.8 KiB |
48
ui-common/example/public/index.html
Normal file
@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>@up9/mizu-common</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
15
ui-common/example/public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "@up9/mizu-common",
|
||||
"name": "@up9/mizu-common",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
9
ui-common/example/src/App.test.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div')
|
||||
ReactDOM.render(<App />, div)
|
||||
ReactDOM.unmountComponentAtNode(div)
|
||||
})
|
25
ui-common/example/src/App.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import TrafficViewer,{useWS, DEFAULT_QUERY} from '@up9/mizu-common';
|
||||
import "@up9/mizu-common/dist/index.css"
|
||||
import {useEffect} from 'react';
|
||||
import Api, {getWebsocketUrl} from "./api";
|
||||
|
||||
const api = Api.getInstance()
|
||||
|
||||
const App = () => {
|
||||
const {message,error,isOpen, openSocket, closeSocket, sendQuery} = useWS(getWebsocketUrl())
|
||||
const trafficViewerApi = {...api, webSocket:{open : openSocket, close: closeSocket, sendQuery: sendQuery}}
|
||||
sendQuery(DEFAULT_QUERY);
|
||||
|
||||
useEffect(() => {
|
||||
return () =>{
|
||||
closeSocket()
|
||||
}
|
||||
},[])
|
||||
|
||||
return <>
|
||||
<TrafficViewer message={message} error={error} isWebSocketOpen={isOpen}
|
||||
trafficViewerApiProp={trafficViewerApi} ></TrafficViewer>
|
||||
</>
|
||||
}
|
||||
|
||||
export default App
|
124
ui-common/example/src/api.js
Normal file
@ -0,0 +1,124 @@
|
||||
import * as axios from "axios";
|
||||
|
||||
export const MizuWebsocketURL = process.env.REACT_APP_OVERRIDE_WS_URL ? process.env.REACT_APP_OVERRIDE_WS_URL :
|
||||
window.location.protocol === 'https:' ? `wss://${window.location.host}/ws` : `ws://${window.location.host}/ws`;
|
||||
|
||||
export const FormValidationErrorType = "formError";
|
||||
|
||||
const CancelToken = axios.CancelToken;
|
||||
|
||||
const apiURL = process.env.REACT_APP_OVERRIDE_API_URL ? process.env.REACT_APP_OVERRIDE_API_URL : `${window.location.origin}/`;
|
||||
|
||||
let token = null
|
||||
let client = null
|
||||
let source = null
|
||||
|
||||
export default class Api {
|
||||
static instance;
|
||||
|
||||
static getInstance() {
|
||||
if (!Api.instance) {
|
||||
Api.instance = new Api();
|
||||
}
|
||||
return Api.instance;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
token = localStorage.getItem("token");
|
||||
|
||||
client = this.getAxiosClient();
|
||||
source = null;
|
||||
}
|
||||
|
||||
tapStatus = async () => {
|
||||
const response = await client.get("/status/tap");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
analyzeStatus = async () => {
|
||||
const response = await client.get("/status/analyze");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getEntry = async (id, query) => {
|
||||
const response = await client.get(`/entries/${id}?query=${query}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
fetchEntries = async (leftOff, direction, query, limit, timeoutMs) => {
|
||||
const response = await client.get(`/entries/?leftOff=${leftOff}&direction=${direction}&query=${query}&limit=${limit}&timeoutMs=${timeoutMs}`).catch(function (thrown) {
|
||||
console.error(thrown.message);
|
||||
return {};
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getRecentTLSLinks = async () => {
|
||||
const response = await client.get("/status/recentTLSLinks");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getOasServices = async () => {
|
||||
const response = await client.get("/oas");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getOasByService = async (selectedService) => {
|
||||
const response = await client.get(`/oas/${selectedService}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
validateQuery = async (query) => {
|
||||
if (source) {
|
||||
source.cancel();
|
||||
}
|
||||
source = CancelToken.source();
|
||||
|
||||
const form = new FormData();
|
||||
form.append('query', query)
|
||||
const response = await client.post(`/query/validate`, form, {
|
||||
cancelToken: source.token
|
||||
}).catch(function (thrown) {
|
||||
if (!axios.isCancel(thrown)) {
|
||||
console.error('Validate error', thrown.message);
|
||||
}
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
persistToken = (tk) => {
|
||||
token = tk;
|
||||
client = this.getAxiosClient();
|
||||
localStorage.setItem('token', token);
|
||||
}
|
||||
|
||||
getAxiosClient = () => {
|
||||
const headers = {
|
||||
Accept: "application/json"
|
||||
}
|
||||
|
||||
if (token) {
|
||||
headers['x-session-token'] = `${token}`; // we use `x-session-token` instead of `Authorization` because the latter is reserved by kubectl proxy, making mizu view not work
|
||||
}
|
||||
return axios.create({
|
||||
baseURL: apiURL,
|
||||
timeout: 31000,
|
||||
headers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getWebsocketUrl(){
|
||||
let websocketUrl = MizuWebsocketURL;
|
||||
if (token) {
|
||||
websocketUrl += `/${token}`;
|
||||
}
|
||||
|
||||
return websocketUrl;
|
||||
}
|
14
ui-common/example/src/index.css
Normal file
@ -0,0 +1,14 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
7
ui-common/example/src/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import './index.css'
|
||||
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from './App'
|
||||
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
1
ui-common/example/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="react-scripts" />
|
5
ui-common/example/src/setupTests.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
41
ui-common/example/tsconfig.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"module": "esnext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"esnext"
|
||||
],
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react-jsx",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": false,
|
||||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es5",
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"build",
|
||||
"exmaple"
|
||||
]
|
||||
}
|
62803
ui-common/package-lock.json
generated
Normal file
94
ui-common/package.json
Normal file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"name": "@up9/mizu-common",
|
||||
"version": "1.0.125",
|
||||
"description": "Made with create-react-library",
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"repository": "/mizu-common",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.modern.js",
|
||||
"source": "src/index.tsx",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "microbundle-crl --no-compress --format modern,cjs",
|
||||
"start": "microbundle-crl watch --no-compress --format modern,cjs",
|
||||
"prepare": "run-s build",
|
||||
"test": "run-s test:unit test:lint test:build",
|
||||
"test:build": "run-s build",
|
||||
"test:lint": "eslint .",
|
||||
"predeploy": "cd example && npm install && npm run build",
|
||||
"deploy": "gh-pages -d example/build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@craco/craco": "^6.4.3",
|
||||
"@material-ui/core": "^4.11.3",
|
||||
"@material-ui/icons": "^4.11.2",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"node-sass": "^6.0.0",
|
||||
"react":"^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"recoil": "^0.5.2",
|
||||
|
||||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^12.20.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.179",
|
||||
"@uiw/react-textarea-code-editor": "^1.4.12",
|
||||
"axios": "^0.25.0",
|
||||
"core-js": "^3.20.2",
|
||||
"highlight.js": "^11.3.1",
|
||||
"json-beautify": "^1.1.1",
|
||||
"jsonpath": "^1.1.1",
|
||||
"marked": "^4.0.10",
|
||||
"material-ui-popup-state": "^2.0.0",
|
||||
"mobx": "^6.3.10",
|
||||
"moment": "^2.29.1",
|
||||
"node-fetch": "^3.1.1",
|
||||
"numeral": "^2.0.6",
|
||||
"protobuf-decoder": "^0.1.0",
|
||||
"react-graph-vis": "^1.0.7",
|
||||
"react-lowlight": "^3.0.0",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"react-scrollable-feed-virtualized": "^1.4.9",
|
||||
"react-syntax-highlighter": "^15.4.3",
|
||||
"react-toastify": "^8.0.3",
|
||||
"redoc": "^2.0.0-rc.59",
|
||||
"styled-components": "^5.3.3",
|
||||
"web-vitals": "^1.1.1",
|
||||
"xml-formatter": "^2.6.0",
|
||||
"@craco/craco": "^6.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"@svgr/rollup": "^6.2.1",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"cross-env": "^7.0.2",
|
||||
"env-cmd": "^10.0.1",
|
||||
"gh-pages": "^2.2.0",
|
||||
"microbundle-crl": "^0.13.10",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.0.4",
|
||||
"rollup-plugin-import-css": "^3.0.2",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"rollup-plugin-sass": "^1.2.10",
|
||||
"rollup-plugin-scss": "^3.0.0",
|
||||
"react":"^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"typescript": "^4.2.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
86
ui-common/src/components/AnalyzeButton/AnalyzeButton.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import {Button} from "@material-ui/core";
|
||||
import React from "react";
|
||||
import logo_up9 from "logo_up9.svg";
|
||||
import {makeStyles} from "@material-ui/core/styles";
|
||||
import { Tooltip } from "../UI";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tooltip: {
|
||||
backgroundColor: "#3868dc",
|
||||
color: "white",
|
||||
fontSize: 13,
|
||||
},
|
||||
}));
|
||||
|
||||
interface AnalyseButtonProps {
|
||||
analyzeStatus: any
|
||||
}
|
||||
|
||||
export const AnalyzeButton: React.FC<AnalyseButtonProps> = ({analyzeStatus}) => {
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const analysisMessage = analyzeStatus?.isRemoteReady ?
|
||||
<span>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><b>Available</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Messages</td>
|
||||
<td><b>{analyzeStatus?.sentCount}</b></td>
|
||||
</tr>
|
||||
</table>
|
||||
</span> :
|
||||
analyzeStatus?.sentCount > 0 ?
|
||||
<span>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><b>Processing</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Messages</td>
|
||||
<td><b>{analyzeStatus?.sentCount}</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan={2}> Please allow a few minutes for the analysis to complete</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span> :
|
||||
<span>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><b>Waiting for traffic</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Messages</td>
|
||||
<td><b>{analyzeStatus?.sentCount}</b></td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
|
||||
return ( <div>
|
||||
<Tooltip title={analysisMessage} isSimple classes={classes}>
|
||||
<div>
|
||||
<Button
|
||||
style={{fontFamily: "system-ui",
|
||||
fontWeight: 600,
|
||||
fontSize: 12,
|
||||
padding: 8}}
|
||||
size={"small"}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<img style={{height: 24, maxHeight: "none", maxWidth: "none"}} src={logo_up9} alt={"up9"}/>}
|
||||
disabled={!analyzeStatus?.isRemoteReady}
|
||||
onClick={() => {
|
||||
window.open(analyzeStatus?.remoteUrl)
|
||||
}}>
|
||||
Analysis
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>);
|
||||
}
|
39
ui-common/src/components/AnalyzeButton/logo_up9.svg
Normal file
@ -0,0 +1,39 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="prefix__logo" width="148" height="88" viewBox="0 0 148 88">
|
||||
<defs>
|
||||
<style>
|
||||
.prefix__cls-1{fill:#070047}.prefix__cls-2{fill:#fff}.prefix__cls-4{fill:#8f9bb2}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="prefix__Group_4690_2_">
|
||||
<path id="prefix__Path_2985_1_" d="M0 62.1a7.832 7.832 0 0 0 3.928 6.786l31.405 18.071a7.866 7.866 0 0 0 7.84 0L74.563 68.9a7.823 7.823 0 0 0 3.928-6.784v-36.2a7.828 7.828 0 0 0-3.926-6.782L43.175 1.052a7.869 7.869 0 0 0-7.86.006L3.914 19.217A7.827 7.827 0 0 0 0 25.994z" class="prefix__cls-1" transform="translate(0 -.004)"/>
|
||||
<path id="prefix__Path_2986_2_" d="M55.987 134.68a.976.976 0 0 1-.008 1.7l-4.4 2.482-.033-.02-6.832 3.948a1.966 1.966 0 0 1-1.966 0l-8.83-5.09A7.818 7.818 0 0 1 30 130.927v-29.356a7.813 7.813 0 0 1 3.979-6.8l.443-.25a.984.984 0 0 1 1.339.369.964.964 0 0 1 .127.482v36.187a.979.979 0 0 0 .488.845l4.909 2.855a.984.984 0 0 0 .981 0l.063-.035a.977.977 0 0 0 0-1.7l-3.994-2.3a.978.978 0 0 1-.49-.847V93.654a1.955 1.955 0 0 1 .995-1.7l3.429-1.936a.986.986 0 0 1 1.339.369.962.962 0 0 1 .127.48v36.187a.978.978 0 0 0 .49.847z" class="prefix__cls-2" transform="translate(-24.126 -72.289)"/>
|
||||
<path id="prefix__Path_2987_2_" d="M117.554 80.338a.981.981 0 0 1-.366-1.338.969.969 0 0 1 .37-.368l9.788-5.733a.981.981 0 0 0 .484-.846V37.572a1.961 1.961 0 0 0-2.951-1.694l-8.823 5.148-4.891 2.767a1.962 1.962 0 0 0-.995 1.707v36.849a.982.982 0 0 0 .49.85l12.246 7.086a1.965 1.965 0 0 0 1.991-.016l3.46-2.076a.983.983 0 0 0-.018-1.694zM116.532 47.6l3.924-2.356a.98.98 0 0 1 1.484.842v22.619a.982.982 0 0 1-.5.854l-3.924 2.224a.984.984 0 0 1-1.339-.37.97.97 0 0 1-.127-.484V48.447a1 1 0 0 1 .482-.847z" class="prefix__cls-2" transform="translate(-88.598 -28.638)"/>
|
||||
<path id="prefix__Path_2988_2_" d="M133.819 43.492V77.9a.979.979 0 0 1-.49.848l-7.858 4.545a.978.978 0 0 0 0 1.693l10.8 6.235a.977.977 0 0 1 0 1.693l-20.607 11.9a.978.978 0 0 0 0 1.7l12.279 7.01a7.87 7.87 0 0 0 7.8 0l26.5-15.161a5.871 5.871 0 0 0 2.957-5.094V62.723a7.825 7.825 0 0 0-3.92-6.778L136.762 41.8a1.959 1.959 0 0 0-2.943 1.7zm24.037 42.144L140.684 75.7a1.957 1.957 0 0 1-.977-1.693V60.981a.983.983 0 0 1 1.472-.848l14.239 8.237a7.826 7.826 0 0 1 3.91 6.772v9.647a.979.979 0 0 1-.979.98.968.968 0 0 1-.493-.133z" transform="translate(-92.625 -33.401)" style="fill:#627ef7"/>
|
||||
<path id="prefix__Path_2989_2_" d="M60.346 291.555l-4.4 2.491-.033-.02-6.832 3.963a1.963 1.963 0 0 1-1.968 0l-8.835-5.111a7.826 7.826 0 0 1-2.853-2.851l4.938-2.851a1 1 0 0 0 .37.384l4.909 2.865a.981.981 0 0 0 .981 0l.063-.035a.982.982 0 0 0 0-1.7l-3.986-2.306a.981.981 0 0 1-.352-.352l5.86-3.382a.99.99 0 0 0 .378.4l11.764 6.8a.981.981 0 0 1-.006 1.7z" class="prefix__cls-4" transform="translate(-28.493 -227.445)"/>
|
||||
<path id="prefix__Path_2991_2_" d="M164.463 248.2a5.858 5.858 0 0 1-2.193 2.207l-26.5 15.2a7.854 7.854 0 0 1-7.8 0l-12.279-7.026a.983.983 0 0 1 0-1.7l16.187-9.375 4.419-2.559a.98.98 0 0 0 0-1.7l-4.419-2.557-6.392-3.69a.98.98 0 0 1 0-1.7l6.385-3.7 1.472-.854a.968.968 0 0 0 .333-.321z" transform="translate(-92.637 -185.485)" style="fill:#3b4c95"/>
|
||||
<path id="prefix__Path_2992_2_" d="M128.9 265.267l-.525.313-2.933 1.762a1.965 1.965 0 0 1-1.991.016l-12.244-7.082a1.021 1.021 0 0 1-.206-.161.928.928 0 0 1-.151-.2v-.006l2.692-1.555 4.445-2.567a.762.762 0 0 0-.094.084.6.6 0 0 0-.08.092.035.035 0 0 0-.012.018.479.479 0 0 0-.055.084.316.316 0 0 0-.02.035c-.01.02-.02.039-.027.059a.369.369 0 0 0-.023.059.378.378 0 0 0-.022.067.08.08 0 0 0-.01.035.729.729 0 0 0-.018.09.974.974 0 0 0 .482 1l10.274 5.872.507.288a.979.979 0 0 1 .366 1.335 1 1 0 0 1-.355.362z" class="prefix__cls-4" transform="translate(-89.145 -205.825)"/>
|
||||
</g>
|
||||
<g id="prefix__Group_4695" data-name="Group 4695" transform="translate(72.538 19.232)">
|
||||
<g id="prefix__Group_4694" data-name="Group 4694">
|
||||
<g id="prefix__Group_4691" data-name="Group 4691">
|
||||
<path id="prefix__Path_2993" d="M394.794 158.1a9.4 9.4 0 0 1-6.626-2.728 9.4 9.4 0 0 1-1.987-2.937 9.248 9.248 0 0 1-.74-3.687v-31.142a3.92 3.92 0 0 1 3.916-3.916h4.455a3.92 3.92 0 0 1 3.916 3.916v28.2h.54v-28.2a3.92 3.92 0 0 1 3.916-3.916h4.507a3.92 3.92 0 0 1 3.916 3.916v36.577a3.92 3.92 0 0 1-3.916 3.916z" class="prefix__cls-2" data-name="Path 2993" transform="translate(-382.507 -110.753)"/>
|
||||
<path id="prefix__Path_2994" d="M394.648 104.564a.979.979 0 0 1 .979.979v36.577a.979.979 0 0 1-.979.979h-11.9a6.454 6.454 0 0 1-4.547-1.866 6.447 6.447 0 0 1-1.367-2.025 6.291 6.291 0 0 1-.5-2.524v-31.141a.979.979 0 0 1 .979-.979h4.455a.979.979 0 0 1 .979.979v30.162a.979.979 0 0 0 .979.979h4.457a.979.979 0 0 0 .979-.979v-30.162a.979.979 0 0 1 .979-.979h4.507m0-5.874h-4.508a6.832 6.832 0 0 0-4.186 1.429 6.819 6.819 0 0 0-4.186-1.429h-4.455a6.861 6.861 0 0 0-6.853 6.853v31.141a12.147 12.147 0 0 0 .983 4.856 12.29 12.29 0 0 0 11.3 7.433h11.9a6.861 6.861 0 0 0 6.853-6.853v-36.577a6.861 6.861 0 0 0-6.853-6.853z" class="prefix__cls-1" data-name="Path 2994" transform="translate(-370.46 -98.69)"/>
|
||||
</g>
|
||||
<g id="prefix__Group_4692" data-name="Group 4692" transform="translate(22.447)">
|
||||
<path id="prefix__Path_2995" d="M504.016 158.1a3.92 3.92 0 0 1-3.916-3.916v-36.578a3.92 3.92 0 0 1 3.916-3.916h11.848a9.332 9.332 0 0 1 3.64.73 9.635 9.635 0 0 1 2.978 1.964 9.144 9.144 0 0 1 2.054 3.015 9.312 9.312 0 0 1 .73 3.642v9.62a9.235 9.235 0 0 1-.74 3.687 9.48 9.48 0 0 1-5.024 4.985 9.3 9.3 0 0 1-3.638.73h-3.478v12.118a3.92 3.92 0 0 1-3.916 3.916zm8.911-28.374v-3.746h-.54v3.746z" class="prefix__cls-2" data-name="Path 2995" transform="translate(-497.163 -110.753)"/>
|
||||
<path id="prefix__Path_2996" d="M503.8 104.564a6.351 6.351 0 0 1 2.5.5 6.709 6.709 0 0 1 2.078 1.367 6.157 6.157 0 0 1 1.392 2.05 6.358 6.358 0 0 1 .5 2.5v9.62a6.3 6.3 0 0 1-.5 2.524 6.536 6.536 0 0 1-1.392 2.05 6.453 6.453 0 0 1-2.078 1.394 6.358 6.358 0 0 1-2.5.5h-5.436a.979.979 0 0 0-.979.979v14.072a.979.979 0 0 1-.979.979h-4.455a.979.979 0 0 1-.979-.979v-36.577a.979.979 0 0 1 .979-.979H503.8m-5.434 16.036h4.457a.979.979 0 0 0 .979-.979v-7.662a.979.979 0 0 0-.979-.979h-4.457a.979.979 0 0 0-.979.979v7.662a.979.979 0 0 0 .979.979m5.434-21.91h-11.847a6.861 6.861 0 0 0-6.853 6.853v36.577a6.861 6.861 0 0 0 6.853 6.853h4.455a6.861 6.861 0 0 0 6.853-6.853v-9.181h.54a12.337 12.337 0 0 0 8.729-3.613 12.423 12.423 0 0 0 2.632-3.873 12.164 12.164 0 0 0 .981-4.854v-9.62a12.217 12.217 0 0 0-.961-4.78 12.067 12.067 0 0 0-2.714-3.981 12.589 12.589 0 0 0-3.881-2.563 12.193 12.193 0 0 0-4.786-.965z" class="prefix__cls-1" data-name="Path 2996" transform="translate(-485.1 -98.69)"/>
|
||||
</g>
|
||||
<g id="prefix__Group_4693" data-name="Group 4693" transform="translate(44.421)">
|
||||
<path id="prefix__Path_2997" d="M621.68 158.107a9.331 9.331 0 0 1-9.35-9.35V144.9a3.92 3.92 0 0 1 3.916-3.916h1.038a9.008 9.008 0 0 1-2.26-1.7 9.676 9.676 0 0 1-1.954-2.931 9.249 9.249 0 0 1-.74-3.687v-9.62a9.328 9.328 0 0 1 9.35-9.35h6.415a9.332 9.332 0 0 1 3.64.73 9.613 9.613 0 0 1 2.978 1.964 9.127 9.127 0 0 1 2.054 3.015 9.316 9.316 0 0 1 .732 3.642v25.707a9.287 9.287 0 0 1-.73 3.638 9.113 9.113 0 0 1-2.054 3.015 9.66 9.66 0 0 1-2.98 1.966 9.308 9.308 0 0 1-3.638.73h-6.417zm3.478-12.289v-3.746h-1.747a3.905 3.905 0 0 1 1.208 2.825v.92zm0-16.085v-3.746h-.54v3.746z" class="prefix__cls-2" data-name="Path 2997" transform="translate(-609.391 -110.761)"/>
|
||||
<path id="prefix__Path_2998" d="M616.024 104.564a6.351 6.351 0 0 1 2.5.5 6.708 6.708 0 0 1 2.078 1.367 6.157 6.157 0 0 1 1.392 2.05 6.358 6.358 0 0 1 .5 2.5v25.707a6.344 6.344 0 0 1-.5 2.5 6.157 6.157 0 0 1-1.392 2.05 6.709 6.709 0 0 1-2.078 1.367 6.358 6.358 0 0 1-2.5.5h-6.415a6.393 6.393 0 0 1-6.413-6.413v-3.857a.979.979 0 0 1 .979-.979h4.455a.979.979 0 0 1 .979.979v2.878a.979.979 0 0 0 .979.979h4.457a.979.979 0 0 0 .979-.979v-7.662a.979.979 0 0 0-.979-.979h-5.436a6.344 6.344 0 0 1-2.5-.5 6.133 6.133 0 0 1-2.05-1.394 6.769 6.769 0 0 1-1.367-2.05 6.291 6.291 0 0 1-.5-2.524v-9.62a6.393 6.393 0 0 1 6.413-6.413h6.415m-5.432 16.029h4.457a.979.979 0 0 0 .979-.979v-7.662a.979.979 0 0 0-.979-.979h-4.457a.979.979 0 0 0-.979.979v7.662a.979.979 0 0 0 .979.979m5.436-21.909h-6.415a12.268 12.268 0 0 0-12.289 12.287v9.62a12.147 12.147 0 0 0 .983 4.856 12.629 12.629 0 0 0 1.281 2.287 6.841 6.841 0 0 0-2.264 5.085v3.857a12.268 12.268 0 0 0 12.287 12.289h6.415a12.2 12.2 0 0 0 4.784-.963 12.579 12.579 0 0 0 3.879-2.559 12.047 12.047 0 0 0 2.714-3.981 12.233 12.233 0 0 0 .963-4.785v-25.707a12.234 12.234 0 0 0-.961-4.782 12.066 12.066 0 0 0-2.714-3.981 12.623 12.623 0 0 0-3.881-2.563 12.233 12.233 0 0 0-4.782-.961z" class="prefix__cls-1" data-name="Path 2998" transform="translate(-597.32 -98.69)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="prefix__Group_4697" data-name="Group 4697" transform="translate(78.412 25.106)">
|
||||
<g id="prefix__Group_4696" data-name="Group 4696">
|
||||
<path id="prefix__Path_2999" d="M419.753 129.669v36.577a.979.979 0 0 1-.979.979h-11.9a6.454 6.454 0 0 1-4.547-1.866 6.447 6.447 0 0 1-1.367-2.025 6.292 6.292 0 0 1-.5-2.524v-31.141a.979.979 0 0 1 .979-.979h4.455a.979.979 0 0 1 .979.979v30.162a.979.979 0 0 0 .979.979h4.457a.979.979 0 0 0 .979-.979v-30.162a.979.979 0 0 1 .979-.979h4.507a.979.979 0 0 1 .979.979z" class="prefix__cls-2" data-name="Path 2999" transform="translate(-400.46 -128.69)"/>
|
||||
<path id="prefix__Path_3000" d="M534.393 135.1v9.62a6.3 6.3 0 0 1-.5 2.524 6.535 6.535 0 0 1-1.392 2.05 6.453 6.453 0 0 1-2.077 1.394 6.358 6.358 0 0 1-2.5.5h-5.436a.979.979 0 0 0-.979.979v14.076a.979.979 0 0 1-.979.979h-4.455a.979.979 0 0 1-.979-.979v-36.574a.979.979 0 0 1 .979-.979h11.848a6.351 6.351 0 0 1 2.5.5 6.708 6.708 0 0 1 2.077 1.367 6.157 6.157 0 0 1 1.392 2.05 6.358 6.358 0 0 1 .501 2.493zm-6.466 8.643v-7.662a.979.979 0 0 0-.979-.979h-4.457a.979.979 0 0 0-.979.979v7.662a.979.979 0 0 0 .979.979h4.457a.979.979 0 0 0 .979-.977z" class="prefix__cls-2" data-name="Path 3000" transform="translate(-492.653 -128.69)"/>
|
||||
<path id="prefix__Path_3001" d="M646.623 135.1v25.71a6.345 6.345 0 0 1-.5 2.5 6.158 6.158 0 0 1-1.392 2.05 6.71 6.71 0 0 1-2.078 1.367 6.358 6.358 0 0 1-2.5.5h-6.415a6.393 6.393 0 0 1-6.413-6.413v-3.857a.979.979 0 0 1 .979-.979h4.455a.979.979 0 0 1 .979.979v2.878a.979.979 0 0 0 .979.979h4.457a.979.979 0 0 0 .979-.979v-7.662a.979.979 0 0 0-.979-.979h-5.436a6.344 6.344 0 0 1-2.5-.5 6.133 6.133 0 0 1-2.05-1.394 6.77 6.77 0 0 1-1.367-2.05 6.292 6.292 0 0 1-.5-2.524V135.1a6.393 6.393 0 0 1 6.413-6.413h6.415a6.351 6.351 0 0 1 2.5.5 6.709 6.709 0 0 1 2.078 1.367 6.157 6.157 0 0 1 1.392 2.05 6.359 6.359 0 0 1 .504 2.496zm-6.466 8.643v-7.662a.979.979 0 0 0-.979-.979h-4.457a.979.979 0 0 0-.979.979v7.662a.979.979 0 0 0 .979.979h4.457a.979.979 0 0 0 .979-.976z" class="prefix__cls-2" data-name="Path 3001" transform="translate(-582.908 -128.69)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 11 KiB |
@ -25,5 +25,4 @@
|
||||
|
||||
.redoc
|
||||
height: 98%
|
||||
overflow-y: scroll
|
||||
|
||||
overflow-y: scroll
|
@ -1,13 +1,13 @@
|
||||
import { Box, Fade, FormControl, MenuItem, Modal, Backdrop, ListSubheader } from "@material-ui/core";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { RedocStandalone } from "redoc";
|
||||
import Api from "../../helpers/api";
|
||||
import { Select } from "../UI/Select";
|
||||
import closeIcon from "../assets/closeIcon.svg";
|
||||
import closeIcon from "assets/closeIcon.svg";
|
||||
import { toast } from 'react-toastify';
|
||||
import style from './OasModal.module.sass';
|
||||
import openApiLogo from '../assets/openApiLogo.png'
|
||||
import openApiLogo from 'assets/openApiLogo.png'
|
||||
import { redocThemeOptions } from "./redocThemeOptions";
|
||||
import React from "react";
|
||||
import { Select } from "../UI/Select";
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute',
|
||||
@ -23,10 +23,9 @@ const modalStyle = {
|
||||
color: '#000',
|
||||
};
|
||||
|
||||
const api = Api.getInstance();
|
||||
const ipAddressWithPortRegex = new RegExp('([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}):([0-9]{1,5})');
|
||||
|
||||
const OasModal = ({ openModal, handleCloseModal }) => {
|
||||
const OasModal = ({ openModal, handleCloseModal, getOasServices, getOasByService}) => {
|
||||
const [oasServices, setOasServices] = useState([] as string[])
|
||||
const [selectedServiceName, setSelectedServiceName] = useState("");
|
||||
const [selectedServiceSpec, setSelectedServiceSpec] = useState(null);
|
||||
@ -40,7 +39,7 @@ const OasModal = ({ openModal, handleCloseModal }) => {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = await api.getOasByService(selectedService);
|
||||
const data = await getOasByService(selectedService);
|
||||
setSelectedServiceSpec(data);
|
||||
} catch (e) {
|
||||
toast.error("Error occurred while fetching service OAS spec");
|
||||
@ -71,7 +70,7 @@ const OasModal = ({ openModal, handleCloseModal }) => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const services = await api.getOasServices();
|
||||
const services = await getOasServices();
|
||||
resolvedArrayBuilder(services);
|
||||
setOasServices(services);
|
||||
} catch (e) {
|
||||
@ -107,7 +106,7 @@ const OasModal = ({ openModal, handleCloseModal }) => {
|
||||
id="service-select"
|
||||
placeholder="Show OAS"
|
||||
value={selectedServiceName}
|
||||
onChange={onSelectedOASService}
|
||||
onChangeCb={onSelectedOASService}
|
||||
>
|
||||
<ListSubheader disableSticky={true}>Resolved</ListSubheader>
|
||||
{resolvedServices.map((service) => (
|
||||
@ -140,4 +139,4 @@ const OasModal = ({ openModal, handleCloseModal }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default OasModal;
|
||||
export default OasModal;
|
4
ui-common/src/components/OasModal/assets/closeIcon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.5 11C20.5 16.2467 16.2467 20.5 11 20.5C5.75329 20.5 1.5 16.2467 1.5 11C1.5 5.75329 5.75329 1.5 11 1.5C16.2467 1.5 20.5 5.75329 20.5 11Z" stroke="#2B3560"/>
|
||||
<path d="M14.4762 9.05338L13.1448 7.7219L11.1528 9.71382L9.16091 7.7219L7.82943 9.05338L9.82135 11.0453L7.83226 13.0344L9.16374 14.3659L11.1528 12.3768L13.1419 14.3659L14.4734 13.0344L12.4843 11.0453L14.4762 9.05338Z" fill="#627EF7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 507 B |
BIN
ui-common/src/components/OasModal/assets/openApiLogo.png
Normal file
After Width: | Height: | Size: 66 KiB |
35
ui-common/src/components/OasModal/redocThemeOptions.ts
Normal file
@ -0,0 +1,35 @@
|
||||
export const redocThemeOptions = {
|
||||
theme:{
|
||||
codeBlock:{
|
||||
backgroundColor:"#11171a",
|
||||
},
|
||||
colors:{
|
||||
responses:{
|
||||
error:{
|
||||
tabTextColor:"#1b1b29"
|
||||
},
|
||||
info:{
|
||||
tabTextColor:"#1b1b29",
|
||||
},
|
||||
success:{
|
||||
tabTextColor:"#0c0b1a"
|
||||
},
|
||||
},
|
||||
text:{
|
||||
primary:"#1b1b29",
|
||||
secondary:"#4d4d4d"
|
||||
}
|
||||
},
|
||||
rightPanel:{
|
||||
backgroundColor:"#253237",
|
||||
},
|
||||
sidebar:{
|
||||
backgroundColor:"#ffffff"
|
||||
},
|
||||
typography:{
|
||||
code:{
|
||||
color:"#0c0b1a"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import {Snackbar} from "@material-ui/core";
|
||||
import MuiAlert from "@material-ui/lab/Alert";
|
||||
import React, {useEffect} from "react";
|
||||
import Api from "../../helpers/api";
|
||||
import { RecoilState, useRecoilValue } from "recoil";
|
||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
|
||||
import TrafficViewerApi from "../TrafficViewer/TrafficViewerApi";
|
||||
import './TLSWarning.sass';
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
interface TLSWarningProps {
|
||||
showTLSWarning: boolean
|
||||
setShowTLSWarning: (show: boolean) => void
|
||||
@ -17,10 +17,12 @@ interface TLSWarningProps {
|
||||
|
||||
export const TLSWarning: React.FC<TLSWarningProps> = ({showTLSWarning, setShowTLSWarning, addressesWithTLS, setAddressesWithTLS, userDismissedTLSWarning, setUserDismissedTLSWarning}) => {
|
||||
|
||||
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const recentTLSLinks = await api.getRecentTLSLinks();
|
||||
const getRecentTLSLinksFunc = trafficViewerApi?.getRecentTLSLinks ? trafficViewerApi?.getRecentTLSLinks : function(){}
|
||||
const recentTLSLinks = await getRecentTLSLinksFunc();
|
||||
if (recentTLSLinks?.length > 0) {
|
||||
setAddressesWithTLS(new Set(recentTLSLinks));
|
||||
setShowTLSWarning(true);
|
||||
@ -29,7 +31,7 @@ export const TLSWarning: React.FC<TLSWarningProps> = ({showTLSWarning, setShowT
|
||||
console.error(e);
|
||||
}
|
||||
})();
|
||||
}, [setShowTLSWarning, setAddressesWithTLS]);
|
||||
}, [setShowTLSWarning, setAddressesWithTLS,trafficViewerApi]);
|
||||
|
||||
return (<Snackbar open={showTLSWarning && !userDismissedTLSWarning}>
|
||||
<MuiAlert classes={{filledWarning: 'customWarningStyle'}} elevation={6} variant="filled"
|
@ -1,15 +1,17 @@
|
||||
import React, {useCallback, useEffect, useMemo, useState} from "react";
|
||||
import styles from './style/EntriesList.module.sass';
|
||||
import styles from '../style/EntriesList.module.sass';
|
||||
import ScrollableFeedVirtualized from "react-scrollable-feed-virtualized";
|
||||
import Moment from 'moment';
|
||||
import {EntryItem} from "./EntryListItem/EntryListItem";
|
||||
import down from "./assets/downImg.svg";
|
||||
import spinner from './assets/spinner.svg';
|
||||
import Api from "../helpers/api";
|
||||
import {useRecoilState, useRecoilValue} from "recoil";
|
||||
import entriesAtom from "../recoil/entries";
|
||||
import wsConnectionAtom, {WsConnectionStatus} from "../recoil/wsConnection";
|
||||
import queryAtom from "../recoil/query";
|
||||
import down from "assets/downImg.svg";
|
||||
import spinner from 'assets/spinner.svg';
|
||||
|
||||
import {RecoilState, useRecoilState, useRecoilValue} from "recoil";
|
||||
import entriesAtom from "../../recoil/entries";
|
||||
import wsConnectionAtom, {WsConnectionStatus} from "../../recoil/wsConnection";
|
||||
import queryAtom from "../../recoil/query";
|
||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi";
|
||||
import TrafficViewerApi from "./TrafficViewerApi";
|
||||
|
||||
interface EntriesListProps {
|
||||
listEntryREF: any;
|
||||
@ -25,7 +27,6 @@ interface EntriesListProps {
|
||||
setNoMoreDataTop: (flag: boolean) => void;
|
||||
leftOffTop: number;
|
||||
setLeftOffTop: (leftOffTop: number) => void;
|
||||
ws: any;
|
||||
openWebSocket: (query: string, resetEntries: boolean) => void;
|
||||
leftOffBottom: number;
|
||||
truncatedTimestamp: number;
|
||||
@ -33,14 +34,13 @@ interface EntriesListProps {
|
||||
scrollableRef: any;
|
||||
}
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, queriedTotal, setQueriedTotal, startTime, noMoreDataTop, setNoMoreDataTop, leftOffTop, setLeftOffTop, ws, openWebSocket, leftOffBottom, truncatedTimestamp, setTruncatedTimestamp, scrollableRef}) => {
|
||||
export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBrokenEvent, isSnappedToBottom, setIsSnappedToBottom, queriedCurrent, setQueriedCurrent, queriedTotal, setQueriedTotal, startTime, noMoreDataTop, setNoMoreDataTop, leftOffTop, setLeftOffTop, openWebSocket, leftOffBottom, truncatedTimestamp, setTruncatedTimestamp, scrollableRef}) => {
|
||||
|
||||
const [entries, setEntries] = useRecoilState(entriesAtom);
|
||||
const wsConnection = useRecoilValue(wsConnectionAtom);
|
||||
const query = useRecoilValue(queryAtom);
|
||||
const isWsConnectionClosed = wsConnection === WsConnectionStatus.Closed;
|
||||
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||
|
||||
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
||||
const [isLoadingTop, setIsLoadingTop] = useState(false);
|
||||
@ -68,7 +68,7 @@ export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBro
|
||||
return;
|
||||
}
|
||||
setIsLoadingTop(true);
|
||||
const data = await api.fetchEntries(leftOffTop, -1, query, 100, 3000);
|
||||
const data = await trafficViewerApi.fetchEntries(leftOffTop, -1, query, 100, 3000);
|
||||
if (!data || data.data === null || data.meta === null) {
|
||||
setNoMoreDataTop(true);
|
||||
setIsLoadingTop(false);
|
||||
@ -104,7 +104,7 @@ export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBro
|
||||
|
||||
const scrollbarVisible = scrollableRef.current?.childWrapperRef.current.clientHeight > scrollableRef.current?.wrapperRef.current.clientHeight;
|
||||
|
||||
return <>
|
||||
return <React.Fragment>
|
||||
<div className={styles.list}>
|
||||
<div id="list" ref={listEntryREF} className={styles.list}>
|
||||
{isLoadingTop && <div className={styles.spinnerContainer}>
|
||||
@ -124,7 +124,7 @@ export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBro
|
||||
title="Fetch old records"
|
||||
className={`${styles.btnOld} ${!scrollbarVisible && leftOffTop > 0 ? styles.showButton : styles.hideButton}`}
|
||||
onClick={(_) => {
|
||||
ws.close();
|
||||
trafficViewerApi.webSocket.close()
|
||||
getOldEntries();
|
||||
}}>
|
||||
<img alt="down" src={down} />
|
||||
@ -152,5 +152,5 @@ export const EntriesList: React.FC<EntriesListProps> = ({listEntryREF, onSnapBro
|
||||
{startTime !== 0 && <div>Started listening at <span style={{marginRight: 5, fontWeight: 600, fontSize: 13}}>{Moment(truncatedTimestamp ? truncatedTimestamp : startTime).utc().format('MM/DD/YYYY, h:mm:ss.SSS A')}</span></div>}
|
||||
</div>
|
||||
</div>
|
||||
</>;
|
||||
</React.Fragment>;
|
||||
};
|
@ -2,13 +2,15 @@ import React, {useEffect, useState} from "react";
|
||||
import EntryViewer from "./EntryDetailed/EntryViewer";
|
||||
import {EntryItem} from "./EntryListItem/EntryListItem";
|
||||
import {makeStyles} from "@material-ui/core";
|
||||
import Protocol from "./UI/Protocol"
|
||||
import Queryable from "./UI/Queryable";
|
||||
import Protocol from "../UI/Protocol"
|
||||
import Queryable from "../UI/Queryable";
|
||||
import {toast} from "react-toastify";
|
||||
import {useRecoilValue} from "recoil";
|
||||
import focusedEntryIdAtom from "../recoil/focusedEntryId";
|
||||
import Api from "../helpers/api";
|
||||
import queryAtom from "../recoil/query";
|
||||
import {RecoilState, useRecoilState, useRecoilValue} from "recoil";
|
||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||
import trafficViewerApi from "../../recoil/TrafficViewerApi";
|
||||
import TrafficViewerApi from "./TrafficViewerApi";
|
||||
import TrafficViewerApiAtom from "../../recoil/TrafficViewerApi/atom";
|
||||
import queryAtom from "../../recoil/query/atom";
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
entryTitle: {
|
||||
@ -78,12 +80,14 @@ const EntrySummary: React.FC<any> = ({entry}) => {
|
||||
/>;
|
||||
};
|
||||
|
||||
const api = Api.getInstance();
|
||||
|
||||
|
||||
export const EntryDetailed = () => {
|
||||
|
||||
const focusedEntryId = useRecoilValue(focusedEntryIdAtom);
|
||||
const trafficViewerApi = useRecoilValue(TrafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||
const query = useRecoilValue(queryAtom);
|
||||
|
||||
const [entryData, setEntryData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -91,7 +95,7 @@ export const EntryDetailed = () => {
|
||||
setEntryData(null);
|
||||
(async () => {
|
||||
try {
|
||||
const entryData = await api.getEntry(focusedEntryId, query);
|
||||
const entryData = await trafficViewerApi.getEntry(focusedEntryId, query);
|
||||
setEntryData(entryData);
|
||||
} catch (error) {
|
||||
if (error.response?.data?.type) {
|
||||
@ -112,7 +116,7 @@ export const EntryDetailed = () => {
|
||||
// eslint-disable-next-line
|
||||
}, [focusedEntryId]);
|
||||
|
||||
return <>
|
||||
return <React.Fragment>
|
||||
{entryData && <EntryTitle
|
||||
protocol={entryData.protocol}
|
||||
data={entryData.data}
|
||||
@ -120,7 +124,7 @@ export const EntryDetailed = () => {
|
||||
elapsedTime={entryData.data.elapsedTime}
|
||||
/>}
|
||||
{entryData && <EntrySummary entry={entryData.base}/>}
|
||||
<>
|
||||
<React.Fragment>
|
||||
{entryData && <EntryViewer
|
||||
representation={entryData.representation}
|
||||
isRulesEnabled={entryData.isRulesEnabled}
|
||||
@ -132,6 +136,6 @@ export const EntryDetailed = () => {
|
||||
elapsedTime={entryData.data.elapsedTime}
|
||||
color={entryData.protocol.backgroundColor}
|
||||
/>}
|
||||
</>
|
||||
</>
|
||||
</React.Fragment>
|
||||
</React.Fragment>
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
@import '../../variables.module'
|
||||
@import '../../../variables.module'
|
||||
|
||||
.title
|
||||
display: flex
|
@ -1,10 +1,10 @@
|
||||
import styles from "./EntrySections.module.sass";
|
||||
import React, {useState} from "react";
|
||||
import {SyntaxHighlighter} from "../UI/SyntaxHighlighter/index";
|
||||
import CollapsibleContainer from "../UI/CollapsibleContainer";
|
||||
import FancyTextDisplay from "../UI/FancyTextDisplay";
|
||||
import Queryable from "../UI/Queryable";
|
||||
import Checkbox from "../UI/Checkbox";
|
||||
import {SyntaxHighlighter} from "../../UI/SyntaxHighlighter/index";
|
||||
import CollapsibleContainer from "../../UI/CollapsibleContainer";
|
||||
import FancyTextDisplay from "../../UI/FancyTextDisplay";
|
||||
import Queryable from "../../UI/Queryable";
|
||||
import Checkbox from "../../UI/Checkbox";
|
||||
import ProtobufDecoder from "protobuf-decoder";
|
||||
import {default as jsonBeautify} from "json-beautify";
|
||||
import {default as xmlBeautify} from "xml-formatter";
|
||||
@ -288,7 +288,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({titl
|
||||
return <React.Fragment>
|
||||
{
|
||||
arrayToIterate && arrayToIterate.length > 0 ?
|
||||
<>
|
||||
<React.Fragment>
|
||||
<EntrySectionContainer title={title} color={color}>
|
||||
<table>
|
||||
<tbody>
|
||||
@ -296,7 +296,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({titl
|
||||
return (
|
||||
<EntryPolicySectionContainer key={index} label={rule.Name} matched={matched && (rule.Type === 'slo' ? rule.ResponseTime >= latency : true)? "Success" : "Failure"}>
|
||||
{
|
||||
<>
|
||||
<React.Fragment>
|
||||
{
|
||||
rule.Key &&
|
||||
<tr className={styles.dataValue}><td><b>Key:</b></td> <td>{rule.Key}</td></tr>
|
||||
@ -325,7 +325,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({titl
|
||||
rule.Value &&
|
||||
<tr className={styles.dataValue}><td><b>Value:</b></td> <td>{rule.Value}</td></tr>
|
||||
}
|
||||
</>
|
||||
</React.Fragment>
|
||||
}
|
||||
</EntryPolicySectionContainer>
|
||||
)
|
||||
@ -335,7 +335,7 @@ export const EntryTablePolicySection: React.FC<EntryPolicySectionProps> = ({titl
|
||||
</tbody>
|
||||
</table>
|
||||
</EntrySectionContainer>
|
||||
</> : <span className={styles.noRules}>No rules could be applied to this request.</span>
|
||||
</React.Fragment> : <span className={styles.noRules}>No rules could be applied to this request.</span>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
@import "../../variables.module"
|
||||
@import "../../../variables.module"
|
||||
|
||||
.Entry
|
||||
font-family: "Source Sans Pro", Lucida Grande, Tahoma, sans-serif
|
@ -1,6 +1,6 @@
|
||||
import React, {useState} from 'react';
|
||||
import styles from './EntryViewer.module.sass';
|
||||
import Tabs from "../UI/Tabs";
|
||||
import Tabs from "../../UI/Tabs";
|
||||
import {EntryTableSection, EntryBodySection, EntryTablePolicySection, EntryContractSection} from "./EntrySections";
|
||||
|
||||
enum SectionTypes {
|
||||
@ -30,7 +30,7 @@ const SectionsRepresentation: React.FC<any> = ({data, color}) => {
|
||||
}
|
||||
}
|
||||
|
||||
return <>{sections}</>;
|
||||
return <React.Fragment>{sections}</React.Fragment>;
|
||||
}
|
||||
|
||||
const AutoRepresentation: React.FC<any> = ({representation, isRulesEnabled, rulesMatched, contractStatus, requestReason, responseReason, contractContent, elapsedTime, color}) => {
|
||||
@ -43,7 +43,7 @@ const AutoRepresentation: React.FC<any> = ({representation, isRulesEnabled, rule
|
||||
|
||||
// Don't fail even if `representation` is an empty string
|
||||
if (!representation) {
|
||||
return <></>;
|
||||
return <React.Fragment></React.Fragment>;
|
||||
}
|
||||
|
||||
const {request, response} = JSON.parse(representation);
|
@ -1,4 +1,4 @@
|
||||
@import '../../variables.module'
|
||||
@import '../../../variables.module'
|
||||
|
||||
.row
|
||||
display: flex
|
@ -2,19 +2,19 @@ import React from "react";
|
||||
import Moment from 'moment';
|
||||
import SwapHorizIcon from '@material-ui/icons/SwapHoriz';
|
||||
import styles from './EntryListItem.module.sass';
|
||||
import StatusCode, {getClassification, StatusCodeClassification} from "../UI/StatusCode";
|
||||
import Protocol, {ProtocolInterface} from "../UI/Protocol"
|
||||
import {Summary} from "../UI/Summary";
|
||||
import Queryable from "../UI/Queryable";
|
||||
import ingoingIconSuccess from "../assets/ingoing-traffic-success.svg"
|
||||
import ingoingIconFailure from "../assets/ingoing-traffic-failure.svg"
|
||||
import ingoingIconNeutral from "../assets/ingoing-traffic-neutral.svg"
|
||||
import outgoingIconSuccess from "../assets/outgoing-traffic-success.svg"
|
||||
import outgoingIconFailure from "../assets/outgoing-traffic-failure.svg"
|
||||
import outgoingIconNeutral from "../assets/outgoing-traffic-neutral.svg"
|
||||
import StatusCode, {getClassification, StatusCodeClassification} from "../../UI/StatusCode";
|
||||
import Protocol, {ProtocolInterface} from "../../UI/Protocol"
|
||||
import {Summary} from "../../UI/Summary";
|
||||
import Queryable from "../../UI/Queryable";
|
||||
import ingoingIconSuccess from "assets/ingoing-traffic-success.svg"
|
||||
import ingoingIconFailure from "assets/ingoing-traffic-failure.svg"
|
||||
import ingoingIconNeutral from "assets/ingoing-traffic-neutral.svg"
|
||||
import outgoingIconSuccess from "assets/outgoing-traffic-success.svg"
|
||||
import outgoingIconFailure from "assets/outgoing-traffic-failure.svg"
|
||||
import outgoingIconNeutral from "assets/outgoing-traffic-neutral.svg"
|
||||
import {useRecoilState} from "recoil";
|
||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||
import queryAtom from "../../recoil/query";
|
||||
import focusedEntryIdAtom from "../../../recoil/focusedEntryId";
|
||||
import queryAtom from "../../../recoil/query";
|
||||
|
||||
interface TCPInterface {
|
||||
ip: string
|
||||
@ -133,7 +133,7 @@ export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode}) =>
|
||||
let endpointServiceContainer = "10px";
|
||||
if (!isStatusCodeEnabled) endpointServiceContainer = "20px";
|
||||
|
||||
return <>
|
||||
return <React.Fragment>
|
||||
<div
|
||||
id={`entry-${entry.id.toString()}`}
|
||||
className={`${styles.row}
|
||||
@ -304,6 +304,6 @@ export const EntryItem: React.FC<EntryProps> = ({entry, style, headingMode}) =>
|
||||
</Queryable>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</React.Fragment>
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5175 11.1465C16.8392 10.8869 17 10.4434 17 10C17 9.55657 16.8392 9.11314 16.5175 8.85348L12.5425 5.64459C13.2682 5.23422 14.1067 5 15 5C17.7614 5 20 7.23858 20 10C20 12.7614 17.7614 15 15 15C14.1067 15 13.2682 14.7658 12.5425 14.3554L16.5175 11.1465Z" fill="#BCCEFD"/>
|
||||
<path d="M16 10C16 10.3167 15.8749 10.6335 15.6247 10.8189L10.1706 14.8624C9.65543 15.2444 9 14.7858 9 14.0435V5.95652C9 5.21417 9.65543 4.75564 10.1706 5.13758L15.6247 9.18106C15.8749 9.36653 16 9.68326 16 10Z" fill="#EB5757"/>
|
||||
<path d="M0 10C0 8.89543 0.895431 8 2 8H10C11.1046 8 12 8.89543 12 10C12 11.1046 11.1046 12 10 12H2C0.895431 12 0 11.1046 0 10Z" fill="#EB5757"/>
|
||||
</svg>
|
After Width: | Height: | Size: 800 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5175 11.1465C16.8392 10.8869 17 10.4434 17 10C17 9.55657 16.8392 9.11314 16.5175 8.85348L12.5425 5.64459C13.2682 5.23422 14.1067 5 15 5C17.7614 5 20 7.23858 20 10C20 12.7614 17.7614 15 15 15C14.1067 15 13.2682 14.7658 12.5425 14.3554L16.5175 11.1465Z" fill="#BCCEFD"/>
|
||||
<path d="M16 10C16 10.3167 15.8749 10.6335 15.6247 10.8189L10.1706 14.8624C9.65543 15.2444 9 14.7858 9 14.0435V5.95652C9 5.21417 9.65543 4.75564 10.1706 5.13758L15.6247 9.18106C15.8749 9.36653 16 9.68326 16 10Z" fill="gray"/>
|
||||
<path d="M0 10C0 8.89543 0.895431 8 2 8H10C11.1046 8 12 8.89543 12 10C12 11.1046 11.1046 12 10 12H2C0.895431 12 0 11.1046 0 10Z" fill="gray"/>
|
||||
</svg>
|
After Width: | Height: | Size: 794 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5175 11.1465C16.8392 10.8869 17 10.4434 17 10C17 9.55657 16.8392 9.11314 16.5175 8.85348L12.5425 5.64459C13.2682 5.23422 14.1067 5 15 5C17.7614 5 20 7.23858 20 10C20 12.7614 17.7614 15 15 15C14.1067 15 13.2682 14.7658 12.5425 14.3554L16.5175 11.1465Z" fill="#BCCEFD"/>
|
||||
<path d="M16 10C16 10.3167 15.8749 10.6335 15.6247 10.8189L10.1706 14.8624C9.65543 15.2444 9 14.7858 9 14.0435V5.95652C9 5.21417 9.65543 4.75564 10.1706 5.13758L15.6247 9.18106C15.8749 9.36653 16 9.68326 16 10Z" fill="#27AE60"/>
|
||||
<path d="M0 10C0 8.89543 0.895431 8 2 8H10C11.1046 8 12 8.89543 12 10C12 11.1046 11.1046 12 10 12H2C0.895431 12 0 11.1046 0 10Z" fill="#27AE60"/>
|
||||
</svg>
|
After Width: | Height: | Size: 800 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 15C17.7614 15 20 12.7615 20 10C20 7.23861 17.7614 5.00003 15 5.00003C13.3642 5.00003 11.9118 5.78558 10.9996 7.00003H14C15.6569 7.00003 17 8.34318 17 10C17 11.6569 15.6569 13 14 13H10.9996C11.9118 14.2145 13.3642 15 15 15Z" fill="#BCCEFD"/>
|
||||
<rect x="4" y="8.00003" width="12" height="4" rx="2" fill="#EB5757"/>
|
||||
<path d="M5.96244e-08 10C6.34015e-08 9.68329 0.125088 9.36656 0.375266 9.18109L5.82939 5.13761C6.34457 4.75567 7 5.2142 7 5.95655L7 14.0435C7 14.7859 6.34457 15.2444 5.82939 14.8625L0.375266 10.819C0.125088 10.6335 5.58474e-08 10.3168 5.96244e-08 10Z" fill="#EB5757"/>
|
||||
</svg>
|
After Width: | Height: | Size: 736 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 15C17.7614 15 20 12.7615 20 10C20 7.23861 17.7614 5.00003 15 5.00003C13.3642 5.00003 11.9118 5.78558 10.9996 7.00003H14C15.6569 7.00003 17 8.34318 17 10C17 11.6569 15.6569 13 14 13H10.9996C11.9118 14.2145 13.3642 15 15 15Z" fill="#BCCEFD"/>
|
||||
<rect x="4" y="8.00003" width="12" height="4" rx="2" fill="gray"/>
|
||||
<path d="M5.96244e-08 10C6.34015e-08 9.68329 0.125088 9.36656 0.375266 9.18109L5.82939 5.13761C6.34457 4.75567 7 5.2142 7 5.95655L7 14.0435C7 14.7859 6.34457 15.2444 5.82939 14.8625L0.375266 10.819C0.125088 10.6335 5.58474e-08 10.3168 5.96244e-08 10Z" fill="gray"/>
|
||||
</svg>
|
After Width: | Height: | Size: 730 B |
@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 15C17.7614 15 20 12.7615 20 10C20 7.23861 17.7614 5.00003 15 5.00003C13.3642 5.00003 11.9118 5.78558 10.9996 7.00003H14C15.6569 7.00003 17 8.34318 17 10C17 11.6569 15.6569 13 14 13H10.9996C11.9118 14.2145 13.3642 15 15 15Z" fill="#BCCEFD"/>
|
||||
<rect x="4" y="8.00003" width="12" height="4" rx="2" fill="#27AE60"/>
|
||||
<path d="M5.96244e-08 10C6.34015e-08 9.68329 0.125088 9.36656 0.375266 9.18109L5.82939 5.13761C6.34457 4.75567 7 5.2142 7 5.95655L7 14.0435C7 14.7859 6.34457 15.2444 5.82939 14.8625L0.375266 10.819C0.125088 10.6335 5.58474e-08 10.3168 5.96244e-08 10Z" fill="#27AE60"/>
|
||||
</svg>
|
After Width: | Height: | Size: 736 B |
@ -1,28 +1,28 @@
|
||||
import React, {useRef, useState} from "react";
|
||||
import styles from './style/Filters.module.sass';
|
||||
import styles from '../style/Filters.module.sass';
|
||||
import {Button, Grid, Modal, Box, Typography, Backdrop, Fade, Divider} from "@material-ui/core";
|
||||
import CodeEditor from '@uiw/react-textarea-code-editor';
|
||||
import MenuBookIcon from '@material-ui/icons/MenuBook';
|
||||
import {SyntaxHighlighter} from "./UI/SyntaxHighlighter/index";
|
||||
import filterUIExample1 from "./assets/filter-ui-example-1.png"
|
||||
import filterUIExample2 from "./assets/filter-ui-example-2.png"
|
||||
import variables from '../variables.module.scss';
|
||||
import {useRecoilState} from "recoil";
|
||||
import queryAtom from "../recoil/query";
|
||||
import useKeyPress from "../hooks/useKeyPress"
|
||||
import shortcutsKeyboard from "../configs/shortcutsKeyboard"
|
||||
import {SyntaxHighlighter} from "../UI/SyntaxHighlighter/index";
|
||||
import filterUIExample1 from "assets/filter-ui-example-1.png"
|
||||
import filterUIExample2 from "assets/filter-ui-example-2.png"
|
||||
import variables from '../../variables.module.scss';
|
||||
import {useRecoilState, useRecoilValue} from "recoil";
|
||||
import queryAtom from "../../recoil/query";
|
||||
import useKeyPress from "../../hooks/useKeyPress"
|
||||
import shortcutsKeyboard from "../../configs/shortcutsKeyboard"
|
||||
import trafficViewerApiAtom from "../../recoil/TrafficViewerApi"
|
||||
|
||||
|
||||
interface FiltersProps {
|
||||
backgroundColor: string
|
||||
ws: any
|
||||
openWebSocket: (query: string, resetEntries: boolean) => void;
|
||||
}
|
||||
|
||||
export const Filters: React.FC<FiltersProps> = ({backgroundColor, ws, openWebSocket}) => {
|
||||
export const Filters: React.FC<FiltersProps> = ({backgroundColor, openWebSocket}) => {
|
||||
return <div className={styles.container}>
|
||||
<QueryForm
|
||||
backgroundColor={backgroundColor}
|
||||
ws={ws}
|
||||
openWebSocket={openWebSocket}
|
||||
/>
|
||||
</div>;
|
||||
@ -30,7 +30,6 @@ export const Filters: React.FC<FiltersProps> = ({backgroundColor, ws, openWebSoc
|
||||
|
||||
interface QueryFormProps {
|
||||
backgroundColor: string
|
||||
ws: any
|
||||
openWebSocket: (query: string, resetEntries: boolean) => void;
|
||||
}
|
||||
|
||||
@ -48,10 +47,11 @@ export const modalStyle = {
|
||||
color: '#000',
|
||||
};
|
||||
|
||||
export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, ws, openWebSocket}) => {
|
||||
export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, openWebSocket}) => {
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [query, setQuery] = useRecoilState(queryAtom);
|
||||
const trafficViewerApi = useRecoilValue(trafficViewerApiAtom)
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
@ -63,7 +63,7 @@ export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, ws, openWe
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
ws.close();
|
||||
trafficViewerApi.webSocket.close()
|
||||
if (query) {
|
||||
openWebSocket(`(${query}) and leftOff(-1)`, true);
|
||||
} else {
|
||||
@ -74,7 +74,7 @@ export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, ws, openWe
|
||||
|
||||
useKeyPress(shortcutsKeyboard.ctrlEnter, handleSubmit, formRef.current);
|
||||
|
||||
return <>
|
||||
return <React.Fragment>
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleSubmit}
|
||||
@ -314,5 +314,5 @@ export const QueryForm: React.FC<QueryFormProps> = ({backgroundColor, ws, openWe
|
||||
</Box>
|
||||
</Fade>
|
||||
</Modal>
|
||||
</>
|
||||
</React.Fragment>
|
||||
}
|
@ -19,28 +19,22 @@
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
.TrafficPage-Header
|
||||
display: flex
|
||||
height: 2.5%
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
padding: 18px 15px
|
||||
|
||||
.TrafficPage-Header-Image
|
||||
.TrafficPageHeaderImage
|
||||
width: 22px
|
||||
height: 22px
|
||||
|
||||
.TrafficPage-Header-Text
|
||||
.TrafficPageHeaderText
|
||||
margin-left: 10px
|
||||
font-family: 'Source Sans Pro', serif
|
||||
font-size: 14px
|
||||
font-weight: bold
|
||||
color: #f7f9fc
|
||||
|
||||
.TrafficPage-Header-Actions
|
||||
.TrafficPageHeaderActions
|
||||
margin-left: auto
|
||||
|
||||
.TrafficPage-Header-Actions-Image
|
||||
.TrafficPageHeaderActionsImage
|
||||
width: 22px
|
||||
height: 22px
|
||||
cursor: pointer
|
||||
@ -48,7 +42,7 @@
|
||||
margin-left: auto
|
||||
transform: translate(0 ,25%)
|
||||
|
||||
.TrafficPage-Viewer
|
||||
.TrafficPageViewer
|
||||
height: 96.5%
|
||||
overflow: auto
|
||||
|
||||
@ -63,20 +57,20 @@
|
||||
height: calc(100% - 60px)
|
||||
overflow: scroll
|
||||
|
||||
.TrafficPage-Container
|
||||
.TrafficPageContainer
|
||||
display: flex
|
||||
flex-grow: 1
|
||||
overflow: hidden
|
||||
background-color: $data-background-color
|
||||
|
||||
.TrafficPage-ListContainer
|
||||
.TrafficPageListContainer
|
||||
display: flex
|
||||
flex-grow: 1
|
||||
overflow: hidden
|
||||
padding-left: 24px
|
||||
flex-direction: column
|
||||
|
||||
.TrafficPage-DetailContainer
|
||||
.TrafficPageDetailContainer
|
||||
width: 45vw
|
||||
background-color: #171c30
|
||||
flex: 0 0 50%
|
||||
@ -119,4 +113,4 @@
|
||||
.playPauseIcon
|
||||
cursor: pointer
|
||||
margin-right: 15px
|
||||
height: 30px
|
||||
height: 30px
|
345
ui-common/src/components/TrafficViewer/TrafficViewer.tsx
Normal file
@ -0,0 +1,345 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Filters } from "./Filters";
|
||||
import { EntriesList } from "./EntriesList";
|
||||
import { makeStyles } from "@material-ui/core";
|
||||
import TrafficViewerStyles from "./TrafficViewer.module.sass";
|
||||
import styles from '../style/EntriesList.module.sass';
|
||||
import { EntryDetailed } from "./EntryDetailed";
|
||||
import playIcon from 'assets/run.svg';
|
||||
import pauseIcon from 'assets/pause.svg';
|
||||
import variables from '../../variables.module.scss';
|
||||
import { toast } from 'react-toastify';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { RecoilRoot, RecoilState, useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
|
||||
import entriesAtom from "../../recoil/entries";
|
||||
import focusedEntryIdAtom from "../../recoil/focusedEntryId";
|
||||
import websocketConnectionAtom, { WsConnectionStatus } from "../../recoil/wsConnection";
|
||||
import queryAtom from "../../recoil/query";
|
||||
import { TLSWarning } from "../TLSWarning/TLSWarning";
|
||||
import trafficViewerApiAtom from "../../recoil/TrafficViewerApi"
|
||||
import TrafficViewerApi from "./TrafficViewerApi";
|
||||
import { StatusBar } from "../UI/StatusBar";
|
||||
import tappingStatusAtom from "../../recoil/tappingStatus/atom";
|
||||
|
||||
|
||||
const useLayoutStyles = makeStyles(() => ({
|
||||
details: {
|
||||
flex: "0 0 50%",
|
||||
width: "45vw",
|
||||
padding: "12px 24px",
|
||||
borderRadius: 4,
|
||||
marginTop: 15,
|
||||
background: variables.headerBackgroundColor,
|
||||
},
|
||||
|
||||
viewer: {
|
||||
display: "flex",
|
||||
overflowY: "auto",
|
||||
height: "calc(100% - 70px)",
|
||||
padding: 5,
|
||||
paddingBottom: 0,
|
||||
overflow: "auto",
|
||||
},
|
||||
}));
|
||||
|
||||
interface TrafficViewerProps {
|
||||
setAnalyzeStatus?: (status: any) => void;
|
||||
api?: any
|
||||
message?: {}
|
||||
error?: {}
|
||||
isWebSocketOpen: boolean
|
||||
trafficViewerApiProp: TrafficViewerApi,
|
||||
actionButtons?: JSX.Element,
|
||||
isShowStatusBar?: boolean
|
||||
}
|
||||
|
||||
const TrafficViewer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus, message, error, isWebSocketOpen, trafficViewerApiProp, actionButtons, isShowStatusBar }) => {
|
||||
const classes = useLayoutStyles();
|
||||
|
||||
const [entries, setEntries] = useRecoilState(entriesAtom);
|
||||
const [focusedEntryId, setFocusedEntryId] = useRecoilState(focusedEntryIdAtom);
|
||||
const [wsConnection, setWsConnection] = useRecoilState(websocketConnectionAtom);
|
||||
const query = useRecoilValue(queryAtom);
|
||||
const [queryToSend, setQueryToSend] = useState("")
|
||||
const setTrafficViewerApiState = useSetRecoilState(trafficViewerApiAtom as RecoilState<TrafficViewerApi>)
|
||||
const [tappingStatus, setTappingStatus] = useRecoilState(tappingStatusAtom);
|
||||
|
||||
|
||||
const [noMoreDataTop, setNoMoreDataTop] = useState(false);
|
||||
const [isSnappedToBottom, setIsSnappedToBottom] = useState(true);
|
||||
|
||||
const [queryBackgroundColor, setQueryBackgroundColor] = useState("#f5f5f5");
|
||||
|
||||
const [queriedCurrent, setQueriedCurrent] = useState(0);
|
||||
const [queriedTotal, setQueriedTotal] = useState(0);
|
||||
const [leftOffBottom, setLeftOffBottom] = useState(0);
|
||||
const [leftOffTop, setLeftOffTop] = useState(null);
|
||||
const [truncatedTimestamp, setTruncatedTimestamp] = useState(0);
|
||||
|
||||
const [startTime, setStartTime] = useState(0);
|
||||
const scrollableRef = useRef(null);
|
||||
|
||||
const [showTLSWarning, setShowTLSWarning] = useState(false);
|
||||
const [userDismissedTLSWarning, setUserDismissedTLSWarning] = useState(false);
|
||||
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set<string>());
|
||||
|
||||
const handleQueryChange = useMemo(
|
||||
() =>
|
||||
debounce(async (query: string) => {
|
||||
if (!query) {
|
||||
setQueryBackgroundColor("#f5f5f5");
|
||||
} else {
|
||||
const data = await trafficViewerApiProp.validateQuery(query);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
if (data.valid) {
|
||||
setQueryBackgroundColor("#d2fad2");
|
||||
} else {
|
||||
setQueryBackgroundColor("#fad6dc");
|
||||
}
|
||||
}
|
||||
}, 500),
|
||||
[]
|
||||
) as (query: string) => void;
|
||||
|
||||
useEffect(() => {
|
||||
handleQueryChange(query);
|
||||
}, [query, handleQueryChange]);
|
||||
|
||||
|
||||
const listEntry = useRef(null);
|
||||
const openWebSocket = (query: string, resetEntries: boolean) => {
|
||||
if (resetEntries) {
|
||||
setFocusedEntryId(null);
|
||||
setEntries([]);
|
||||
setQueriedCurrent(0);
|
||||
setLeftOffTop(null);
|
||||
setNoMoreDataTop(false);
|
||||
}
|
||||
setQueryToSend(query)
|
||||
trafficViewerApiProp.webSocket.open();
|
||||
}
|
||||
|
||||
const onmessage = useCallback((e) => {
|
||||
if (!e?.data) return;
|
||||
const message = JSON.parse(e.data);
|
||||
switch (message.messageType) {
|
||||
case "entry":
|
||||
const entry = message.data;
|
||||
if (!focusedEntryId) setFocusedEntryId(entry.id.toString());
|
||||
const newEntries = [...entries, entry];
|
||||
if (newEntries.length === 10001) {
|
||||
setLeftOffTop(newEntries[0].entry.id);
|
||||
newEntries.shift();
|
||||
setNoMoreDataTop(false);
|
||||
}
|
||||
setEntries(newEntries);
|
||||
break;
|
||||
case "status":
|
||||
setTappingStatus(message.tappingStatus);
|
||||
break;
|
||||
case "analyzeStatus":
|
||||
setAnalyzeStatus(message.analyzeStatus);
|
||||
break;
|
||||
case "outboundLink":
|
||||
onTLSDetected(message.Data.DstIP);
|
||||
break;
|
||||
case "toast":
|
||||
toast[message.data.type](message.data.text, {
|
||||
position: "bottom-right",
|
||||
theme: "colored",
|
||||
autoClose: message.data.autoClose,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
});
|
||||
break;
|
||||
case "queryMetadata":
|
||||
setQueriedCurrent(queriedCurrent + message.data.current);
|
||||
setQueriedTotal(message.data.total);
|
||||
setLeftOffBottom(message.data.leftOff);
|
||||
setTruncatedTimestamp(message.data.truncatedTimestamp);
|
||||
if (leftOffTop === null) {
|
||||
setLeftOffTop(message.data.leftOff - 1);
|
||||
}
|
||||
break;
|
||||
case "startTime":
|
||||
setStartTime(message.data);
|
||||
break;
|
||||
default:
|
||||
console.error(
|
||||
`unsupported websocket message type, Got: ${message.messageType}`
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [message]);
|
||||
|
||||
useEffect(() => {
|
||||
onmessage(message)
|
||||
}, [message, onmessage])
|
||||
|
||||
useEffect(() => {
|
||||
onerror(error)
|
||||
}, [error])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
isWebSocketOpen ? setWsConnection(WsConnectionStatus.Connected) : setWsConnection(WsConnectionStatus.Closed)
|
||||
trafficViewerApiProp.webSocket.sendQuery(queryToSend)
|
||||
}, [isWebSocketOpen, queryToSend, setWsConnection])
|
||||
|
||||
const onerror = (event) => {
|
||||
console.error("WebSocket error:", event);
|
||||
if (query) {
|
||||
openWebSocket(`(${query}) and leftOff(${leftOffBottom})`, false);
|
||||
} else {
|
||||
openWebSocket(`leftOff(${leftOffBottom})`, false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
setTrafficViewerApiState(trafficViewerApiProp)
|
||||
openWebSocket("leftOff(-1)", true);
|
||||
try {
|
||||
const tapStatusResponse = await trafficViewerApiProp.tapStatus();
|
||||
setTappingStatus(tapStatusResponse);
|
||||
if (setAnalyzeStatus) {
|
||||
const analyzeStatusResponse = await trafficViewerApiProp.analyzeStatus();
|
||||
setAnalyzeStatus(analyzeStatusResponse);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})()
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const toggleConnection = () => {
|
||||
if (wsConnection === WsConnectionStatus.Closed) {
|
||||
|
||||
if (query) {
|
||||
openWebSocket(`(${query}) and leftOff(-1)`, true);
|
||||
} else {
|
||||
openWebSocket(`leftOff(-1)`, true);
|
||||
}
|
||||
scrollableRef.current.jumpToBottom();
|
||||
setIsSnappedToBottom(true);
|
||||
}
|
||||
else if (wsConnection === WsConnectionStatus.Connected) {
|
||||
trafficViewerApiProp.webSocket.close()
|
||||
setWsConnection(WsConnectionStatus.Closed);
|
||||
}
|
||||
}
|
||||
|
||||
const onTLSDetected = (destAddress: string) => {
|
||||
addressesWithTLS.add(destAddress);
|
||||
setAddressesWithTLS(new Set(addressesWithTLS));
|
||||
|
||||
if (!userDismissedTLSWarning) {
|
||||
setShowTLSWarning(true);
|
||||
}
|
||||
};
|
||||
|
||||
const getConnectionIndicator = () => {
|
||||
switch (wsConnection) {
|
||||
case WsConnectionStatus.Connected:
|
||||
return <div className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.greenIndicatorContainer}`}>
|
||||
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.greenIndicator}`} />
|
||||
</div>
|
||||
default:
|
||||
return <div className={`${TrafficViewerStyles.indicatorContainer} ${TrafficViewerStyles.redIndicatorContainer}`}>
|
||||
<div className={`${TrafficViewerStyles.indicator} ${TrafficViewerStyles.redIndicator}`} />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
const getConnectionTitle = () => {
|
||||
switch (wsConnection) {
|
||||
case WsConnectionStatus.Connected:
|
||||
return "streaming live traffic"
|
||||
default:
|
||||
return "streaming paused";
|
||||
}
|
||||
}
|
||||
|
||||
const onSnapBrokenEvent = () => {
|
||||
setIsSnappedToBottom(false);
|
||||
if (wsConnection === WsConnectionStatus.Connected) {
|
||||
trafficViewerApiProp.webSocket.close()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={TrafficViewerStyles.TrafficPage}>
|
||||
{tappingStatus && isShowStatusBar && <StatusBar />}
|
||||
<div className={TrafficViewerStyles.TrafficPageHeader}>
|
||||
<div className={TrafficViewerStyles.TrafficPageStreamStatus}>
|
||||
<img className={TrafficViewerStyles.playPauseIcon} style={{ visibility: wsConnection === WsConnectionStatus.Connected ? "visible" : "hidden" }} alt="pause"
|
||||
src={pauseIcon} onClick={toggleConnection} />
|
||||
<img className={TrafficViewerStyles.playPauseIcon} style={{ position: "absolute", visibility: wsConnection === WsConnectionStatus.Connected ? "hidden" : "visible" }} alt="play"
|
||||
src={playIcon} onClick={toggleConnection} />
|
||||
<div className={TrafficViewerStyles.connectionText}>
|
||||
{getConnectionTitle()}
|
||||
{getConnectionIndicator()}
|
||||
</div>
|
||||
</div>
|
||||
{actionButtons}
|
||||
</div>
|
||||
{<div className={TrafficViewerStyles.TrafficPageContainer}>
|
||||
<div className={TrafficViewerStyles.TrafficPageListContainer}>
|
||||
<Filters
|
||||
backgroundColor={queryBackgroundColor}
|
||||
openWebSocket={openWebSocket}
|
||||
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
<EntriesList
|
||||
listEntryREF={listEntry}
|
||||
onSnapBrokenEvent={onSnapBrokenEvent}
|
||||
isSnappedToBottom={isSnappedToBottom}
|
||||
setIsSnappedToBottom={setIsSnappedToBottom}
|
||||
queriedCurrent={queriedCurrent}
|
||||
setQueriedCurrent={setQueriedCurrent}
|
||||
queriedTotal={queriedTotal}
|
||||
setQueriedTotal={setQueriedTotal}
|
||||
startTime={startTime}
|
||||
noMoreDataTop={noMoreDataTop}
|
||||
setNoMoreDataTop={setNoMoreDataTop}
|
||||
leftOffTop={leftOffTop}
|
||||
setLeftOffTop={setLeftOffTop}
|
||||
openWebSocket={openWebSocket}
|
||||
leftOffBottom={leftOffBottom}
|
||||
truncatedTimestamp={truncatedTimestamp}
|
||||
setTruncatedTimestamp={setTruncatedTimestamp}
|
||||
scrollableRef={scrollableRef}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.details} id="rightSideContainer">
|
||||
{focusedEntryId && <EntryDetailed />}
|
||||
</div>
|
||||
</div>}
|
||||
<TLSWarning showTLSWarning={showTLSWarning}
|
||||
setShowTLSWarning={setShowTLSWarning}
|
||||
addressesWithTLS={addressesWithTLS}
|
||||
setAddressesWithTLS={setAddressesWithTLS}
|
||||
userDismissedTLSWarning={userDismissedTLSWarning}
|
||||
setUserDismissedTLSWarning={setUserDismissedTLSWarning} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoiedTrafficViewer = React.memo(TrafficViewer)
|
||||
const TrafficViewerContainer: React.FC<TrafficViewerProps> = ({ setAnalyzeStatus, message, isWebSocketOpen, trafficViewerApiProp, actionButtons, isShowStatusBar = true }) => {
|
||||
return <RecoilRoot>
|
||||
<MemoiedTrafficViewer message={message} isWebSocketOpen={isWebSocketOpen} actionButtons={actionButtons} isShowStatusBar={isShowStatusBar}
|
||||
trafficViewerApiProp={trafficViewerApiProp} setAnalyzeStatus={setAnalyzeStatus} />
|
||||
</RecoilRoot>
|
||||
}
|
||||
|
||||
export default TrafficViewerContainer
|
15
ui-common/src/components/TrafficViewer/TrafficViewerApi.ts
Normal file
@ -0,0 +1,15 @@
|
||||
type TrafficViewerApi = {
|
||||
validateQuery : (query: any) => any
|
||||
tapStatus : () => any
|
||||
analyzeStatus : () => any
|
||||
fetchEntries : (leftOff: any, direction: number, query: any, limit: number, timeoutMs: number) => any
|
||||
getEntry : (entryId : any, query:string) => any
|
||||
getRecentTLSLinks : () => any,
|
||||
webSocket : {
|
||||
open : () => {},
|
||||
close : () => {},
|
||||
sendQuery : (query:string) => {}
|
||||
}
|
||||
}
|
||||
|
||||
export default TrafficViewerApi
|
@ -0,0 +1,3 @@
|
||||
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 2.82846L7.82843 3.6478e-05L9.24264 1.41425L5 5.65689L4.99997 5.65686L3.58579 4.24268L0.75733 1.41422L2.17154 5.00679e-06L5 2.82846Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 301 B |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 24 KiB |
5
ui-common/src/components/TrafficViewer/assets/pause.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="30" height="30" rx="15" fill="#F7B202"/>
|
||||
<rect x="11" y="9" width="2" height="11" rx="1" fill="white"/>
|
||||
<rect x="17" y="9" width="2" height="11" rx="1" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 283 B |
4
ui-common/src/components/TrafficViewer/assets/run.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="13.5" stroke="#205CF5" stroke-width="3"/>
|
||||
<path d="M20 15C20 15.3167 19.8392 15.6335 19.5175 15.8189L12.5051 19.8624C11.8427 20.2444 11 19.7858 11 19.0435V10.9565C11 10.2142 11.8427 9.75564 12.5051 10.1376L19.5175 14.1811C19.8392 14.3665 20 14.6833 20 15Z" fill="#205CF5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 404 B |
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||
<circle cx="50" cy="50" fill="none" stroke="#1d3f72" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138" transform="rotate(275.903 50 50)">
|
||||
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform>
|
||||
</circle>
|
||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
After Width: | Height: | Size: 673 B |
18
ui-common/src/components/UI/Checkbox.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
export interface Props {
|
||||
checked: boolean;
|
||||
onToggle: (checked:boolean) => any;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const Checkbox: React.FC<Props> = ({checked, onToggle, disabled}) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input style={!disabled ? {cursor: "pointer"}: {}} type="checkbox" checked={checked} disabled={disabled} onChange={(event) => onToggle(event.target.checked)}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Checkbox;
|
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import collapsedImg from "../assets/collapsed.svg";
|
||||
import expandedImg from "../assets/expanded.svg";
|
||||
import "./style/CollapsibleContainer.sass";
|
||||
import collapsedImg from "assets/collapsed.svg";
|
||||
import expandedImg from "assets/expanded.svg";
|
||||
import styles from "./style/CollapsibleContainer.module.sass";
|
||||
|
||||
interface Props {
|
||||
title: string | React.ReactNode,
|
||||
@ -12,22 +12,21 @@ interface Props {
|
||||
}
|
||||
|
||||
const CollapsibleContainer: React.FC<Props> = ({title, children, expanded, titleClassName, className, stickyHeader = false}) => {
|
||||
const classNames = `CollapsibleContainer ${expanded ? "CollapsibleContainer-Expanded" : "CollapsibleContainer-Collapsed"} ${className ? className : ''}`;
|
||||
const classNames = `${styles.CollapsibleContainer} ${expanded ? `${styles.CollapsibleContainerExpanded}` : `${styles.CollapsibleContainerCollapsed}`} ${className ? className : ''}`;
|
||||
|
||||
// This is needed to achieve the sticky header feature.
|
||||
// It is needed an un-contained component for the css to work properly.
|
||||
const content = <React.Fragment>
|
||||
<div
|
||||
className={`CollapsibleContainer-Header ${stickyHeader ? "CollapsibleContainer-Header-Sticky" : ""}
|
||||
${expanded ? "CollapsibleContainer-Header-Expanded" : ""}`}
|
||||
>
|
||||
className={`${styles.CollapsibleContainerHeader} ${stickyHeader ? `${styles.CollapsibleContainerHeaderSticky}` : ""}
|
||||
${expanded ? `${styles.CollapsibleContainerHeaderExpanded}` : ""}`}>
|
||||
{
|
||||
React.isValidElement(title)?
|
||||
<React.Fragment>{title}</React.Fragment> :
|
||||
<React.Fragment>
|
||||
<div className={`CollapsibleContainer-Title ${titleClassName ? titleClassName : ''}`}>{title}</div>
|
||||
<div className={`${styles.CollapsibleContainerTitle} ${titleClassName ? titleClassName : ''}`}>{title}</div>
|
||||
<img
|
||||
className="CollapsibleContainer-ExpandCollapseButton"
|
||||
className={styles.CollapsibleContainerExpandCollapseButton}
|
||||
src={expanded ? expandedImg : collapsedImg}
|
||||
alt="Expand/Collapse Button"
|
||||
/>
|
61
ui-common/src/components/UI/CustomModal.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React from 'react';
|
||||
import { makeStyles, Modal, Backdrop, Fade, Box } from '@material-ui/core';
|
||||
import {useCommonStyles} from "../../helpers/commonStyle";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
modal: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
},
|
||||
modalContents: {
|
||||
borderRadius: "5px",
|
||||
outline: "none",
|
||||
minWidth: "300px",
|
||||
backgroundColor: "rgb(255, 255, 255)",
|
||||
},
|
||||
modalBackdrop :{
|
||||
background : "rgba(24, 51, 121, 0.8)"
|
||||
}
|
||||
});
|
||||
|
||||
export interface CustomModalProps {
|
||||
open: boolean;
|
||||
children: React.ReactElement | React.ReactElement[];
|
||||
disableBackdropClick?: boolean;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
isPadding?: boolean;
|
||||
isWide? :boolean;
|
||||
}
|
||||
|
||||
const CustomModal: React.FunctionComponent<CustomModalProps> = ({ open = false, onClose, disableBackdropClick = false, children, className}) => {
|
||||
const classes = useStyles({});
|
||||
const globals = useCommonStyles().modal
|
||||
|
||||
|
||||
const onModalClose = (reason) => {
|
||||
if(reason === 'backdropClick' && disableBackdropClick)
|
||||
return;
|
||||
onClose();
|
||||
}
|
||||
|
||||
return <Modal disableEnforceFocus open={open} onClose={(event, reason) => onModalClose(reason)}
|
||||
className={`${classes.modal}`}
|
||||
closeAfterTransition
|
||||
BackdropComponent={Backdrop}
|
||||
BackdropProps={{
|
||||
timeout: 500,
|
||||
className:`${classes.modalBackdrop}`
|
||||
}}>
|
||||
<div className={`${classes.modalContents} ${globals} ${className ? className : ''}`} >
|
||||
<Fade in={open}>
|
||||
<Box>
|
||||
{children}
|
||||
</Box>
|
||||
</Fade>
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
|
||||
export default CustomModal;
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import duplicateImg from "../assets/duplicate.svg";
|
||||
import './style/FancyTextDisplay.sass';
|
||||
import duplicateImg from "assets/duplicate.svg";
|
||||
import styles from './style/FancyTextDisplay.module.sass';
|
||||
|
||||
interface Props {
|
||||
text: string | number,
|
||||
@ -33,21 +33,21 @@ const FancyTextDisplay: React.FC<Props> = ({text, className, isPossibleToCopy =
|
||||
return () => clearTimeout(timer);
|
||||
}, [showCopiedNotification]);
|
||||
|
||||
const textElement = <span className={'FancyTextDisplay-Text'}>{text}</span>;
|
||||
const textElement = <span className={styles.FancyTextDisplayText}>{text}</span>;
|
||||
|
||||
const copyButton = isPossibleToCopy && text ? <CopyToClipboard text={text} onCopy={onCopy}>
|
||||
<span
|
||||
className={`FancyTextDisplay-Icon`}
|
||||
className={styles.FancyTextDisplayIcon}
|
||||
title={`Copy "${text}" value to clipboard`}
|
||||
>
|
||||
<img src={duplicateImg} alt="Duplicate full value"/>
|
||||
{showCopiedNotification && <span className={'FancyTextDisplay-CopyNotifier'}>Copied</span>}
|
||||
{showCopiedNotification && <span className={styles.FancyTextDisplayCopyNotifier}>Copied</span>}
|
||||
</span>
|
||||
</CopyToClipboard> : null;
|
||||
|
||||
return (
|
||||
<p
|
||||
className={`FancyTextDisplay-Container ${className ? className : ''} ${displayIconOnMouseOver ? 'displayIconOnMouseOver ' : ''} ${applyTextEllipsis ? ' FancyTextDisplay-ContainerEllipsis' : ''}`}
|
||||
className={`${styles.FancyTextDisplayContainer} ${className ? className : ''} ${displayIconOnMouseOver ? ` ${styles.displayIconOnMouseOver} ` : ''} ${applyTextEllipsis ? ` ${styles.FancyTextDisplayContainerEllipsis} `: ''}`}
|
||||
title={text}
|
||||
onMouseOver={ e => setShowTooltip(true)}
|
||||
onMouseLeave={ e => setShowTooltip(false)}
|
||||
@ -55,7 +55,7 @@ const FancyTextDisplay: React.FC<Props> = ({text, className, isPossibleToCopy =
|
||||
{!buttonOnly && flipped && textElement}
|
||||
{copyButton}
|
||||
{!buttonOnly && !flipped && textElement}
|
||||
{useTooltip && showTooltip && <span className={'FancyTextDisplay-CopyNotifier'}>{text}</span>}
|
||||
{useTooltip && showTooltip && <span className={styles.FancyTextDisplayCopyNotifier}>{text}</span>}
|
||||
</p>
|
||||
);
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import React, {useEffect, useState} from "react";
|
||||
import './style/LoadingOverlay.sass';
|
||||
import style from '../style/LoadingOverlay.module.sass';
|
||||
|
||||
const SpinnerShowDelayMs = 350;
|
||||
|
||||
@ -18,13 +18,13 @@ const LoadingOverlay: React.FC<LoadingOverlayProps> = ({delay}) => {
|
||||
setTimeout(() => {
|
||||
if(isRelevant)
|
||||
setIsVisible(true);
|
||||
}, delay ?? SpinnerShowDelayMs);
|
||||
}, delay || SpinnerShowDelayMs);
|
||||
|
||||
return () => isRelevant = false;
|
||||
}, [delay]);
|
||||
|
||||
return <div className="loading-overlay-container" hidden={!isVisible}>
|
||||
<div className="loading-overlay-spinner"/>
|
||||
return <div className={style.loadingOverlayContainer} hidden={!isVisible}>
|
||||
<div className={style.loadingOverlaySpinner}/>
|
||||
</div>
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import AddCircleIcon from '@material-ui/icons/AddCircle';
|
||||
import './style/Queryable.sass';
|
||||
import QueryableStyle from './style/Queryable.module.sass';
|
||||
import {useRecoilState} from "recoil";
|
||||
import queryAtom from "../../recoil/query";
|
||||
|
||||
@ -39,26 +39,21 @@ const Queryable: React.FC<Props> = ({query, style, iconStyle, className, useTool
|
||||
|
||||
const addButton = query ? <CopyToClipboard text={query} onCopy={onCopy}>
|
||||
<span
|
||||
className={`Queryable-Icon`}
|
||||
className={QueryableStyle.QueryableIcon}
|
||||
title={`Add "${query}" to the filter`}
|
||||
style={iconStyle}
|
||||
>
|
||||
style={iconStyle}>
|
||||
<AddCircleIcon fontSize="small" color="inherit"/>
|
||||
{showAddedNotification && <span className={'Queryable-AddNotifier'}>Added</span>}
|
||||
{showAddedNotification && <span className={QueryableStyle.QueryableAddNotifier}>Added</span>}
|
||||
</span>
|
||||
</CopyToClipboard> : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`Queryable-Container displayIconOnMouseOver ${className ? className : ''} ${displayIconOnMouseOver ? 'displayIconOnMouseOver ' : ''}`}
|
||||
style={style}
|
||||
onMouseOver={ e => setShowTooltip(true)}
|
||||
onMouseLeave={ e => setShowTooltip(false)}
|
||||
>
|
||||
{flipped && addButton}
|
||||
{children}
|
||||
{!flipped && addButton}
|
||||
{useTooltip && showTooltip && <span className={'Queryable-Tooltip'}>{query}</span>}
|
||||
<div className={`${QueryableStyle.QueryableContainer} ${QueryableStyle.displayIconOnMouseOver} ${className ? className : ''} ${displayIconOnMouseOver ? QueryableStyle.displayIconOnMouseOver : ''}`}
|
||||
style={style} onMouseOver={ e => setShowTooltip(true)} onMouseLeave={ e => setShowTooltip(false)}>
|
||||
{flipped && addButton}
|
||||
{children}
|
||||
{!flipped && addButton}
|
||||
{useTooltip && showTooltip && <span className={QueryableStyle.QueryableTooltip}>{query}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import {ReactComponent as DefaultIconDown} from '../assets/default_icon_down.svg';
|
||||
import {ReactComponent as DefaultIconDown} from './assets/default_icon_down.svg';
|
||||
import {MenuItem, Select as MUISelect} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import {SelectProps as MUISelectProps} from '@material-ui/core/Select/Select';
|
||||
@ -27,20 +27,20 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
export interface SelectProps extends MUISelectProps {
|
||||
onChange: (string) => void;
|
||||
onChangeCb: (arg0: string) => void;
|
||||
value: string | string[];
|
||||
ellipsis?: boolean;
|
||||
labelOnTop?: boolean;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
trimItemsWhenMultiple?: boolean;
|
||||
transformDisplay?: (string) => string;
|
||||
transformDisplay?: (arg0: string) => string;
|
||||
}
|
||||
|
||||
export const Select: React.FC<SelectProps> = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
onChangeCb,
|
||||
transformDisplay,
|
||||
ellipsis = true,
|
||||
multiple,
|
||||
@ -53,9 +53,9 @@ export const Select: React.FC<SelectProps> = ({
|
||||
}) => {
|
||||
let _value = value;
|
||||
|
||||
const _onChange = (_, item) => {
|
||||
const _onChange = ( item: { props: { value: any; }; }) => {
|
||||
const value = item.props.value;
|
||||
value === ALL_KEY ? onChange(ALL_KEY) : onChange(value);
|
||||
value === ALL_KEY ? onChangeCb(ALL_KEY) : onChangeCb(value);
|
||||
}
|
||||
|
||||
if (multiple && (!_value || _value.length === 0)) _value = [ALL_KEY];
|
||||
@ -78,7 +78,7 @@ export const Select: React.FC<SelectProps> = ({
|
||||
value: _value,
|
||||
renderValue,
|
||||
multiple,
|
||||
onChange: _onChange,
|
||||
onChange: (e,data) => _onChange(data),
|
||||
})}
|
||||
>
|
||||
{multiple && <MenuItem key={ALL_KEY} value={ALL_KEY}>{transformItem(ALL_KEY)}</MenuItem>}
|
@ -1,8 +1,8 @@
|
||||
import './style/StatusBar.sass';
|
||||
import style from './style/StatusBar.module.sass';
|
||||
import React, {useState} from "react";
|
||||
import warningIcon from '../assets/warning_icon.svg';
|
||||
import failIcon from '../assets/failed.svg';
|
||||
import successIcon from '../assets/success.svg';
|
||||
import warningIcon from 'assets/warning_icon.svg';
|
||||
import failIcon from 'assets/failed.svg';
|
||||
import successIcon from 'assets/success.svg';
|
||||
import {useRecoilValue} from "recoil";
|
||||
import tappingStatusAtom, {tappingStatusDetails} from "../../recoil/tappingStatus";
|
||||
|
||||
@ -16,10 +16,10 @@ export const StatusBar = () => {
|
||||
const [expandedBar, setExpandedBar] = useState(false);
|
||||
const {uniqueNamespaces, amountOfPods, amountOfTappedPods, amountOfUntappedPods} = useRecoilValue(tappingStatusDetails);
|
||||
|
||||
return <div className={'statusBar' + (expandedBar ? ' expandedStatusBar' : "")} onMouseOver={() => setExpandedBar(true)} onMouseLeave={() => setExpandedBar(false)}>
|
||||
<div className="podsCount">
|
||||
{tappingStatus.some(pod => !pod.isTapped) && <img src={warningIcon} alt="warning"/>}
|
||||
<span className='pods-count-text'>
|
||||
return <div className={`${style.statusBar} ${(expandedBar ? `${style.expandedStatusBar}` : "")}`} onMouseOver={() => setExpandedBar(true)} onMouseLeave={() => setExpandedBar(false)}>
|
||||
<div className={style.podsCount}>
|
||||
{tappingStatus.some(pod => !pod.isTapped) && <img src={warningIcon} alt="warning"/>}
|
||||
<span className={style.podsCountText}>
|
||||
{`Tapping ${amountOfUntappedPods > 0 ? amountOfTappedPods + " / " + amountOfPods : amountOfPods} ${pluralize('pod', amountOfPods)} in ${pluralize('namespace', uniqueNamespaces.length)} ${uniqueNamespaces.join(", ")}`}
|
||||
</span>
|
||||
</div>
|
@ -9,7 +9,7 @@ export enum StatusCodeClassification {
|
||||
}
|
||||
|
||||
interface EntryProps {
|
||||
statusCode: number
|
||||
statusCode: number,
|
||||
statusQuery: string
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Tooltip from "./Tooltip";
|
||||
import React from "react";
|
||||
import {makeStyles} from '@material-ui/core/styles';
|
||||
import { makeStyles, Theme,createStyles } from '@material-ui/core/styles';
|
||||
import variables from '../../variables.module.scss';
|
||||
|
||||
interface Tab {
|
||||
tab: string,
|
||||
disabled?: boolean,
|
||||
@ -20,13 +21,12 @@ interface Props {
|
||||
dark?: boolean,
|
||||
}
|
||||
|
||||
const useTabsStyles = makeStyles((theme) => ({
|
||||
|
||||
const useTabsStyles = makeStyles((theme : Theme) => createStyles({
|
||||
root: {
|
||||
height: 40,
|
||||
paddingTop: 15
|
||||
},
|
||||
|
||||
|
||||
tab: {
|
||||
display: 'inline-block',
|
||||
textTransform: 'uppercase',
|
||||
@ -39,7 +39,7 @@ const useTabsStyles = makeStyles((theme) => ({
|
||||
},
|
||||
|
||||
active: {
|
||||
fontWeight: 600,
|
||||
fontWeight: 400,
|
||||
color: variables.fontColor,
|
||||
cursor: 'unset',
|
||||
borderBottom: "2px solid " + variables.fontColor,
|
||||
@ -67,7 +67,6 @@ const useTabsStyles = makeStyles((theme) => ({
|
||||
margin: '0 20px',
|
||||
cursor: 'unset',
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
|
@ -29,7 +29,7 @@ const Tooltip: React.FC<TooltipProps> = (props) => {
|
||||
|
||||
const classes = useStyles(props.variant);
|
||||
|
||||
const variant = props.variant ?? 'default';
|
||||
const variant = props.variant || 'default';
|
||||
|
||||
const backgroundClass = isSimple ? "" : "noBackground"
|
||||
|
||||
@ -41,7 +41,7 @@ const Tooltip: React.FC<TooltipProps> = (props) => {
|
||||
TransitionComponent={Fade}
|
||||
{..._props}
|
||||
>
|
||||
{props.children ?? <div/>}
|
||||
{props.children || <div/>}
|
||||
</MUITooltip>
|
||||
);
|
||||
};
|
14
ui-common/src/components/UI/assets/collapsed.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1{fill:#fff;stroke:#707070;opacity:0}.cls-2{fill:#627ef7}.cls-3{stroke:none}.cls-4{fill:none}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="icon_down_diagonal" transform="translate(-1304 -166)">
|
||||
<g id="Rectangle_924" class="cls-1" data-name="Rectangle 924">
|
||||
<path d="M0 0h22v22H0z" class="cls-3" transform="translate(1304 166)"/>
|
||||
<path d="M.5.5h21v21H.5z" class="cls-4" transform="translate(1304 166)"/>
|
||||
</g>
|
||||
<path id="icon_down" d="M5.117 0H3.07v3.07H0v2.047h5.117V0z" class="cls-2" transform="translate(1312.46 174.491)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 711 B |
3
ui-common/src/components/UI/assets/default_icon_down.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="7.237" height="7.237" viewBox="0 0 7.237 7.237" fill="white">
|
||||
<path id="icon_down" d="M5.117 0H3.07v3.07H0v2.047h5.117V0z" transform="rotate(45 1.809 4.367)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 218 B |
20
ui-common/src/components/UI/assets/duplicate.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1,.cls-2,.cls-5{fill:#fff}.cls-1{opacity:0}.cls-3,.cls-6{fill:none}.cls-3{stroke:#8f9bb2}.cls-4,.cls-5{stroke:none}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="duplicate" transform="rotate(180 11 11)">
|
||||
<path id="Rectangle_1845" d="M0 0h22v22H0z" class="cls-1" data-name="Rectangle 1845"/>
|
||||
<g id="Group_4275" data-name="Group 4275" transform="translate(4 4)">
|
||||
<g id="Subtraction_10" class="cls-2" data-name="Subtraction 10">
|
||||
<path d="M5 8.498H2c-.827 0-1.5-.673-1.5-1.5v-5c0-.827.673-1.5 1.5-1.5h.611v2.409c0 1.378 1.122 2.5 2.5 2.5h1.39v1.591c0 .827-.674 1.5-1.5 1.5z" class="cls-4" transform="translate(1 5.202)"/>
|
||||
<path d="M5 7.998a1 1 0 0 0 1-1V5.907h-.889c-1.654 0-3-1.346-3-3V.998H2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h3m0 1H2c-1.103 0-2-.897-2-2v-5c0-1.103.897-2 2-2H3.11v2.909c0 1.103.897 2 2 2h1.89v2.091c0 1.103-.898 2-2 2z" class="cls-5" transform="translate(1 5.202)"/>
|
||||
</g>
|
||||
<g id="Rectangle_680" class="cls-3" data-name="Rectangle 680" transform="translate(4)">
|
||||
<rect width="9" height="11" class="cls-4" rx="2"/>
|
||||
<rect width="8" height="10" x=".5" y=".5" class="cls-6" rx="1.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
3
ui-common/src/components/UI/assets/expanded.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2427 11.8285L14.0711 9.00003L15.4853 10.4142L11.2427 14.6569L11.2426 14.6569L9.82846 13.2427L7 10.4142L8.41421 9L11.2427 11.8285Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 302 B |
1
ui-common/src/components/UI/assets/failed.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><linearGradient id="wRKXFJsqHCxLE9yyOYHkza" x1="9.858" x2="38.142" y1="9.858" y2="38.142" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#wRKXFJsqHCxLE9yyOYHkza)" d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"/><path d="M33.192,28.95L28.243,24l4.95-4.95c0.781-0.781,0.781-2.047,0-2.828l-1.414-1.414 c-0.781-0.781-2.047-0.781-2.828,0L24,19.757l-4.95-4.95c-0.781-0.781-2.047-0.781-2.828,0l-1.414,1.414 c-0.781,0.781-0.781,2.047,0,2.828l4.95,4.95l-4.95,4.95c-0.781,0.781-0.781,2.047,0,2.828l1.414,1.414 c0.781,0.781,2.047,0.781,2.828,0l4.95-4.95l4.95,4.95c0.781,0.781,2.047,0.781,2.828,0l1.414-1.414 C33.973,30.997,33.973,29.731,33.192,28.95z" opacity=".05"/><path d="M32.839,29.303L27.536,24l5.303-5.303c0.586-0.586,0.586-1.536,0-2.121l-1.414-1.414 c-0.586-0.586-1.536-0.586-2.121,0L24,20.464l-5.303-5.303c-0.586-0.586-1.536-0.586-2.121,0l-1.414,1.414 c-0.586,0.586-0.586,1.536,0,2.121L20.464,24l-5.303,5.303c-0.586,0.586-0.586,1.536,0,2.121l1.414,1.414 c0.586,0.586,1.536,0.586,2.121,0L24,27.536l5.303,5.303c0.586,0.586,1.536,0.586,2.121,0l1.414-1.414 C33.425,30.839,33.425,29.889,32.839,29.303z" opacity=".07"/><path fill="#fff" d="M31.071,15.515l1.414,1.414c0.391,0.391,0.391,1.024,0,1.414L18.343,32.485 c-0.391,0.391-1.024,0.391-1.414,0l-1.414-1.414c-0.391-0.391-0.391-1.024,0-1.414l14.142-14.142 C30.047,15.124,30.681,15.124,31.071,15.515z"/><path fill="#fff" d="M32.485,31.071l-1.414,1.414c-0.391,0.391-1.024,0.391-1.414,0L15.515,18.343 c-0.391-0.391-0.391-1.024,0-1.414l1.414-1.414c0.391-0.391,1.024-0.391,1.414,0l14.142,14.142 C32.876,30.047,32.876,30.681,32.485,31.071z"/></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
ui-common/src/components/UI/assets/success.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><linearGradient id="I9GV0SozQFknxHSR6DCx5a" x1="9.858" x2="38.142" y1="9.858" y2="38.142" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#21ad64"/><stop offset="1" stop-color="#088242"/></linearGradient><path fill="url(#I9GV0SozQFknxHSR6DCx5a)" d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"/><path d="M32.172,16.172L22,26.344l-5.172-5.172c-0.781-0.781-2.047-0.781-2.828,0l-1.414,1.414 c-0.781,0.781-0.781,2.047,0,2.828l8,8c0.781,0.781,2.047,0.781,2.828,0l13-13c0.781-0.781,0.781-2.047,0-2.828L35,16.172 C34.219,15.391,32.953,15.391,32.172,16.172z" opacity=".05"/><path d="M20.939,33.061l-8-8c-0.586-0.586-0.586-1.536,0-2.121l1.414-1.414c0.586-0.586,1.536-0.586,2.121,0 L22,27.051l10.525-10.525c0.586-0.586,1.536-0.586,2.121,0l1.414,1.414c0.586,0.586,0.586,1.536,0,2.121l-13,13 C22.475,33.646,21.525,33.646,20.939,33.061z" opacity=".07"/><path fill="#fff" d="M21.293,32.707l-8-8c-0.391-0.391-0.391-1.024,0-1.414l1.414-1.414c0.391-0.391,1.024-0.391,1.414,0 L22,27.758l10.879-10.879c0.391-0.391,1.024-0.391,1.414,0l1.414,1.414c0.391,0.391,0.391,1.024,0,1.414l-13,13 C22.317,33.098,21.683,33.098,21.293,32.707z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
34
ui-common/src/components/UI/assets/warning_icon.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="29.999" viewBox="0 0 22 29.999">
|
||||
<defs>
|
||||
<filter id="Rectangle_2909" width="21" height="25.999" x=".43" y="0" filterUnits="userSpaceOnUse">
|
||||
<feOffset dy="3"/>
|
||||
<feGaussianBlur result="blur" stdDeviation="3"/>
|
||||
<feFlood flood-opacity=".161"/>
|
||||
<feComposite in2="blur" operator="in"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
<filter id="Rectangle_2911" width="21" height="20.999" x=".43" y="9" filterUnits="userSpaceOnUse">
|
||||
<feOffset dy="3"/>
|
||||
<feGaussianBlur result="blur-2" stdDeviation="3"/>
|
||||
<feFlood flood-opacity=".161"/>
|
||||
<feComposite in2="blur-2" operator="in"/>
|
||||
<feComposite in="SourceGraphic"/>
|
||||
</filter>
|
||||
<style>
|
||||
.cls-2{fill:#fff}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="warning_icon" transform="translate(-883 -4234.5)">
|
||||
<circle id="Ellipse_1021" cx="11" cy="11" r="11" fill="#fdab2b" data-name="Ellipse 1021" transform="translate(883 4235)"/>
|
||||
<g id="Group_5975" data-name="Group 5975" transform="translate(892.43 4240.5)">
|
||||
<g id="Group_5974" data-name="Group 5974">
|
||||
<g filter="url(#Rectangle_2909)" transform="translate(-9.43 -6)">
|
||||
<rect id="Rectangle_2909-2" width="3" height="7.999" class="cls-2" data-name="Rectangle 2909" rx="1.5" transform="translate(9.43 6)"/>
|
||||
</g>
|
||||
<g filter="url(#Rectangle_2911)" transform="translate(-9.43 -6)">
|
||||
<rect id="Rectangle_2911-2" width="3" height="2.999" class="cls-2" data-name="Rectangle 2911" rx="1.499" transform="translate(9.43 15)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
11
ui-common/src/components/UI/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import LoadingOverlay from "./LoadingOverlay";
|
||||
import { Select } from "./Select";
|
||||
import Tabs from "./Tabs";
|
||||
import Tooltip from "./Tooltip";
|
||||
import Checkbox from "./Checkbox"
|
||||
import { StatusBar } from "./StatusBar";
|
||||
import CustomModal from "./CustomModal";
|
||||
|
||||
|
||||
export {LoadingOverlay,Select,Tabs,Tooltip,Checkbox,CustomModal}
|
||||
export {StatusBar}
|
@ -1,33 +1,33 @@
|
||||
.CollapsibleContainer-Header
|
||||
.CollapsibleContainerHeader
|
||||
display: flex
|
||||
align-items: center
|
||||
cursor: pointer
|
||||
min-height: 40px
|
||||
|
||||
.CollapsibleContainer-Header-Sticky
|
||||
.CollapsibleContainerHeaderSticky
|
||||
background: #1f253f
|
||||
position: sticky
|
||||
top: 0
|
||||
z-index: 1
|
||||
|
||||
.CollapsibleContainer-Bigger
|
||||
.CollapsibleContainerBigger
|
||||
padding: 10px
|
||||
font-size: 14px !important
|
||||
|
||||
.CollapsibleContainer-ExpandCollapseButton
|
||||
.CollapsibleContainerExpandCollapseButton
|
||||
margin-left: auto
|
||||
padding-right: 2%
|
||||
|
||||
.CollapsibleContainer-Expanded
|
||||
.CollapsibleContainerExpanded
|
||||
min-height: 40px
|
||||
|
||||
.CollapsibleContainer-Title
|
||||
.CollapsibleContainerTitle
|
||||
font-weight: 600
|
||||
font-family: 'Source Sans Pro', sans-serif
|
||||
font-size: 12px
|
||||
|
||||
.CollapsibleContainer-Expanded .CollapsibleContainer-Title
|
||||
.CollapsibleContainerExpanded .CollapsibleContainerTitle
|
||||
color: rgba(186, 199, 255, 1)
|
||||
|
||||
.CollapsibleContainer-Collapsed .CollapsibleContainer-Title
|
||||
.CollapsibleContainerCollapsed .CollapsibleContainerTitle
|
||||
color: rgba(186, 199, 255, 0.75)
|
@ -1,18 +1,18 @@
|
||||
.FancyTextDisplay-Container
|
||||
.FancyTextDisplayContainer
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
&.displayIconOnMouseOver
|
||||
.FancyTextDisplay-Icon
|
||||
.FancyTextDisplayIcon
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
&:hover
|
||||
.FancyTextDisplay-Icon
|
||||
.FancyTextDisplayIcon
|
||||
opacity: 1
|
||||
pointer-events: all
|
||||
|
||||
|
||||
.FancyTextDisplay-Icon
|
||||
.FancyTextDisplayIcon
|
||||
height: 22px
|
||||
width: 22px
|
||||
cursor: pointer
|
||||
@ -22,20 +22,19 @@
|
||||
background-color: rgba(255, 255, 255, 0.06)
|
||||
border-radius: 4px
|
||||
|
||||
&.FancyTextDisplay-ContainerEllipsis
|
||||
.FancyTextDisplay-Text
|
||||
&.FancyTextDisplayContainerEllipsis
|
||||
.FancyTextDisplayText
|
||||
text-align: left
|
||||
text-overflow: ellipsis
|
||||
overflow: hidden
|
||||
white-space: nowrap
|
||||
width: calc(100% - 30px)
|
||||
|
||||
.FancyTextDisplay-CopyNotifier
|
||||
.FancyTextDisplayCopyNotifier
|
||||
background-color: #4252a5
|
||||
padding: 2px 5px
|
||||
border-radius: 4px
|
||||
position: absolute
|
||||
transform: translate(0, -80%)
|
||||
color: white
|
||||
z-index: 1000
|
||||
|
||||
z-index: 1000
|
@ -1,19 +1,19 @@
|
||||
.Queryable-Container
|
||||
.QueryableContainer
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
&.displayIconOnMouseOver
|
||||
.Queryable-Icon
|
||||
.QueryableIcon
|
||||
opacity: 0
|
||||
width: 0px
|
||||
pointer-events: none
|
||||
&:hover
|
||||
.Queryable-Icon
|
||||
.QueryableIcon
|
||||
opacity: 1
|
||||
pointer-events: all
|
||||
|
||||
|
||||
.Queryable-Icon
|
||||
.QueryableIcon
|
||||
height: 22px
|
||||
width: 22px
|
||||
cursor: pointer
|
||||
@ -24,7 +24,7 @@
|
||||
border-radius: 4px
|
||||
color: #1E884B
|
||||
|
||||
.Queryable-AddNotifier
|
||||
.QueryableAddNotifier
|
||||
background-color: #1E884B
|
||||
font-weight: normal
|
||||
padding: 2px 5px
|
||||
@ -35,7 +35,7 @@
|
||||
z-index: 1000
|
||||
font-size: 11px
|
||||
|
||||
.Queryable-Tooltip
|
||||
.QueryableTooltip
|
||||
background-color: #1E884B
|
||||
font-weight: normal
|
||||
padding: 2px 5px
|
||||
@ -44,5 +44,4 @@
|
||||
transform: translate(0, -80%)
|
||||
color: white
|
||||
z-index: 1000
|
||||
font-size: 11px
|
||||
|
||||
font-size: 11px
|
@ -2,4 +2,4 @@
|
||||
fill: #627ef7
|
||||
|
||||
.list
|
||||
margin-top: 8px
|
||||
margin-top: 8px
|
@ -24,12 +24,13 @@
|
||||
justify-content: center
|
||||
font-weight: 600
|
||||
|
||||
.pods-count-text
|
||||
.podsCountText
|
||||
width: 100%
|
||||
max-width: 100%
|
||||
white-space: nowrap
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
img
|
||||
margin-right: 10px
|
||||
height: 22px
|
BIN
ui-common/src/components/assets/authBackground.png
Normal file
After Width: | Height: | Size: 137 KiB |
5
ui-common/src/components/assets/icon-next.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
|
||||
<g id="prefix__icon_expand" fill="#627ef7" transform="rotate(0 11 11)">
|
||||
<path id="prefix__icon_down" d="M5.117 0H3.07v3.07H0v2.047h5.117V0z" transform="rotate(-45 16.54 -2.201)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 292 B |
5
ui-common/src/components/assets/icon-prev.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22">
|
||||
<g id="prefix__icon_expand" fill="#627ef7" transform="rotate(180 11 11)">
|
||||
<path id="prefix__icon_down" d="M5.117 0H3.07v3.07H0v2.047h5.117V0z" transform="rotate(-45 16.54 -2.201)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 294 B |
@ -1,4 +1,4 @@
|
||||
.loading-overlay-container
|
||||
.loadingOverlayContainer
|
||||
position: absolute
|
||||
display: flex
|
||||
width: 100%
|
||||
@ -9,7 +9,7 @@
|
||||
justify-content: center
|
||||
background-color: rgba(25, 25, 25, 0.5)
|
||||
|
||||
.loading-overlay-spinner
|
||||
.loadingOverlaySpinner
|
||||
width: 60px
|
||||
height: 60px
|
||||
top: 50%
|
7
ui-common/src/components/style/Spinner.module.sass
Normal file
@ -0,0 +1,7 @@
|
||||
@import "../../variables.module"
|
||||
|
||||
.spinnerContainer
|
||||
display: flex
|
||||
justify-content: center
|
||||
margin-bottom: 10px
|
||||
|
7
ui-common/src/configs/shortcutsKeyboard.ts
Normal file
@ -0,0 +1,7 @@
|
||||
const dictionary = {
|
||||
ctrlEnter : [{metaKey : true, code:"Enter"}, {ctrlKey:true, code:"Enter"}], // support Ctrl/command
|
||||
enter : [{code:"Enter"}]
|
||||
};
|
||||
|
||||
|
||||
export default dictionary;
|
62
ui-common/src/helpers/commonStyle.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import {makeStyles} from "@material-ui/core";
|
||||
|
||||
// @ts-ignore
|
||||
export const useCommonStyles = makeStyles(() => ({
|
||||
button: {
|
||||
backgroundColor: "#205cf5",
|
||||
color: "white",
|
||||
fontWeight: "600 !important",
|
||||
fontSize: 12,
|
||||
padding: "9px 12px",
|
||||
borderRadius: "6px ! important",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "#205cf5",
|
||||
},
|
||||
"&:disabled":{
|
||||
backgroundColor: "rgba(0, 0, 0, 0.26)"
|
||||
}
|
||||
},
|
||||
outlinedButton: {
|
||||
backgroundColor: "transparent",
|
||||
color: "#205cf5",
|
||||
fontWeight: "600 !important",
|
||||
fontSize: 12,
|
||||
padding: "8px 12px",
|
||||
border: "1px #205cf5 solid",
|
||||
borderRadius: "6px ! important",
|
||||
|
||||
"&:hover": {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
|
||||
imagedButton: {
|
||||
padding: "1px 14px"
|
||||
},
|
||||
|
||||
textField: {
|
||||
outline: 0,
|
||||
background: "white",
|
||||
borderRadius: "4px",
|
||||
padding: "8px 10px",
|
||||
border: "1px #9D9D9D solid",
|
||||
fontSize: "14px",
|
||||
color: "#494677",
|
||||
height: "30px",
|
||||
boxSizing: "border-box"
|
||||
},
|
||||
modal :{
|
||||
position: 'absolute',
|
||||
top: '40%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -40%)',
|
||||
width: "CLAMP(600px,50%, 800px)",
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: '5px',
|
||||
boxShadow: 24,
|
||||
outline: "none",
|
||||
p: 4,
|
||||
color: '#000',
|
||||
}
|
||||
}));
|
38
ui-common/src/hooks/useKeyPress.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
|
||||
const useKeyPress = (eventConfigs, callback, node = null) => {
|
||||
// implement the callback ref pattern
|
||||
const callbackRef = useRef(callback);
|
||||
useLayoutEffect(() => {
|
||||
callbackRef.current = callback;
|
||||
});
|
||||
|
||||
// handle what happens on key press
|
||||
const handleKeyPress = useCallback(
|
||||
(event) => {
|
||||
|
||||
// check if one of the key is part of the ones we want
|
||||
if (eventConfigs.some((eventConfig) => Object.keys(eventConfig).every(nameKey => eventConfig[nameKey] === event[nameKey]))) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault();
|
||||
callbackRef.current(event);
|
||||
}
|
||||
},
|
||||
[eventConfigs]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// target is either the provided node or the document
|
||||
const targetNode = node || document;
|
||||
// attach the event listener
|
||||
targetNode &&
|
||||
targetNode.addEventListener("keydown", handleKeyPress);
|
||||
|
||||
// remove the event listener
|
||||
return () =>
|
||||
targetNode &&
|
||||
targetNode.removeEventListener("keydown", handleKeyPress);
|
||||
}, [handleKeyPress, node]);
|
||||
};
|
||||
|
||||
export default useKeyPress;
|