mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-01-25 02:08:19 +00:00
Feat/jaeger (#243)
* added dummy jaeger json file * added jaeger types file * added dev jaeger endpoint * added jaeger modal with trace visual / information * refactored jaeger logic fixed offsets on short duration spans * refactored into smaller components & added basic scroll bar * removed buttons, added scroll-x, fixed details height and scroll-y * shrunk bar graph to fit view port * slight adjustments * removed ref and now calculate width based on window innerwidth * @media for phone layouts * -fixed details width and padding. -removed scroll.tsx as not needed now -using SpanKind to find parent for now * -reduced truncate size for smaller screens * -root span is now determined from parentSpanId not being found * removed un-needed calls to /getRecentCalls as this was causing a race condition when pcap & jaeger fetching at same time - removed console.log's * wip: add tabs for recent callt tracing * wip: add tabs for recent callt tracing * wip: add tabs for recent callt tracing * fix: review comments * fix: review comments --------- Co-authored-by: ajukes <ajukes@vibecoms.co.uk>
This commit is contained in:
@@ -48,6 +48,7 @@ app.get(
|
||||
remote_host: "3.55.24.34",
|
||||
direction: 0 === i % 2 ? "inbound" : "outbound",
|
||||
trunk: 0 === i % 2 ? "twilio" : "user",
|
||||
trace_id: nanoid(),
|
||||
};
|
||||
data.push(call);
|
||||
}
|
||||
@@ -136,6 +137,17 @@ app.get(
|
||||
}
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/api/Accounts/:account_sid/RecentCalls/trace/:trace_id",
|
||||
(req: Request, res: Response) => {
|
||||
const json = fs.readFileSync(
|
||||
path.resolve(process.cwd(), "server", "sample-jaeger.json"),
|
||||
{ encoding: "utf8" }
|
||||
);
|
||||
res.status(200).json(JSON.parse(json));
|
||||
}
|
||||
);
|
||||
|
||||
/** Alerts mock API responses for local dev */
|
||||
app.get("/api/Accounts/:account_sid/Alerts", (req: Request, res: Response) => {
|
||||
const data: Alert[] = [];
|
||||
|
||||
1159
server/sample-jaeger.json
Normal file
1159
server/sample-jaeger.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,7 @@ import type {
|
||||
LcrCarrierSetEntry,
|
||||
} from "./types";
|
||||
import { StatusCodes } from "./types";
|
||||
import { JaegerRoot } from "./jaeger-types";
|
||||
|
||||
/** Wrap all requests to normalize response handling */
|
||||
const fetchTransport = <Type>(
|
||||
@@ -633,6 +634,14 @@ export const getPcap = (sid: string, sipCallId: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const getJaegerTrace = (sid: string, traceId: string) => {
|
||||
return getFetch<JaegerRoot>(
|
||||
import.meta.env.DEV
|
||||
? `${DEV_BASE_URL}/Accounts/${sid}/RecentCalls/trace/${traceId}`
|
||||
: `${API_ACCOUNTS}/${sid}/RecentCalls/trace/${traceId}`
|
||||
);
|
||||
};
|
||||
|
||||
export const getServiceProviderRecentCall = (
|
||||
sid: string,
|
||||
sipCallId: string
|
||||
|
||||
62
src/api/jaeger-types.ts
Normal file
62
src/api/jaeger-types.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
export interface JaegerRoot {
|
||||
resourceSpans: JaegerResourceSpan[];
|
||||
}
|
||||
|
||||
export interface JaegerResourceSpan {
|
||||
resource: JaegerResource;
|
||||
instrumentationLibrarySpans: InstrumentationLibrarySpan[];
|
||||
}
|
||||
|
||||
export interface JaegerResource {
|
||||
attributes: JaegerAttribute[];
|
||||
}
|
||||
|
||||
export interface InstrumentationLibrarySpan {
|
||||
instrumentationLibrary: InstrumentationLibrary;
|
||||
spans: JaegerSpan[];
|
||||
}
|
||||
|
||||
export interface InstrumentationLibrary {
|
||||
name: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface JaegerSpan {
|
||||
traceId: string;
|
||||
spanId: string;
|
||||
parentSpanId: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
startTimeUnixNano: number;
|
||||
endTimeUnixNano: number;
|
||||
attributes: JaegerAttribute[];
|
||||
}
|
||||
|
||||
export interface JaegerAttribute {
|
||||
key: string;
|
||||
value: JaegerValue;
|
||||
}
|
||||
|
||||
export interface JaegerValue {
|
||||
stringValue: string;
|
||||
doubleValue: string;
|
||||
}
|
||||
|
||||
export interface JaegerGroup {
|
||||
level: number;
|
||||
startPx: number;
|
||||
endPx: number;
|
||||
durationPx: number;
|
||||
startMs: number;
|
||||
endMs: number;
|
||||
durationMs: number;
|
||||
traceId: string;
|
||||
spanId: string;
|
||||
parentSpanId: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
startTimeUnixNano: number;
|
||||
endTimeUnixNano: number;
|
||||
attributes: JaegerAttribute[];
|
||||
children: JaegerGroup[];
|
||||
}
|
||||
@@ -289,6 +289,7 @@ export interface RecentCall {
|
||||
remote_host: string;
|
||||
direction: string;
|
||||
trunk: string;
|
||||
trace_id: string;
|
||||
}
|
||||
|
||||
export interface SpeechCredential {
|
||||
|
||||
29
src/containers/internal/views/recent-calls/call-detail.tsx
Normal file
29
src/containers/internal/views/recent-calls/call-detail.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
import { RecentCall } from "src/api/types";
|
||||
|
||||
export type CallDetailProps = {
|
||||
call: RecentCall;
|
||||
};
|
||||
|
||||
export const CallDetail = ({ call }: CallDetailProps) => {
|
||||
return (
|
||||
<>
|
||||
<div className="item__details">
|
||||
<div className="pre-grid">
|
||||
{Object.keys(call).map((key) => (
|
||||
<React.Fragment key={key}>
|
||||
<div>{key}:</div>
|
||||
<div>
|
||||
{call[key as keyof typeof call]
|
||||
? call[key as keyof typeof call].toString()
|
||||
: "null"}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallDetail;
|
||||
181
src/containers/internal/views/recent-calls/call-tracing.tsx
Normal file
181
src/containers/internal/views/recent-calls/call-tracing.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Bar } from "./jaeger/bar";
|
||||
import { JaegerGroup, JaegerRoot, JaegerSpan } from "src/api/jaeger-types";
|
||||
import { getJaegerTrace } from "src/api";
|
||||
import { toastError } from "src/store";
|
||||
import { RecentCall } from "src/api/types";
|
||||
import { JaegerDetail } from "./jaeger/detail";
|
||||
|
||||
function useWindowSize() {
|
||||
const [windowSize, setWindowSize] = useState({
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
setWindowSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
handleResize();
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
export type CallTracingProps = {
|
||||
call: RecentCall;
|
||||
};
|
||||
|
||||
export const CallTracing = ({ call }: CallTracingProps) => {
|
||||
const [jaegerRoot, setJaegerRoot] = useState<JaegerRoot>();
|
||||
const [jaegerGroup, setJaegerGroup] = useState<JaegerGroup>();
|
||||
const [jaegerDetail, setJaegerDetail] = useState<JaegerGroup>();
|
||||
const windowSize = useWindowSize();
|
||||
|
||||
const getSpansFromJaegerRoot = (trace: JaegerRoot) => {
|
||||
setJaegerRoot(trace);
|
||||
const spans: JaegerSpan[] = [];
|
||||
trace.resourceSpans.forEach((resourceSpan) => {
|
||||
resourceSpan.instrumentationLibrarySpans.forEach(
|
||||
(instrumentationLibrarySpan) => {
|
||||
instrumentationLibrarySpan.spans.forEach((value) => {
|
||||
const attrs = value.attributes.filter(
|
||||
(attr) =>
|
||||
!(
|
||||
attr.key.startsWith("telemetry") ||
|
||||
attr.key.startsWith("internal")
|
||||
)
|
||||
);
|
||||
value.attributes = attrs;
|
||||
spans.push(value);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
spans.sort((a, b) => a.startTimeUnixNano - b.startTimeUnixNano);
|
||||
return spans;
|
||||
};
|
||||
|
||||
const getGroupsByParent = (spanId: string, groups: JaegerGroup[]) => {
|
||||
groups.sort((a, b) => a.startTimeUnixNano - b.startTimeUnixNano);
|
||||
return groups.filter((value) => value.parentSpanId === spanId);
|
||||
};
|
||||
|
||||
const getRootSpan = (spans: JaegerSpan[]) => {
|
||||
const spanIds = spans.map((value) => value.spanId);
|
||||
return spans.find((value) => spanIds.indexOf(value.parentSpanId) == -1);
|
||||
};
|
||||
|
||||
const getRootGroup = (grps: JaegerGroup[]) => {
|
||||
const spanIds = grps.map((value) => value.spanId);
|
||||
return grps.find((value) => spanIds.indexOf(value.parentSpanId) == -1);
|
||||
};
|
||||
|
||||
const calculateRatio = (span: JaegerSpan) => {
|
||||
const { innerWidth } = window;
|
||||
const durationMs =
|
||||
(span.endTimeUnixNano - span.startTimeUnixNano) / 1_000_000;
|
||||
|
||||
if (durationMs > innerWidth) {
|
||||
const offset = innerWidth > 1200 ? 3 : innerWidth > 800 ? 2.5 : 2;
|
||||
return durationMs / (innerWidth - innerWidth / offset);
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
const buildSpans = (root: JaegerRoot) => {
|
||||
const spans = getSpansFromJaegerRoot(root);
|
||||
const rootSpan = getRootSpan(spans);
|
||||
if (rootSpan) {
|
||||
const startTime = rootSpan.startTimeUnixNano;
|
||||
const ratio = calculateRatio(rootSpan);
|
||||
calculateRatio(rootSpan);
|
||||
const groups: JaegerGroup[] = spans.map((span) => {
|
||||
const level = 0;
|
||||
const children: JaegerGroup[] = [];
|
||||
const startMs = (span.startTimeUnixNano - startTime) / 1_000_000;
|
||||
const durationMs =
|
||||
(span.endTimeUnixNano - span.startTimeUnixNano) / 1_000_000;
|
||||
const startPx = startMs / ratio;
|
||||
const durationPx = durationMs / ratio;
|
||||
const endPx = startPx + durationPx;
|
||||
const endMs = startMs + durationMs;
|
||||
return {
|
||||
level,
|
||||
children,
|
||||
startPx,
|
||||
endPx,
|
||||
durationPx,
|
||||
startMs,
|
||||
endMs,
|
||||
durationMs,
|
||||
...span,
|
||||
};
|
||||
});
|
||||
|
||||
const rootGroup = getRootGroup(groups);
|
||||
if (rootGroup) {
|
||||
rootGroup.children = buildChildren(
|
||||
rootGroup.level + 1,
|
||||
rootGroup,
|
||||
groups
|
||||
);
|
||||
setJaegerDetail(rootGroup);
|
||||
setJaegerGroup(rootGroup);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const buildChildren = (
|
||||
level: number,
|
||||
rootGroup: JaegerGroup,
|
||||
groups: JaegerGroup[]
|
||||
): JaegerGroup[] => {
|
||||
return getGroupsByParent(rootGroup.spanId, groups).map((group) => {
|
||||
group.level = level;
|
||||
group.children = buildChildren(group.level + 1, group, groups);
|
||||
return group;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (call.trace_id && call.trace_id != "00000000000000000000000000000000") {
|
||||
getJaegerTrace(call.account_sid, call.trace_id)
|
||||
.then(({ json }) => {
|
||||
if (json) {
|
||||
buildSpans(json);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastError(error.msg);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (jaegerRoot) {
|
||||
buildSpans(jaegerRoot);
|
||||
}
|
||||
}, [windowSize]);
|
||||
|
||||
if (jaegerGroup) {
|
||||
return (
|
||||
<>
|
||||
<div className="item__details">
|
||||
<div className="barGroup">
|
||||
<Bar group={jaegerGroup} handleRowSelect={setJaegerDetail} />
|
||||
</div>
|
||||
</div>
|
||||
{jaegerDetail && <JaegerDetail group={jaegerDetail} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CallTracing;
|
||||
@@ -4,8 +4,10 @@ import dayjs from "dayjs";
|
||||
import { Icons } from "src/components";
|
||||
import { formatPhoneNumber } from "src/utils";
|
||||
import { PcapButton } from "./pcap";
|
||||
|
||||
import type { RecentCall } from "src/api/types";
|
||||
import { Tabs, Tab } from "@jambonz/ui-kit";
|
||||
import CallDetail from "./call-detail";
|
||||
import CallTracing from "./call-tracing";
|
||||
|
||||
type DetailsItemProps = {
|
||||
call: RecentCall;
|
||||
@@ -13,6 +15,7 @@ type DetailsItemProps = {
|
||||
|
||||
export const DetailsItem = ({ call }: DetailsItemProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState("");
|
||||
|
||||
return (
|
||||
<div className="item">
|
||||
@@ -55,21 +58,29 @@ export const DetailsItem = ({ call }: DetailsItemProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div className="item__details">
|
||||
<div className="pre-grid">
|
||||
{Object.keys(call).map((key) => (
|
||||
<React.Fragment key={key}>
|
||||
<div>{key}:</div>
|
||||
<div>
|
||||
{call[key as keyof typeof call]
|
||||
? call[key as keyof typeof call].toString()
|
||||
: "null"}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{call.trace_id === "00000000000000000000000000000000" ? (
|
||||
<CallDetail call={call} />
|
||||
) : (
|
||||
<Tabs active={[activeTab, setActiveTab]}>
|
||||
<Tab id="details" label="Details">
|
||||
<CallDetail call={call} />
|
||||
</Tab>
|
||||
<Tab id="tracing" label="Tracing">
|
||||
<CallTracing call={call} />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
)}
|
||||
{open && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
width: "300px",
|
||||
}}
|
||||
>
|
||||
<PcapButton call={call} />
|
||||
</div>
|
||||
{open && <PcapButton call={call} />}
|
||||
</div>
|
||||
)}
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
|
||||
56
src/containers/internal/views/recent-calls/jaeger/bar.tsx
Normal file
56
src/containers/internal/views/recent-calls/jaeger/bar.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React from "react";
|
||||
import { JaegerGroup } from "src/api/jaeger-types";
|
||||
|
||||
import "./styles.scss";
|
||||
import { formattedDuration } from "./utils";
|
||||
|
||||
type BarProps = {
|
||||
group: JaegerGroup;
|
||||
handleRowSelect: (grp: JaegerGroup) => void;
|
||||
};
|
||||
|
||||
export const Bar = ({ group, handleRowSelect }: BarProps) => {
|
||||
const titleMargin = group.level * 30;
|
||||
|
||||
const handleRowClick = () => {
|
||||
handleRowSelect(group);
|
||||
};
|
||||
|
||||
const truncate = (str: string) => {
|
||||
if (str.length > 36) {
|
||||
return str.substring(0, 36) + "...";
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="barWrapper">
|
||||
<div
|
||||
role="presentation"
|
||||
className="barWrapper__row"
|
||||
onClick={handleRowClick}
|
||||
>
|
||||
<div
|
||||
className="barWrapper__header"
|
||||
style={{ paddingLeft: `${titleMargin}px` }}
|
||||
>
|
||||
{truncate(group.name)}
|
||||
</div>
|
||||
<button
|
||||
className="barWrapper__span"
|
||||
style={{ marginLeft: `${group.startPx}px`, width: group.durationPx }}
|
||||
/>
|
||||
<div className="barWrapper__duration">
|
||||
{formattedDuration(group.durationMs)}
|
||||
</div>
|
||||
</div>
|
||||
{group.children.map((value) => (
|
||||
<Bar
|
||||
key={value.spanId}
|
||||
group={value}
|
||||
handleRowSelect={handleRowSelect}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
55
src/containers/internal/views/recent-calls/jaeger/detail.tsx
Normal file
55
src/containers/internal/views/recent-calls/jaeger/detail.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { JaegerGroup } from "src/api/jaeger-types";
|
||||
import dayjs from "dayjs";
|
||||
import "./styles.scss";
|
||||
import { formattedDuration } from "./utils";
|
||||
|
||||
type JaegerDetailProps = {
|
||||
group: JaegerGroup;
|
||||
};
|
||||
|
||||
export const JaegerDetail = ({ group }: JaegerDetailProps) => {
|
||||
return (
|
||||
<div className="spanDetailsWrapper">
|
||||
<div className="spanDetailsWrapper__header">Span: {group.name}</div>
|
||||
<div className="spanDetailsWrapper__detailsWrapper">
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">Span ID:</div>
|
||||
<div className="spanDetailsWrapper__details_body">{group.spanId}</div>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">Span Start:</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{dayjs
|
||||
.unix(group.startTimeUnixNano / 1000000000)
|
||||
.format("DD/MM/YY HH:mm:ss.SSS")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">Span End:</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{dayjs
|
||||
.unix(group.endTimeUnixNano / 1000000000)
|
||||
.format("DD/MM/YY HH:mm:ss.SSS")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">Duration:</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{formattedDuration(group.durationMs)}
|
||||
</div>
|
||||
</div>
|
||||
{group.attributes.map((attribute) => (
|
||||
<div key={attribute.key} className="spanDetailsWrapper__details">
|
||||
<div className="spanDetailsWrapper__details_header">
|
||||
{attribute.key}:
|
||||
</div>
|
||||
<div className="spanDetailsWrapper__details_body">
|
||||
{attribute.value.stringValue || attribute.value.doubleValue}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
234
src/containers/internal/views/recent-calls/jaeger/styles.scss
Normal file
234
src/containers/internal/views/recent-calls/jaeger/styles.scss
Normal file
@@ -0,0 +1,234 @@
|
||||
@use "src/styles/vars";
|
||||
@use "src/styles/mixins";
|
||||
@use "@jambonz/ui-kit/src/styles/vars" as ui-vars;
|
||||
|
||||
.jaegerModal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
background-color: #fff;
|
||||
z-index: vars.$zindex00;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.modalHeader button {
|
||||
min-height: 28px;
|
||||
|
||||
@media (max-width: 820px) {
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
@media (min-width: 820px) {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.modalHeader {
|
||||
display: flex;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
vertical-align: middle;
|
||||
justify-content: left;
|
||||
height: 8vh;
|
||||
|
||||
@media (max-width: 380px) {
|
||||
padding: 10px;
|
||||
height: 10vh;
|
||||
font-size: 0.9em;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
@media (min-width: 410px) {
|
||||
padding: 10px;
|
||||
height: 8vh;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
padding: 20px;
|
||||
font-weight: lighter;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
@media (min-width: 820px) {
|
||||
padding: 20px;
|
||||
font-weight: lighter;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
@media (min-width: 910px) {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
&__header_item {
|
||||
padding-top: 10px;
|
||||
margin-left: 20px;
|
||||
display: flex;
|
||||
font-weight: bolder;
|
||||
|
||||
@media (max-width: 380px) {
|
||||
padding-top: 8px;
|
||||
margin-left: 15px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
@media (min-width: 380px) {
|
||||
padding-top: 12px;
|
||||
margin-left: 15px;
|
||||
font-size: 0.7em;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
@media (min-width: 410px) {
|
||||
padding-top: 11px;
|
||||
margin-left: 15px;
|
||||
font-size: 0.7em;
|
||||
font-weight: lighter;
|
||||
}
|
||||
|
||||
@media (min-width: 820px) {
|
||||
padding-top: 10px;
|
||||
font-weight: bolder;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
@media (min-width: 910px) {
|
||||
padding-top: 12px;
|
||||
font-weight: bolder;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.barGroup {
|
||||
border-radius: ui-vars.$px01;
|
||||
@include mixins.code();
|
||||
text-align: left;
|
||||
padding: ui-vars.$px03;
|
||||
color: ui-vars.$pink;
|
||||
background-color: ui-vars.$dark;
|
||||
border-radius: ui-vars.$px01;
|
||||
margin-top: ui-vars.$px02;
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
padding: 15px;
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
|
||||
.barWrapper {
|
||||
width: 100%;
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__row:hover {
|
||||
font-weight: bolder;
|
||||
color: ui-vars.$purple;
|
||||
}
|
||||
|
||||
&__row:hover button {
|
||||
font-weight: bolder;
|
||||
background-color: ui-vars.$purple;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__header {
|
||||
min-width: 400px;
|
||||
height: 15px;
|
||||
padding-top: 4px;
|
||||
font-size: small;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
padding-top: 4px;
|
||||
min-width: 250px;
|
||||
font-size: x-small;
|
||||
}
|
||||
}
|
||||
|
||||
&__span {
|
||||
padding-top: 4px;
|
||||
height: 5px;
|
||||
flex-shrink: 0;
|
||||
vertical-align: middle;
|
||||
border: 3px solid #444;
|
||||
border-radius: 8px;
|
||||
background-color: ui-vars.$jambonz;
|
||||
min-width: 6px;
|
||||
}
|
||||
|
||||
&__duration {
|
||||
margin: 5px;
|
||||
min-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.spanDetailsWrapper {
|
||||
border-radius: ui-vars.$px01;
|
||||
@include mixins.code();
|
||||
text-align: left;
|
||||
padding: ui-vars.$px03;
|
||||
color: ui-vars.$pink;
|
||||
background-color: ui-vars.$dark;
|
||||
border-radius: ui-vars.$px01;
|
||||
margin-top: ui-vars.$px02;
|
||||
|
||||
&__detailsWrapper {
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
font-size: 0.9em;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
padding: 5px;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
padding: 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
&__details {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__details_header {
|
||||
padding: 5px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
@media (min-width: 600px) {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
min-width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
&__details_body {
|
||||
flex-grow: 1;
|
||||
white-space: pre;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
background-color: #fff;
|
||||
color: ui-vars.$jambonz;
|
||||
padding: 20px 20px 20px 30px;
|
||||
border-bottom: thin solid #ccc;
|
||||
}
|
||||
}
|
||||
16
src/containers/internal/views/recent-calls/jaeger/utils.tsx
Normal file
16
src/containers/internal/views/recent-calls/jaeger/utils.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export const formattedDuration = (duration: number) => {
|
||||
if (duration < 1) {
|
||||
return (Math.round(duration * 100) / 100).toFixed(2) + "ms";
|
||||
} else if (duration < 1000) {
|
||||
return (Math.round(duration * 100) / 100).toFixed(0) + "ms";
|
||||
} else if (duration >= 1000) {
|
||||
const min = Math.floor((duration / 1000 / 60) << 0);
|
||||
if (min == 0) {
|
||||
const secs = parseFloat(`${duration / 1000}`).toFixed(2);
|
||||
return `${secs}s`;
|
||||
} else {
|
||||
const sec = Math.floor((duration / 1000) % 60);
|
||||
return `${min}m ${sec}s`;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { getPcap, getRecentCall } from "src/api";
|
||||
import { getPcap } from "src/api";
|
||||
import { toastError } from "src/store";
|
||||
|
||||
import type { Pcap, RecentCall } from "src/api/types";
|
||||
@@ -13,21 +13,13 @@ export const PcapButton = ({ call }: PcapButtonProps) => {
|
||||
const [pcap, setPcap] = useState<Pcap>();
|
||||
|
||||
useEffect(() => {
|
||||
getRecentCall(call.account_sid, call.sip_callid)
|
||||
.then(({ json }) => {
|
||||
if (json.total > 0) {
|
||||
getPcap(call.account_sid, call.sip_callid)
|
||||
.then(({ blob }) => {
|
||||
if (blob) {
|
||||
setPcap({
|
||||
data_url: URL.createObjectURL(blob),
|
||||
file_name: `callid-${call.sip_callid}.pcap`,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
toastError(error.msg);
|
||||
});
|
||||
getPcap(call.account_sid, call.sip_callid)
|
||||
.then(({ blob }) => {
|
||||
if (blob) {
|
||||
setPcap({
|
||||
data_url: URL.createObjectURL(blob),
|
||||
file_name: `callid-${call.sip_callid}.pcap`,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -170,6 +170,7 @@ details {
|
||||
color: ui-vars.$pink;
|
||||
background-color: ui-vars.$dark;
|
||||
border-radius: ui-vars.$px01;
|
||||
margin-top: ui-vars.$px02;
|
||||
}
|
||||
|
||||
.pcap {
|
||||
|
||||
Reference in New Issue
Block a user