mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-05-27 19:45:23 +00:00
1974
ui/package-lock.json
generated
1974
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.3",
|
||||
"@material-ui/lab": "^4.0.0-alpha.60",
|
||||
"@testing-library/jest-dom": "^5.11.10",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
@@ -11,6 +12,8 @@
|
||||
"@types/node": "^12.20.10",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"jsonpath": "^1.1.1",
|
||||
"axios": "^0.21.1",
|
||||
"node-sass": "^5.0.0",
|
||||
"numeral": "^2.0.6",
|
||||
"protobuf-decoder": "^0.1.0",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, {useState} from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import './App.sass';
|
||||
import logo from './components/assets/Mizu-logo.svg';
|
||||
import {Button} from "@material-ui/core";
|
||||
import {Button, Snackbar} from "@material-ui/core";
|
||||
import {HarPage} from "./components/HarPage";
|
||||
import Tooltip from "./components/Tooltip";
|
||||
import {makeStyles} from "@material-ui/core/styles";
|
||||
import MuiAlert from '@material-ui/lab/Alert';
|
||||
import Api from "./helpers/api";
|
||||
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
@@ -15,11 +17,37 @@ const useStyles = makeStyles(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const api = new Api();
|
||||
|
||||
const App = () => {
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
const [analyzeStatus, setAnalyzeStatus] = useState(null);
|
||||
const [showTLSWarning, setShowTLSWarning] = useState(false);
|
||||
const [userDismissedTLSWarning, setUserDismissedTLSWarning] = useState(false);
|
||||
const [addressesWithTLS, setAddressesWithTLS] = useState(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const recentTLSLinks = await api.getRecentTLSLinks();
|
||||
|
||||
if (recentTLSLinks?.length > 0) {
|
||||
setAddressesWithTLS(new Set([...addressesWithTLS, ...recentTLSLinks]));
|
||||
setShowTLSWarning(true);
|
||||
}
|
||||
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const onTLSDetected = (destAddress: string) => {
|
||||
addressesWithTLS.add(destAddress);
|
||||
setAddressesWithTLS(new Set(addressesWithTLS));
|
||||
|
||||
if (!userDismissedTLSWarning) {
|
||||
setShowTLSWarning(true);
|
||||
}
|
||||
};
|
||||
|
||||
const analysisMessage = analyzeStatus?.isRemoteReady ?
|
||||
<span>
|
||||
@@ -88,7 +116,12 @@ const App = () => {
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
<HarPage setAnalyzeStatus={setAnalyzeStatus}/>
|
||||
<HarPage setAnalyzeStatus={setAnalyzeStatus} onTLSDetected={onTLSDetected}/>
|
||||
<Snackbar open={showTLSWarning && !userDismissedTLSWarning}>
|
||||
<MuiAlert elevation={6} variant="filled" onClose={() => setUserDismissedTLSWarning(true)} severity="warning">
|
||||
Mizu is detecting TLS traffic{addressesWithTLS.size ? ` (directed to ${Array.from(addressesWithTLS).join(", ")})` : ''}, this type of traffic will not be displayed.
|
||||
</MuiAlert>
|
||||
</Snackbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import styles from './style/HarEntriesList.module.sass';
|
||||
import spinner from './assets/spinner.svg';
|
||||
import ScrollableFeed from "react-scrollable-feed";
|
||||
import {StatusType} from "./HarFilters";
|
||||
import Api from "../helpers/api";
|
||||
|
||||
interface HarEntriesListProps {
|
||||
entries: any[];
|
||||
@@ -25,6 +26,8 @@ enum FetchOperator {
|
||||
GT = "gt"
|
||||
}
|
||||
|
||||
const api = new Api();
|
||||
|
||||
export const HarEntriesList: React.FC<HarEntriesListProps> = ({entries, setEntries, focusedEntryId, setFocusedEntryId, connectionOpen, noMoreDataTop, setNoMoreDataTop, noMoreDataBottom, setNoMoreDataBottom, methodsFilter, statusFilter, pathFilter}) => {
|
||||
|
||||
const [loadMoreTop, setLoadMoreTop] = useState(false);
|
||||
@@ -54,14 +57,9 @@ export const HarEntriesList: React.FC<HarEntriesListProps> = ({entries, setEntri
|
||||
return entries.filter(filterEntries);
|
||||
},[entries, filterEntries])
|
||||
|
||||
const fetchData = async (operator, timestamp) => {
|
||||
const response = await fetch(`http://localhost:8899/api/entries?limit=50&operator=${operator}×tamp=${timestamp}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
const getOldEntries = useCallback(async () => {
|
||||
setIsLoadingTop(true);
|
||||
const data = await fetchData(FetchOperator.LT, entries[0].timestamp);
|
||||
const data = await api.fetchEntries(FetchOperator.LT, entries[0].timestamp);
|
||||
setLoadMoreTop(false);
|
||||
|
||||
let scrollTo;
|
||||
@@ -89,7 +87,7 @@ export const HarEntriesList: React.FC<HarEntriesListProps> = ({entries, setEntri
|
||||
}, [loadMoreTop, connectionOpen, noMoreDataTop, getOldEntries]);
|
||||
|
||||
const getNewEntries = async () => {
|
||||
const data = await fetchData(FetchOperator.GT, entries[entries.length - 1].timestamp);
|
||||
const data = await api.fetchEntries(FetchOperator.GT, entries[entries.length - 1].timestamp);
|
||||
let scrollTo;
|
||||
if(data.length === 0) {
|
||||
setNoMoreDataBottom(true);
|
||||
|
||||
@@ -19,6 +19,13 @@ interface HAREntry {
|
||||
isCurrentRevision?: boolean;
|
||||
timestamp: Date;
|
||||
isOutgoing?: boolean;
|
||||
latency: number;
|
||||
rules: Rules;
|
||||
}
|
||||
|
||||
interface Rules {
|
||||
status: boolean;
|
||||
latency: number
|
||||
}
|
||||
|
||||
interface HAREntryProps {
|
||||
@@ -48,9 +55,16 @@ export const HarEntry: React.FC<HAREntryProps> = ({entry, setFocusedEntryId, isS
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let backgroundColor = "";
|
||||
if ('latency' in entry.rules) {
|
||||
if (entry.rules.latency !== -1) {
|
||||
backgroundColor = entry.rules.latency >= entry.latency ? styles.ruleSuccessRow : styles.ruleFailureRow
|
||||
} else {
|
||||
backgroundColor = entry.rules.status ? styles.ruleSuccessRow : styles.ruleFailureRow
|
||||
}
|
||||
}
|
||||
return <>
|
||||
<div id={entry.id} className={`${styles.row} ${isSelected ? styles.rowSelected : ''}`} onClick={() => setFocusedEntryId(entry.id)}>
|
||||
<div id={entry.id} className={`${styles.row} ${isSelected ? styles.rowSelected : backgroundColor}`} onClick={() => setFocusedEntryId(entry.id)}>
|
||||
{entry.statusCode && <div>
|
||||
<StatusCode statusCode={entry.statusCode}/>
|
||||
</div>}
|
||||
|
||||
@@ -29,7 +29,7 @@ const HarEntryTitle: React.FC<any> = ({har}) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const {log: {entries}} = har;
|
||||
const {response, request, timings: {receive}} = entries[0];
|
||||
const {response, request, timings: {receive}} = entries[0].entry;
|
||||
const {status, statusText, bodySize} = response;
|
||||
|
||||
|
||||
@@ -40,9 +40,10 @@ const HarEntryTitle: React.FC<any> = ({har}) => {
|
||||
<div style={{flexGrow: 1, overflow: 'hidden'}}>
|
||||
<EndpointPath method={request?.method} path={request?.url}/>
|
||||
</div>
|
||||
<div style={{margin: "0 24px", opacity: 0.5}}>{formatSize(bodySize)}</div>
|
||||
<div style={{marginRight: 24, opacity: 0.5}}>{status} {statusText}</div>
|
||||
<div style={{opacity: 0.5}}>{Math.round(receive)}ms</div>
|
||||
<div style={{margin: "0 18px", opacity: 0.5}}>{formatSize(bodySize)}</div>
|
||||
<div style={{marginRight: 18, opacity: 0.5}}>{status} {statusText}</div>
|
||||
<div style={{marginRight: 18, opacity: 0.5}}>{Math.round(receive)}ms</div>
|
||||
<div style={{opacity: 0.5}}>{'rulesMatched' in entries[0] ? entries[0].rulesMatched?.length : '0'} Rules Applied</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,27 @@
|
||||
width: 1%
|
||||
max-width: 15rem
|
||||
|
||||
.rulesTitleSuccess
|
||||
color: #0C0B1A
|
||||
|
||||
.rulesMatchedSuccess
|
||||
background: #E8FFF1
|
||||
padding: 5px
|
||||
border-radius: 4px
|
||||
color: #219653
|
||||
font-style: normal
|
||||
font-size: 0.7rem
|
||||
font-weight: 600
|
||||
|
||||
.rulesMatchedFailure
|
||||
background: #FFE9EF
|
||||
padding: 5px
|
||||
border-radius: 4px
|
||||
color: #DB2156
|
||||
font-style: normal
|
||||
font-size: 0.7rem
|
||||
font-weight: 600
|
||||
|
||||
.dataValue
|
||||
color: $blue-gray
|
||||
margin: 0
|
||||
@@ -66,7 +87,6 @@
|
||||
border-top: 1px solid $light-blue-color
|
||||
padding: 1rem
|
||||
background: none
|
||||
|
||||
table
|
||||
width: 100%
|
||||
tr td:first-child
|
||||
|
||||
@@ -5,6 +5,7 @@ import CollapsibleContainer from "../CollapsibleContainer";
|
||||
import FancyTextDisplay from "../FancyTextDisplay";
|
||||
import Checkbox from "../Checkbox";
|
||||
import ProtobufDecoder from "protobuf-decoder";
|
||||
var jp = require('jsonpath');
|
||||
|
||||
interface HAREntryViewLineProps {
|
||||
label: string;
|
||||
@@ -144,3 +145,122 @@ export const HAREntryTableSection: React.FC<HAREntrySectionProps> = ({title, arr
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface HAREntryPolicySectionProps {
|
||||
service: string,
|
||||
title: string,
|
||||
response: any,
|
||||
latency?: number,
|
||||
arrayToIterate: any[],
|
||||
}
|
||||
|
||||
|
||||
interface HAREntryPolicySectionCollapsibleTitleProps {
|
||||
label: string;
|
||||
matched: string;
|
||||
isExpanded: boolean;
|
||||
}
|
||||
|
||||
const HAREntryPolicySectionCollapsibleTitle: React.FC<HAREntryPolicySectionCollapsibleTitleProps> = ({label, matched, isExpanded}) => {
|
||||
return <div className={styles.title}>
|
||||
<span className={`${styles.button} ${isExpanded ? styles.expanded : ''}`}>
|
||||
{isExpanded ? '-' : '+'}
|
||||
</span>
|
||||
<span>
|
||||
<tr className={styles.dataLine}>
|
||||
<td className={`${styles.dataKey} ${styles.rulesTitleSuccess}`}>{label}</td>
|
||||
<td className={`${styles.dataKey} ${matched === 'Success' ? styles.rulesMatchedSuccess : styles.rulesMatchedFailure}`}>{matched}</td>
|
||||
</tr>
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
interface HAREntryPolicySectionContainerProps {
|
||||
label: string;
|
||||
matched: string;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export const HAREntryPolicySectionContainer: React.FC<HAREntryPolicySectionContainerProps> = ({label, matched, children}) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
return <CollapsibleContainer
|
||||
className={styles.collapsibleContainer}
|
||||
isExpanded={expanded}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
title={<HAREntryPolicySectionCollapsibleTitle label={label} matched={matched} isExpanded={expanded}/>}
|
||||
>
|
||||
{children}
|
||||
</CollapsibleContainer>
|
||||
}
|
||||
|
||||
export const HAREntryTablePolicySection: React.FC<HAREntryPolicySectionProps> = ({service, title, response, latency, arrayToIterate}) => {
|
||||
const base64ToJson = response.content.mimeType === "application/json; charset=utf-8" ? JSON.parse(Buffer.from(response.content.text, "base64").toString()) : {};
|
||||
return <React.Fragment>
|
||||
{
|
||||
arrayToIterate && arrayToIterate.length > 0 ?
|
||||
<>
|
||||
<HAREntrySectionContainer title={title}>
|
||||
<table>
|
||||
<tbody>
|
||||
{arrayToIterate.map(({rule, matched}, index) => {
|
||||
|
||||
|
||||
return (
|
||||
<HAREntryPolicySectionContainer key={index} label={rule.Name} matched={matched && (rule.Type === 'latency' ? rule.Latency >= latency : true)? "Success" : "Failure"}>
|
||||
{
|
||||
|
||||
<>
|
||||
{
|
||||
rule.Key != "" ?
|
||||
<tr className={styles.dataValue}><td><b>Key</b>:</td><td>{rule.Key}</td></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
rule.Latency != "" ?
|
||||
<tr className={styles.dataValue}><td><b>Latency:</b></td> <td>{rule.Latency}</td></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
rule.Method != "" ?
|
||||
<tr className={styles.dataValue}><td><b>Method:</b></td> <td>{rule.Method}</td></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
rule.Path != "" ?
|
||||
<tr className={styles.dataValue}><td><b>Path:</b></td> <td>{rule.Path}</td></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
rule.Service != "" ?
|
||||
<tr className={styles.dataValue}><td><b>Service:</b></td> <td>{service}</td></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
rule.Type != "" ?
|
||||
<tr className={styles.dataValue}><td><b>Type:</b></td> <td>{rule.Type}</td></tr>
|
||||
: null
|
||||
}
|
||||
{
|
||||
rule.Value != "" ?
|
||||
<tr className={styles.dataValue}><td><b>Value:</b></td> <td>{rule.Value}</td></tr>
|
||||
: null
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
</HAREntryPolicySectionContainer>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</HAREntrySectionContainer>
|
||||
|
||||
</> : <span/>
|
||||
}
|
||||
</React.Fragment>
|
||||
}
|
||||
@@ -1,19 +1,22 @@
|
||||
import React, {useState} from 'react';
|
||||
import styles from './HAREntryViewer.module.sass';
|
||||
import Tabs from "../Tabs";
|
||||
import {HAREntryTableSection, HAREntryBodySection} from "./HAREntrySections";
|
||||
import {HAREntryTableSection, HAREntryBodySection, HAREntryTablePolicySection} from "./HAREntrySections";
|
||||
|
||||
const MIME_TYPE_KEY = 'mimeType';
|
||||
|
||||
const HAREntryDisplay: React.FC<any> = ({entry, isCollapsed: initialIsCollapsed, isResponseMocked}) => {
|
||||
const {request, response} = entry;
|
||||
|
||||
const HAREntryDisplay: React.FC<any> = ({har, entry, isCollapsed: initialIsCollapsed, isResponseMocked}) => {
|
||||
const {request, response, timings: {receive}} = entry;
|
||||
const rulesMatched = har.log.entries[0].rulesMatched
|
||||
const TABS = [
|
||||
{tab: 'request'},
|
||||
{
|
||||
tab: 'response',
|
||||
badge: <>{isResponseMocked && <span className="smallBadge virtual mock">MOCK</span>}</>
|
||||
},
|
||||
{
|
||||
tab: 'Rules',
|
||||
},
|
||||
];
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(TABS[0].tab);
|
||||
@@ -43,6 +46,9 @@ const HAREntryDisplay: React.FC<any> = ({entry, isCollapsed: initialIsCollapsed,
|
||||
|
||||
<HAREntryTableSection title={'Cookies'} arrayToIterate={response.cookies}/>
|
||||
</React.Fragment>}
|
||||
{currentTab === TABS[2].tab && <React.Fragment>
|
||||
<HAREntryTablePolicySection service={har.log.entries[0].service} title={'Rule'} latency={receive} response={response} arrayToIterate={rulesMatched ? rulesMatched : []}/>
|
||||
</React.Fragment>}
|
||||
</div>}
|
||||
</div>;
|
||||
}
|
||||
@@ -58,7 +64,7 @@ const HAREntryViewer: React.FC<Props> = ({harObject, className, isResponseMocked
|
||||
const {log: {entries}} = harObject;
|
||||
const isCollapsed = entries.length > 1;
|
||||
return <div className={`${className ? className : ''}`}>
|
||||
{Object.keys(entries).map((entry: any, index) => <HAREntryDisplay isCollapsed={isCollapsed} key={index} entry={entries[entry]} isResponseMocked={isResponseMocked} showTitle={showTitle}/>)}
|
||||
{Object.keys(entries).map((entry: any, index) => <HAREntryDisplay har={harObject} isCollapsed={isCollapsed} key={index} entry={entries[entry].entry} isResponseMocked={isResponseMocked} showTitle={showTitle}/>)}
|
||||
</div>
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import playIcon from './assets/run.svg';
|
||||
import pauseIcon from './assets/pause.svg';
|
||||
import variables from './style/variables.module.scss';
|
||||
import {StatusBar} from "./StatusBar";
|
||||
import Api, {MizuWebsocketURL} from "../helpers/api";
|
||||
|
||||
const useLayoutStyles = makeStyles(() => ({
|
||||
details: {
|
||||
@@ -37,25 +38,12 @@ enum ConnectionStatus {
|
||||
|
||||
interface HarPageProps {
|
||||
setAnalyzeStatus: (status: any) => void;
|
||||
onTLSDetected: (destAddress: string) => void;
|
||||
}
|
||||
|
||||
const mizuAPIPathPrefix = "/mizu";
|
||||
const api = new Api();
|
||||
|
||||
|
||||
// When working locally (with npm run start) we need to change the PORT
|
||||
const getMizuApiUrl = () => {
|
||||
return `${window.location.origin}${mizuAPIPathPrefix}`;
|
||||
};
|
||||
|
||||
const getMizuWebsocketUrl = () => {
|
||||
return `ws://${window.location.host}${mizuAPIPathPrefix}/ws`;
|
||||
}
|
||||
|
||||
|
||||
const mizuApiUrl = getMizuApiUrl();
|
||||
const mizuWebsocketUrl = getMizuWebsocketUrl();
|
||||
|
||||
export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus}) => {
|
||||
export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus, onTLSDetected}) => {
|
||||
|
||||
const classes = useLayoutStyles();
|
||||
|
||||
@@ -75,7 +63,7 @@ export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus}) => {
|
||||
const ws = useRef(null);
|
||||
|
||||
const openWebSocket = () => {
|
||||
ws.current = new WebSocket(mizuWebsocketUrl);
|
||||
ws.current = new WebSocket(MizuWebsocketURL);
|
||||
ws.current.onopen = () => setConnection(ConnectionStatus.Connected);
|
||||
ws.current.onclose = () => setConnection(ConnectionStatus.Closed);
|
||||
}
|
||||
@@ -84,7 +72,6 @@ export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus}) => {
|
||||
ws.current.onmessage = e => {
|
||||
if (!e?.data) return;
|
||||
const message = JSON.parse(e.data);
|
||||
|
||||
switch (message.messageType) {
|
||||
case "entry":
|
||||
const entry = message.data
|
||||
@@ -106,6 +93,9 @@ export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus}) => {
|
||||
case "analyzeStatus":
|
||||
setAnalyzeStatus(message.analyzeStatus);
|
||||
break
|
||||
case "outboundLink":
|
||||
onTLSDetected(message.Data.DstIP);
|
||||
break;
|
||||
default:
|
||||
console.error(`unsupported websocket message type, Got: ${message.messageType}`)
|
||||
}
|
||||
@@ -113,24 +103,32 @@ export const HarPage: React.FC<HarPageProps> = ({setAnalyzeStatus}) => {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
openWebSocket();
|
||||
fetch(`${mizuApiUrl}/api/tapStatus`)
|
||||
.then(response => response.json())
|
||||
.then(data => setTappingStatus(data));
|
||||
|
||||
fetch(`${mizuApiUrl}/api/analyzeStatus`)
|
||||
.then(response => response.json())
|
||||
.then(data => setAnalyzeStatus(data));
|
||||
(async () => {
|
||||
openWebSocket();
|
||||
try{
|
||||
const tapStatusResponse = await api.tapStatus();
|
||||
setTappingStatus(tapStatusResponse);
|
||||
const analyzeStatusResponse = await api.analyzeStatus();
|
||||
setAnalyzeStatus(analyzeStatusResponse);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})()
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!focusedEntryId) return;
|
||||
setSelectedHarEntry(null)
|
||||
fetch(`${mizuApiUrl}/api/entries/${focusedEntryId}`)
|
||||
.then(response => response.json())
|
||||
.then(data => setSelectedHarEntry(data));
|
||||
setSelectedHarEntry(null);
|
||||
(async () => {
|
||||
try {
|
||||
const entryData = await api.getEntry(focusedEntryId);
|
||||
setSelectedHarEntry(entryData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
})()
|
||||
}, [focusedEntryId])
|
||||
|
||||
const toggleConnection = () => {
|
||||
|
||||
@@ -29,12 +29,14 @@ export const StatusBar: React.FC<Props> = ({tappingStatus}) => {
|
||||
<div className="podsCount">{`Tapping ${amountOfPods} ${pluralize('pod', amountOfPods)} in ${pluralize('namespace', uniqueNamespaces.length)} ${uniqueNamespaces.join(", ")}`}</div>
|
||||
{expandedBar && <div style={{marginTop: 20}}>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Pod name</th>
|
||||
<th>Namespace</th>
|
||||
</tr>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pod name</th>
|
||||
<th>Namespace</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tappingStatus.pods.map(pod => <tr>
|
||||
{tappingStatus.pods.map(pod => <tr key={pod.name}>
|
||||
<td>{pod.name}</td>
|
||||
<td>{pod.namespace}</td>
|
||||
</tr>)}
|
||||
|
||||
@@ -23,6 +23,14 @@
|
||||
margin-left: 10px
|
||||
margin-right: 3px
|
||||
|
||||
.ruleSuccessRow
|
||||
border: 1px $success-color solid
|
||||
border-left: 5px $success-color solid
|
||||
|
||||
.ruleFailureRow
|
||||
border: 1px $failure-color solid
|
||||
border-left: 5px $failure-color solid
|
||||
|
||||
.service
|
||||
text-overflow: ellipsis
|
||||
overflow: hidden
|
||||
|
||||
50
ui/src/helpers/api.js
Normal file
50
ui/src/helpers/api.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as axios from "axios";
|
||||
|
||||
const mizuAPIPathPrefix = "/mizu";
|
||||
|
||||
// When working locally (with npm run start) change to:
|
||||
// export const MizuWebsocketURL = `ws://localhost:8899${mizuAPIPathPrefix}/ws`;
|
||||
export const MizuWebsocketURL = `ws://${window.location.host}${mizuAPIPathPrefix}/ws`;
|
||||
|
||||
export default class Api {
|
||||
|
||||
constructor() {
|
||||
|
||||
// When working locally (with npm run start) change to:
|
||||
// const apiURL = `http://localhost:8899/${mizuAPIPathPrefix}/api/`;
|
||||
const apiURL = `${window.location.origin}${mizuAPIPathPrefix}/api/`;
|
||||
|
||||
this.client = axios.create({
|
||||
baseURL: apiURL,
|
||||
timeout: 31000,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tapStatus = async () => {
|
||||
const response = await this.client.get("/tapStatus");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
analyzeStatus = async () => {
|
||||
const response = await this.client.get("/analyzeStatus");
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getEntry = async (entryId) => {
|
||||
const response = await this.client.get(`/entries/${entryId}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
fetchEntries = async (operator, timestamp) => {
|
||||
const response = await this.client.get(`/entries?limit=50&operator=${operator}×tamp=${timestamp}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
getRecentTLSLinks = async () => {
|
||||
const response = await this.client.get("/recentTLSLinks");
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user