change(ui) - dev tools
This commit is contained in:
parent
9b6be7e580
commit
a6227c7265
28 changed files with 1417 additions and 302 deletions
|
|
@ -112,15 +112,15 @@ export default class Autoscroll extends React.PureComponent<Props, {
|
|||
{children}
|
||||
</div>
|
||||
|
||||
<div className={stl.navButtons}>
|
||||
{/* <label><input type={'checkbox'} checked={this.state.autoScroll} onChange={(e) => this.setState({ autoScroll: !this.state.autoScroll })} /> Autoscroll</label> */}
|
||||
{/* <div className={stl.navButtons}>
|
||||
<label><input type={'checkbox'} checked={this.state.autoScroll} onChange={(e) => this.setState({ autoScroll: !this.state.autoScroll })} /> Autoscroll</label>
|
||||
{navigation && (
|
||||
<>
|
||||
<IconButton size="small" icon="chevron-up" onClick={this.onPrevClick} />
|
||||
<IconButton size="small" icon="chevron-down" onClick={this.onNextClick} className="mt-5" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
& >.infoPoint {
|
||||
font-size: 12px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:not(:last-child):after {
|
||||
|
|
|
|||
|
|
@ -83,11 +83,12 @@ export default class ConsoleContent extends React.PureComponent {
|
|||
<Tabs tabs={TABS} active={activeTab} onClick={this.onTabClick} border={false} />
|
||||
</div>
|
||||
<Input
|
||||
className="input-small"
|
||||
className="input-small h-8"
|
||||
placeholder="Filter by keyword"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
height={28}
|
||||
onChange={this.onFilterChange}
|
||||
/>
|
||||
</BottomBlock.Header>
|
||||
|
|
@ -101,7 +102,7 @@ export default class ConsoleContent extends React.PureComponent {
|
|||
<Autoscroll autoScrollTo={Math.max(lastIndex, 0)}>
|
||||
{filtered.map((l, index) => (
|
||||
<div
|
||||
className={cn('flex py-2 px-4', {
|
||||
className={cn(stl.line, 'flex py-2 px-4', {
|
||||
info: !l.isYellow() && !l.isRed(),
|
||||
warn: l.isYellow(),
|
||||
error: l.isRed(),
|
||||
|
|
@ -114,10 +115,10 @@ export default class ConsoleContent extends React.PureComponent {
|
|||
<div className={cn(stl.timestamp)}>
|
||||
<Icon size="14" className={stl.icon} {...getIconProps(l.level)} />
|
||||
</div>
|
||||
<div className={cn(stl.timestamp, {})}>
|
||||
{/* <div className={cn(stl.timestamp, {})}>
|
||||
{Duration.fromMillis(l.time).toFormat('mm:ss.SSS')}
|
||||
</div>
|
||||
<div key={l.key} className={cn(stl.line)} data-scroll-item={l.isRed()}>
|
||||
</div> */}
|
||||
<div key={l.key} className={cn('')} data-scroll-item={l.isRed()}>
|
||||
<div className={stl.message}>{renderWithNL(l.value)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@
|
|||
display: flex;
|
||||
align-items: flex-start;
|
||||
border-bottom: solid thin $gray-light-shade;
|
||||
&:hover {
|
||||
background-coor: $active-blue !important;
|
||||
}
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
// import { connectPlayer } from 'Player';
|
||||
import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Button } from 'UI';
|
||||
import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI';
|
||||
import { getRE } from 'App/utils';
|
||||
import { TYPES } from 'Types/session/resource';
|
||||
import { formatBytes } from 'App/utils';
|
||||
|
|
@ -48,22 +48,17 @@ export function renderType(r) {
|
|||
|
||||
export function renderName(r) {
|
||||
return (
|
||||
<Popup
|
||||
style={{ width: '100%' }}
|
||||
content={<div className={stl.popupNameContent}>{r.url}</div>}
|
||||
>
|
||||
<div className={stl.popupNameTrigger}>{r.name}</div>
|
||||
</Popup>
|
||||
<Popup style={{ width: '100%' }} content={<div className={stl.popupNameContent}>{r.url}</div>}>
|
||||
<div className={stl.popupNameTrigger}>{r.name}</div>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderStart(r) {
|
||||
return (
|
||||
<div className="flex justify-between items-center grow-0 w-full">
|
||||
<span>
|
||||
{Duration.fromMillis(r.time).toFormat('mm:ss.SSS')}
|
||||
</span>
|
||||
<Button
|
||||
<span>{Duration.fromMillis(r.time).toFormat('mm:ss.SSS')}</span>
|
||||
{/* <Button
|
||||
variant="text"
|
||||
className="right-0 text-xs uppercase p-2 color-gray-500 hover:color-teal"
|
||||
onClick={(e) => {
|
||||
|
|
@ -72,9 +67,9 @@ export function renderStart(r) {
|
|||
}}
|
||||
>
|
||||
Jump
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
</Button> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderXHRText = () => (
|
||||
|
|
@ -243,39 +238,45 @@ export default class NetworkContent extends React.PureComponent {
|
|||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={this.onFilterChange}
|
||||
height={28}
|
||||
/>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<InfoLine>
|
||||
<InfoLine.Point label={filtered.length} value=" requests" />
|
||||
<InfoLine.Point
|
||||
label={formatBytes(transferredSize)}
|
||||
value="transferred"
|
||||
display={transferredSize > 0}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={formatBytes(resourcesSize)}
|
||||
value="resources"
|
||||
display={resourcesSize > 0}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={formatMs(domBuildingTime)}
|
||||
value="DOM Building Time"
|
||||
display={domBuildingTime != null}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={domContentLoadedTime && formatMs(domContentLoadedTime.value)}
|
||||
value="DOMContentLoaded"
|
||||
display={domContentLoadedTime != null}
|
||||
dotColor={DOM_LOADED_TIME_COLOR}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={loadTime && formatMs(loadTime.value)}
|
||||
value="Load"
|
||||
display={loadTime != null}
|
||||
dotColor={LOAD_TIME_COLOR}
|
||||
/>
|
||||
</InfoLine>
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<div>
|
||||
<Toggler checked={true} name="test" onChange={() => {}} label="4xx-5xx Only" />
|
||||
</div>
|
||||
<InfoLine>
|
||||
<InfoLine.Point label={filtered.length} value=" requests" />
|
||||
<InfoLine.Point
|
||||
label={formatBytes(transferredSize)}
|
||||
value="transferred"
|
||||
display={transferredSize > 0}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={formatBytes(resourcesSize)}
|
||||
value="resources"
|
||||
display={resourcesSize > 0}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={formatMs(domBuildingTime)}
|
||||
value="DOM Building Time"
|
||||
display={domBuildingTime != null}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={domContentLoadedTime && formatMs(domContentLoadedTime.value)}
|
||||
value="DOMContentLoaded"
|
||||
display={domContentLoadedTime != null}
|
||||
dotColor={DOM_LOADED_TIME_COLOR}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={loadTime && formatMs(loadTime.value)}
|
||||
value="Load"
|
||||
display={loadTime != null}
|
||||
dotColor={LOAD_TIME_COLOR}
|
||||
/>
|
||||
</InfoLine>
|
||||
</div>
|
||||
<NoContent
|
||||
title={
|
||||
<div className="capitalize flex items-center mt-16">
|
||||
|
|
@ -296,11 +297,11 @@ export default class NetworkContent extends React.PureComponent {
|
|||
activeIndex={lastIndex}
|
||||
>
|
||||
{[
|
||||
{
|
||||
label: 'Start',
|
||||
width: 120,
|
||||
render: renderStart,
|
||||
},
|
||||
// {
|
||||
// label: 'Start',
|
||||
// width: 120,
|
||||
// render: renderStart,
|
||||
// },
|
||||
{
|
||||
label: 'Status',
|
||||
dataKey: 'status',
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { Controls as PlayerControls, connectPlayer } from 'Player';
|
||||
import {
|
||||
AreaChart,
|
||||
AreaChart,
|
||||
Area,
|
||||
ComposedChart,
|
||||
Line,
|
||||
XAxis,
|
||||
XAxis,
|
||||
YAxis,
|
||||
Tooltip,
|
||||
Tooltip,
|
||||
ResponsiveContainer,
|
||||
ReferenceLine,
|
||||
CartesianGrid,
|
||||
|
|
@ -23,40 +23,35 @@ import stl from './performance.module.css';
|
|||
import BottomBlock from '../BottomBlock';
|
||||
import InfoLine from '../BottomBlock/InfoLine';
|
||||
|
||||
|
||||
const CPU_VISUAL_OFFSET = 10;
|
||||
|
||||
|
||||
const FPS_COLOR = '#C5E5E7';
|
||||
const FPS_STROKE_COLOR = "#92C7CA";
|
||||
const FPS_LOW_COLOR = "pink";
|
||||
const FPS_VERY_LOW_COLOR = "red";
|
||||
const CPU_COLOR = "#A8D1DE";
|
||||
const CPU_STROKE_COLOR = "#69A5B8";
|
||||
const FPS_STROKE_COLOR = '#92C7CA';
|
||||
const FPS_LOW_COLOR = 'pink';
|
||||
const FPS_VERY_LOW_COLOR = 'red';
|
||||
const CPU_COLOR = '#A8D1DE';
|
||||
const CPU_STROKE_COLOR = '#69A5B8';
|
||||
const USED_HEAP_COLOR = '#A9ABDC';
|
||||
const USED_HEAP_STROKE_COLOR = "#8588CF";
|
||||
const USED_HEAP_STROKE_COLOR = '#8588CF';
|
||||
const TOTAL_HEAP_STROKE_COLOR = '#4A4EB7';
|
||||
const NODES_COUNT_COLOR = "#C6A9DC";
|
||||
const NODES_COUNT_STROKE_COLOR = "#7360AC";
|
||||
const HIDDEN_SCREEN_COLOR = "#CCC";
|
||||
const NODES_COUNT_COLOR = '#C6A9DC';
|
||||
const NODES_COUNT_STROKE_COLOR = '#7360AC';
|
||||
const HIDDEN_SCREEN_COLOR = '#CCC';
|
||||
|
||||
|
||||
const CURSOR_COLOR = "#394EFF";
|
||||
const CURSOR_COLOR = '#394EFF';
|
||||
|
||||
const Gradient = ({ color, id }) => (
|
||||
<linearGradient id={ id } x1="-1" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={ color } stopOpacity={ 0.7 } />
|
||||
<stop offset="95%" stopColor={ color } stopOpacity={ 0.2 } />
|
||||
<linearGradient id={id} x1="-1" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={color} stopOpacity={0.7} />
|
||||
<stop offset="95%" stopColor={color} stopOpacity={0.2} />
|
||||
</linearGradient>
|
||||
);
|
||||
|
||||
|
||||
const TOTAL_HEAP = "Allocated Heap";
|
||||
const USED_HEAP = "JS Heap";
|
||||
const FPS = "Framerate";
|
||||
const CPU = "CPU Load";
|
||||
const NODES_COUNT = "Nodes Сount";
|
||||
|
||||
const TOTAL_HEAP = 'Allocated Heap';
|
||||
const USED_HEAP = 'JS Heap';
|
||||
const FPS = 'Framerate';
|
||||
const CPU = 'CPU Load';
|
||||
const NODES_COUNT = 'Nodes Сount';
|
||||
|
||||
const FPSTooltip = ({ active, payload }) => {
|
||||
if (!active || !payload || payload.length < 3) {
|
||||
|
|
@ -64,8 +59,8 @@ const FPSTooltip = ({ active, payload }) => {
|
|||
}
|
||||
if (payload[0].value === null) {
|
||||
return (
|
||||
<div className={ stl.tooltipWrapper } style={{ color: HIDDEN_SCREEN_COLOR }}>
|
||||
{"Page is not active. User switched the tab or hid the window."}
|
||||
<div className={stl.tooltipWrapper} style={{ color: HIDDEN_SCREEN_COLOR }}>
|
||||
{'Page is not active. User switched the tab or hid the window.'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -79,9 +74,9 @@ const FPSTooltip = ({ active, payload }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={ stl.tooltipWrapper } style={ style }>
|
||||
<span className="font-medium">{`${ FPS }: `}</span>
|
||||
{ Math.trunc(payload[0].value) }
|
||||
<div className={stl.tooltipWrapper} style={style}>
|
||||
<span className="font-medium">{`${FPS}: `}</span>
|
||||
{Math.trunc(payload[0].value)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -91,47 +86,47 @@ const CPUTooltip = ({ active, payload }) => {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={ stl.tooltipWrapper } >
|
||||
<span className="font-medium">{`${ CPU }: `}</span>
|
||||
{ payload[0].value - CPU_VISUAL_OFFSET }
|
||||
{"%"}
|
||||
<div className={stl.tooltipWrapper}>
|
||||
<span className="font-medium">{`${CPU}: `}</span>
|
||||
{payload[0].value - CPU_VISUAL_OFFSET}
|
||||
{'%'}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const HeapTooltip = ({ active, payload}) => {
|
||||
const HeapTooltip = ({ active, payload }) => {
|
||||
if (!active || payload.length < 2) return null;
|
||||
return (
|
||||
<div className={ stl.tooltipWrapper } >
|
||||
<div className={stl.tooltipWrapper}>
|
||||
<p>
|
||||
<span className="font-medium">{`${ TOTAL_HEAP }: `}</span>
|
||||
{ formatBytes(payload[0].value)}
|
||||
<span className="font-medium">{`${TOTAL_HEAP}: `}</span>
|
||||
{formatBytes(payload[0].value)}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium">{`${ USED_HEAP }: `}</span>
|
||||
{ formatBytes(payload[1].value)}
|
||||
<span className="font-medium">{`${USED_HEAP}: `}</span>
|
||||
{formatBytes(payload[1].value)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const NodesCountTooltip = ({ active, payload} ) => {
|
||||
const NodesCountTooltip = ({ active, payload }) => {
|
||||
if (!active || !payload || payload.length === 0) return null;
|
||||
return (
|
||||
<div className={ stl.tooltipWrapper } >
|
||||
<div className={stl.tooltipWrapper}>
|
||||
<p>
|
||||
<span className="font-medium">{`${ NODES_COUNT }: `}</span>
|
||||
{ payload[0].value }
|
||||
<span className="font-medium">{`${NODES_COUNT}: `}</span>
|
||||
{payload[0].value}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const TICKS_COUNT = 10;
|
||||
function generateTicks(data: Array<Timed>): Array<number> {
|
||||
if (data.length === 0) return [];
|
||||
const minTime = data[0].time;
|
||||
const maxTime = data[data.length-1].time;
|
||||
const maxTime = data[data.length - 1].time;
|
||||
|
||||
const ticks = [];
|
||||
const tickGap = (maxTime - minTime) / (TICKS_COUNT + 1);
|
||||
|
|
@ -159,8 +154,9 @@ function addFpsMetadata(data) {
|
|||
} else if (point.fps < LOW_FPS) {
|
||||
fpsLowMarker = LOW_FPS_MARKER_VALUE;
|
||||
}
|
||||
}
|
||||
if (point.fps == null ||
|
||||
}
|
||||
if (
|
||||
point.fps == null ||
|
||||
(i > 0 && data[i - 1].fps == null) //||
|
||||
//(i < data.length-1 && data[i + 1].fps == null)
|
||||
) {
|
||||
|
|
@ -174,17 +170,17 @@ function addFpsMetadata(data) {
|
|||
fpsLowMarker,
|
||||
fpsVeryLowMarker,
|
||||
hiddenScreenMarker,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@connect(state => ({
|
||||
userDeviceHeapSize: state.getIn([ "sessions", "current", "userDeviceHeapSize" ]),
|
||||
userDeviceMemorySize: state.getIn([ "sessions", "current", "userDeviceMemorySize" ]),
|
||||
@connect((state) => ({
|
||||
userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']),
|
||||
userDeviceMemorySize: state.getIn(['sessions', 'current', 'userDeviceMemorySize']),
|
||||
}))
|
||||
export default class Performance extends React.PureComponent {
|
||||
_timeTicks = generateTicks(this.props.performanceChartData)
|
||||
_data = addFpsMetadata(this.props.performanceChartData)
|
||||
_timeTicks = generateTicks(this.props.performanceChartData);
|
||||
_data = addFpsMetadata(this.props.performanceChartData);
|
||||
// state = {
|
||||
// totalHeap: false,
|
||||
// usedHeap: true,
|
||||
|
|
@ -197,7 +193,7 @@ export default class Performance extends React.PureComponent {
|
|||
if (!!point) {
|
||||
PlayerControls.jump(point.time);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onChartClick = (e) => {
|
||||
if (e === null) return;
|
||||
|
|
@ -206,10 +202,10 @@ export default class Performance extends React.PureComponent {
|
|||
if (!!point) {
|
||||
PlayerControls.jump(point.time);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
const {
|
||||
userDeviceHeapSize,
|
||||
userDeviceMemorySize,
|
||||
connType,
|
||||
|
|
@ -218,19 +214,19 @@ export default class Performance extends React.PureComponent {
|
|||
avaliability = {},
|
||||
} = this.props;
|
||||
const { fps, cpu, heap, nodes } = avaliability;
|
||||
const avaliableCount = [ fps, cpu, heap, nodes ].reduce((c, av) => av ? c + 1 : c, 0);
|
||||
const height = avaliableCount === 0 ? "0" : `${100 / avaliableCount}%`;
|
||||
const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0);
|
||||
const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`;
|
||||
|
||||
return (
|
||||
<BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
<div className="flex items-center">
|
||||
<span className="font-semibold color-gray-medium mr-4">Performance</span>
|
||||
<div className="flex items-center w-full">
|
||||
<div className="font-semibold color-gray-medium mr-auto">Performance</div>
|
||||
<InfoLine>
|
||||
<InfoLine.Point
|
||||
label="Device Heap Size"
|
||||
value={ formatBytes(userDeviceHeapSize) }
|
||||
display={ userDeviceHeapSize != null }
|
||||
value={formatBytes(userDeviceHeapSize)}
|
||||
display={true}
|
||||
/>
|
||||
{/* <InfoLine.Point */}
|
||||
{/* label="Device Memory Size" */}
|
||||
|
|
@ -238,55 +234,52 @@ export default class Performance extends React.PureComponent {
|
|||
{/* /> */}
|
||||
<InfoLine.Point
|
||||
label="Connection Type"
|
||||
value={ connType }
|
||||
display={ connType != null && connType !== "unknown" }
|
||||
value={connType}
|
||||
display={connType != null && connType !== 'unknown'}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label="Connection Speed"
|
||||
value={ connBandwidth >= 1000
|
||||
? `${ connBandwidth / 1000 } Mbps`
|
||||
: `${ connBandwidth } Kbps`
|
||||
value={
|
||||
connBandwidth >= 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps`
|
||||
}
|
||||
display={ connBandwidth != null }
|
||||
display={connBandwidth != null}
|
||||
/>
|
||||
</InfoLine>
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
{ fps &&
|
||||
<ResponsiveContainer height={ height }>
|
||||
{fps && (
|
||||
<ResponsiveContainer height={height}>
|
||||
<AreaChart
|
||||
onClick={ this.onChartClick }
|
||||
onClick={this.onChartClick}
|
||||
data={this._data}
|
||||
syncId="s"
|
||||
margin={{
|
||||
top: 0, right: 0, left: 0, bottom: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<defs>
|
||||
<Gradient id="fpsGradient" color={ FPS_COLOR } />
|
||||
<Gradient id="fpsGradient" color={FPS_COLOR} />
|
||||
</defs>
|
||||
{/* <CartesianGrid strokeDasharray="1 1" stroke="#ddd" horizontal={ false } /> */}
|
||||
{/* <CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" /> */}
|
||||
<XAxis
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
mirror
|
||||
orientation="top"
|
||||
tickLine={ false }
|
||||
tickFormatter={ durationFromMsFormatted }
|
||||
tick={{ fontSize: "12px" }}
|
||||
tickLine={false}
|
||||
tickFormatter={durationFromMsFormatted}
|
||||
tick={{ fontSize: '12px', fill: '#333' }}
|
||||
domain={[0, 'dataMax']}
|
||||
ticks={this._timeTicks}
|
||||
>
|
||||
<Label value="FPS" position="insideTopRight" className="fill-gray-medium" />
|
||||
<Label value="FPS" position="insideTopRight" className="fill-gray-darkest" />
|
||||
</XAxis>
|
||||
<YAxis
|
||||
axisLine={ false }
|
||||
tick={ false }
|
||||
mirror
|
||||
domain={[0, 85]}
|
||||
/>
|
||||
<YAxis axisLine={false} tick={false} mirror domain={[0, 85]} />
|
||||
{/* <YAxis */}
|
||||
{/* yAxisId="r" */}
|
||||
{/* axisLine={ false } */}
|
||||
|
|
@ -295,41 +288,41 @@ export default class Performance extends React.PureComponent {
|
|||
{/* domain={[0, 120]} */}
|
||||
{/* orientation="right" */}
|
||||
{/* /> */}
|
||||
<Area
|
||||
dataKey="fps"
|
||||
type="stepBefore"
|
||||
<Area
|
||||
dataKey="fps"
|
||||
type="stepBefore"
|
||||
stroke={FPS_STROKE_COLOR}
|
||||
fill="url(#fpsGradient)"
|
||||
dot={false}
|
||||
activeDot={{
|
||||
activeDot={{
|
||||
onClick: this.onDotClick,
|
||||
style: { cursor: "pointer" },
|
||||
}}
|
||||
isAnimationActive={ false }
|
||||
/>
|
||||
style: { cursor: 'pointer' },
|
||||
}}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Area
|
||||
dataKey="fpsLowMarker"
|
||||
dataKey="fpsLowMarker"
|
||||
type="stepBefore"
|
||||
stroke="none"
|
||||
fill={ FPS_LOW_COLOR }
|
||||
fill={FPS_LOW_COLOR}
|
||||
activeDot={false}
|
||||
isAnimationActive={ false }
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Area
|
||||
<Area
|
||||
dataKey="fpsVeryLowMarker"
|
||||
type="stepBefore"
|
||||
stroke="none"
|
||||
fill={ FPS_VERY_LOW_COLOR }
|
||||
fill={FPS_VERY_LOW_COLOR}
|
||||
activeDot={false}
|
||||
isAnimationActive={ false }
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Area
|
||||
dataKey="hiddenScreenMarker"
|
||||
type="stepBefore"
|
||||
stroke="none"
|
||||
fill={ HIDDEN_SCREEN_COLOR }
|
||||
fill={HIDDEN_SCREEN_COLOR}
|
||||
activeDot={false}
|
||||
isAnimationActive={ false }
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
{/* <Area */}
|
||||
{/* yAxisId="r" */}
|
||||
|
|
@ -346,101 +339,95 @@ export default class Performance extends React.PureComponent {
|
|||
{/* isAnimationActive={ false } */}
|
||||
{/* /> */}
|
||||
<ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} />
|
||||
<Tooltip
|
||||
content={FPSTooltip}
|
||||
filterNull={ false }
|
||||
/>
|
||||
<Tooltip content={FPSTooltip} filterNull={false} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
{ cpu &&
|
||||
<ResponsiveContainer height={ height }>
|
||||
)}
|
||||
{cpu && (
|
||||
<ResponsiveContainer height={height}>
|
||||
<AreaChart
|
||||
onClick={ this.onChartClick }
|
||||
onClick={this.onChartClick}
|
||||
data={this._data}
|
||||
syncId="s"
|
||||
margin={{
|
||||
top: 0, right: 0, left: 0, bottom: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<defs>
|
||||
<Gradient id="cpuGradient" color={ CPU_COLOR } />
|
||||
<Gradient id="cpuGradient" color={CPU_COLOR} />
|
||||
</defs>
|
||||
{/* <CartesianGrid strokeDasharray="3 3" vertical={ false } stroke="#EEEEEE" /> */}
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
mirror
|
||||
orientation="top"
|
||||
tickLine={false}
|
||||
tickFormatter={()=> ""}
|
||||
domain={[0, 'dataMax']}
|
||||
ticks={this._timeTicks}
|
||||
>
|
||||
<Label value="CPU" position="insideTopRight" className="fill-gray-medium" />
|
||||
</XAxis>
|
||||
<YAxis
|
||||
axisLine={ false }
|
||||
tick={ false }
|
||||
mirror
|
||||
domain={[ 0, 120]}
|
||||
orientation="right"
|
||||
/>
|
||||
<Area
|
||||
dataKey="cpu"
|
||||
type="monotone"
|
||||
stroke={CPU_STROKE_COLOR}
|
||||
// fill="none"
|
||||
fill="url(#cpuGradient)"
|
||||
dot={false}
|
||||
activeDot={{
|
||||
onClick: this.onDotClick,
|
||||
style: { cursor: "pointer" },
|
||||
}}
|
||||
isAnimationActive={ false }
|
||||
/>
|
||||
<Area
|
||||
dataKey="hiddenScreenMarker"
|
||||
type="stepBefore"
|
||||
stroke="none"
|
||||
fill={ HIDDEN_SCREEN_COLOR }
|
||||
activeDot={false}
|
||||
isAnimationActive={ false }
|
||||
/>
|
||||
<ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} />
|
||||
<Tooltip
|
||||
content={CPUTooltip}
|
||||
filterNull={ false }
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
|
||||
{ heap &&
|
||||
<ResponsiveContainer height={ height }>
|
||||
<ComposedChart
|
||||
onClick={ this.onChartClick }
|
||||
data={this._data}
|
||||
margin={{
|
||||
top: 0, right: 0, left: 0, bottom: 0,
|
||||
}}
|
||||
syncId="s"
|
||||
>
|
||||
<defs>
|
||||
<Gradient id="usedHeapGradient" color={ USED_HEAP_COLOR } />
|
||||
</defs>
|
||||
{/* <CartesianGrid strokeDasharray="1 1" stroke="#ddd" horizontal={ false } /> */}
|
||||
<XAxis
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
mirror
|
||||
orientation="top"
|
||||
tickLine={false}
|
||||
tickFormatter={()=> ""} // tick={false} + this._timeTicks to cartesian array
|
||||
tickFormatter={() => ''}
|
||||
domain={[0, 'dataMax']}
|
||||
ticks={this._timeTicks}
|
||||
>
|
||||
<Label value="HEAP" position="insideTopRight" className="fill-gray-medium" />
|
||||
<Label value="CPU" position="insideTopRight" className="fill-gray-darkest" />
|
||||
</XAxis>
|
||||
<YAxis axisLine={false} tick={false} mirror domain={[0, 120]} orientation="right" />
|
||||
<Area
|
||||
dataKey="cpu"
|
||||
type="monotone"
|
||||
stroke={CPU_STROKE_COLOR}
|
||||
// fill="none"
|
||||
fill="url(#cpuGradient)"
|
||||
dot={false}
|
||||
activeDot={{
|
||||
onClick: this.onDotClick,
|
||||
style: { cursor: 'pointer' },
|
||||
}}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Area
|
||||
dataKey="hiddenScreenMarker"
|
||||
type="stepBefore"
|
||||
stroke="none"
|
||||
fill={HIDDEN_SCREEN_COLOR}
|
||||
activeDot={false}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} />
|
||||
<Tooltip content={CPUTooltip} filterNull={false} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
|
||||
{heap && (
|
||||
<ResponsiveContainer height={height}>
|
||||
<ComposedChart
|
||||
onClick={this.onChartClick}
|
||||
data={this._data}
|
||||
margin={{
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
syncId="s"
|
||||
>
|
||||
<defs>
|
||||
<Gradient id="usedHeapGradient" color={USED_HEAP_COLOR} />
|
||||
</defs>
|
||||
{/* <CartesianGrid strokeDasharray="1 1" stroke="#ddd" horizontal={ false } /> */}
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
mirror
|
||||
orientation="top"
|
||||
tickLine={false}
|
||||
tickFormatter={() => ''} // tick={false} + this._timeTicks to cartesian array
|
||||
domain={[0, 'dataMax']}
|
||||
ticks={this._timeTicks}
|
||||
>
|
||||
<Label value="HEAP" position="insideTopRight" className="fill-gray-darkest" />
|
||||
</XAxis>
|
||||
<YAxis
|
||||
axisLine={false}
|
||||
|
|
@ -448,19 +435,19 @@ export default class Performance extends React.PureComponent {
|
|||
mirror
|
||||
// Hack to keep only end tick
|
||||
minTickGap={Number.MAX_SAFE_INTEGER}
|
||||
domain={[0, max => max*1.2]}
|
||||
domain={[0, (max) => max * 1.2]}
|
||||
/>
|
||||
<Line
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="totalHeap"
|
||||
// fill="url(#totalHeapGradient)"
|
||||
stroke={TOTAL_HEAP_STROKE_COLOR}
|
||||
dot={false}
|
||||
activeDot={{
|
||||
activeDot={{
|
||||
onClick: this.onDotClick,
|
||||
style: { cursor: "pointer" },
|
||||
style: { cursor: 'pointer' },
|
||||
}}
|
||||
isAnimationActive={ false }
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<Area
|
||||
dataKey="usedHeap"
|
||||
|
|
@ -468,81 +455,78 @@ export default class Performance extends React.PureComponent {
|
|||
fill="url(#usedHeapGradient)"
|
||||
stroke={USED_HEAP_STROKE_COLOR}
|
||||
dot={false}
|
||||
activeDot={{
|
||||
activeDot={{
|
||||
onClick: this.onDotClick,
|
||||
style: { cursor: "pointer" },
|
||||
}}
|
||||
isAnimationActive={ false }
|
||||
style: { cursor: 'pointer' },
|
||||
}}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} />
|
||||
<Tooltip
|
||||
content={HeapTooltip}
|
||||
filterNull={ false }
|
||||
/>
|
||||
<Tooltip content={HeapTooltip} filterNull={false} />
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
{ nodes &&
|
||||
<ResponsiveContainer height={ height }>
|
||||
)}
|
||||
{nodes && (
|
||||
<ResponsiveContainer height={height}>
|
||||
<AreaChart
|
||||
onClick={ this.onChartClick }
|
||||
onClick={this.onChartClick}
|
||||
data={this._data}
|
||||
syncId="s"
|
||||
margin={{
|
||||
top: 0, right: 0, left: 0, bottom: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<defs>
|
||||
<Gradient id="nodesGradient" color={ NODES_COUNT_COLOR } />
|
||||
<Gradient id="nodesGradient" color={NODES_COUNT_COLOR} />
|
||||
</defs>
|
||||
{/* <CartesianGrid strokeDasharray="1 1" stroke="#ddd" horizontal={ false } /> */}
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
mirror
|
||||
orientation="top"
|
||||
tickLine={false}
|
||||
tickFormatter={()=> ""}
|
||||
domain={[0, 'dataMax']}
|
||||
ticks={this._timeTicks}
|
||||
>
|
||||
<Label value="NODES" position="insideTopRight" className="fill-gray-medium" />
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
type="number"
|
||||
mirror
|
||||
orientation="top"
|
||||
tickLine={false}
|
||||
tickFormatter={() => ''}
|
||||
domain={[0, 'dataMax']}
|
||||
ticks={this._timeTicks}
|
||||
>
|
||||
<Label value="NODES" position="insideTopRight" className="fill-gray-darkest" />
|
||||
</XAxis>
|
||||
<YAxis
|
||||
axisLine={ false }
|
||||
tick={ false }
|
||||
axisLine={false}
|
||||
tick={false}
|
||||
mirror
|
||||
orientation="right"
|
||||
domain={[0, max => max*1.2]}
|
||||
domain={[0, (max) => max * 1.2]}
|
||||
/>
|
||||
<Area
|
||||
dataKey="nodesCount"
|
||||
type="monotone"
|
||||
<Area
|
||||
dataKey="nodesCount"
|
||||
type="monotone"
|
||||
stroke={NODES_COUNT_STROKE_COLOR}
|
||||
// fill="none"
|
||||
fill="url(#nodesGradient)"
|
||||
dot={false}
|
||||
activeDot={{
|
||||
activeDot={{
|
||||
onClick: this.onDotClick,
|
||||
style: { cursor: "pointer" },
|
||||
}}
|
||||
isAnimationActive={ false }
|
||||
/>
|
||||
<ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} />
|
||||
<Tooltip
|
||||
content={NodesCountTooltip}
|
||||
filterNull={ false }
|
||||
style: { cursor: 'pointer' },
|
||||
}}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
<ReferenceLine x={performanceChartTime} stroke={CURSOR_COLOR} />
|
||||
<Tooltip content={NodesCountTooltip} filterNull={false} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
}
|
||||
)}
|
||||
</BottomBlock.Content>
|
||||
</BottomBlock>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ConnectedPerformance = connectPlayer(state => ({
|
||||
export const ConnectedPerformance = connectPlayer((state) => ({
|
||||
performanceChartTime: state.performanceChartTime,
|
||||
performanceChartData: state.performanceChartData,
|
||||
connType: state.connType,
|
||||
|
|
|
|||
|
|
@ -3,4 +3,5 @@
|
|||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #ccc;
|
||||
color: $gray-dark !important;
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ import Overlay from './Overlay';
|
|||
import stl from './player.module.css';
|
||||
import { updateLastPlayedSession } from 'Duck/sessions';
|
||||
import OverviewPanel from '../OverviewPanel';
|
||||
import NetworkPanel from 'Shared/DevTools/NetworkPanel/NetworkPanel';
|
||||
|
||||
@connectPlayer(state => ({
|
||||
live: state.live,
|
||||
|
|
@ -112,7 +113,8 @@ export default class Player extends React.PureComponent {
|
|||
<Console />
|
||||
}
|
||||
{ bottomBlock === NETWORK &&
|
||||
<Network />
|
||||
// <Network />
|
||||
<NetworkPanel />
|
||||
}
|
||||
{ bottomBlock === STACKEVENTS &&
|
||||
<StackEvents />
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ $offset: 10px;
|
|||
.headers {
|
||||
box-shadow: 0 1px 2px 0 $gray-light;
|
||||
background-color: $gray-lightest;
|
||||
color: $gray-medium;
|
||||
color: $gray-darkest;
|
||||
font-size: 12px;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
|
|
@ -47,6 +47,10 @@ $offset: 10px;
|
|||
.row {
|
||||
display: flex;
|
||||
padding: 0 $offset;
|
||||
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
/*align-items: center;
|
||||
cursor: pointer;
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import stl from './bottomBlock.module.css';
|
||||
|
||||
const BottomBlock = ({
|
||||
children = null,
|
||||
className = '',
|
||||
additionalHeight = 0,
|
||||
...props
|
||||
}) => (
|
||||
<div className={ cn(stl.wrapper, "flex flex-col mb-2") } { ...props } >
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
|
||||
BottomBlock.displayName = 'BottomBlock';
|
||||
|
||||
export default BottomBlock;
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import stl from './content.module.css';
|
||||
|
||||
const Content = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}) => (
|
||||
<div className={ cn(className, stl.content) } { ...props } >
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
|
||||
Content.displayName = 'Content';
|
||||
|
||||
export default Content;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import cn from 'classnames';
|
||||
import { closeBottomBlock } from 'Duck/components/player';
|
||||
import { Input, CloseButton } from 'UI';
|
||||
import stl from './header.module.css';
|
||||
|
||||
const Header = ({
|
||||
children,
|
||||
className,
|
||||
closeBottomBlock,
|
||||
onFilterChange,
|
||||
showClose = true,
|
||||
...props
|
||||
}) => (
|
||||
<div className={ cn("relative border-r border-l py-1", stl.header) } >
|
||||
<div className={ cn("w-full h-full flex justify-between items-center", className) } >
|
||||
<div className="w-full flex items-center justify-between">{ children }</div>
|
||||
{ showClose && <CloseButton onClick={ closeBottomBlock } size="18" className="ml-2" /> }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Header.displayName = 'Header';
|
||||
|
||||
export default connect(null, { closeBottomBlock })(Header);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import cls from './infoLine.module.css';
|
||||
|
||||
const InfoLine = ({ children }) => (
|
||||
<div className={ cls.info }>
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
|
||||
const Point = ({ label = '', value = '', display=true, color, dotColor }) => display
|
||||
? <div className={ cls.infoPoint } style={{ color }}>
|
||||
{ dotColor != null && <div className={ cn(cls.dot, `bg-${dotColor}`) } /> }
|
||||
<span className={cls.label}>{ `${label}` }</span> { value }
|
||||
</div>
|
||||
: null;
|
||||
|
||||
InfoLine.Point = Point;
|
||||
|
||||
export default InfoLine;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
.wrapper {
|
||||
background: $white;
|
||||
/* padding-right: 10px; */
|
||||
/* border: solid thin $gray-light; */
|
||||
height: 300px;
|
||||
|
||||
border-top: thin dashed #cccccc;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.content {
|
||||
height: 86%;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
.header {
|
||||
padding: 0 10px;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid $gray-light;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import BottomBlock from './BottomBlock';
|
||||
import Header from './Header';
|
||||
import Content from './Content';
|
||||
|
||||
BottomBlock.Header = Header;
|
||||
BottomBlock.Content = Content;
|
||||
|
||||
export default BottomBlock;
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
|
||||
.info {
|
||||
padding-left: 10px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
& >.infoPoint {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&:not(:last-child):after {
|
||||
content: '';
|
||||
margin: 0 12px;
|
||||
height: 30px;
|
||||
border-right: 1px solid $gray-light-shade;
|
||||
}
|
||||
& .label {
|
||||
font-weight: 500;
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// import { NONE, CONSOLE, NETWORK, STACKEVENTS, REDUX_STATE, PROFILER, PERFORMANCE, GRAPHQL } from 'Duck/components/player';
|
||||
//
|
||||
//
|
||||
// export default {
|
||||
// [NONE]: {
|
||||
// Component: null,
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
import React, { useState } from 'react';
|
||||
import cn from 'classnames';
|
||||
// import { connectPlayer } from 'Player';
|
||||
import { QuestionMarkHint, Popup, Tabs, Input, NoContent, Icon, Toggler, Button } from 'UI';
|
||||
import { getRE } from 'App/utils';
|
||||
import { TYPES } from 'Types/session/resource';
|
||||
import { formatBytes } from 'App/utils';
|
||||
import { formatMs } from 'App/date';
|
||||
|
||||
import TimeTable from '../TimeTable';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import InfoLine from '../BottomBlock/InfoLine';
|
||||
// import stl from './network.module.css';
|
||||
import { Duration } from 'luxon';
|
||||
import { connectPlayer, jump, pause } from 'Player';
|
||||
import { useModal } from 'App/components/Modal';
|
||||
import FetchDetailsModal from 'Shared/FetchDetailsModal';
|
||||
|
||||
const ALL = 'ALL';
|
||||
const XHR = 'xhr';
|
||||
const JS = 'js';
|
||||
const CSS = 'css';
|
||||
const IMG = 'img';
|
||||
const MEDIA = 'media';
|
||||
const OTHER = 'other';
|
||||
|
||||
const TAB_TO_TYPE_MAP: any = {
|
||||
[XHR]: TYPES.XHR,
|
||||
[JS]: TYPES.JS,
|
||||
[CSS]: TYPES.CSS,
|
||||
[IMG]: TYPES.IMG,
|
||||
[MEDIA]: TYPES.MEDIA,
|
||||
[OTHER]: TYPES.OTHER,
|
||||
};
|
||||
const TABS: any = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER].map((tab) => ({
|
||||
text: tab,
|
||||
key: tab,
|
||||
}));
|
||||
|
||||
const DOM_LOADED_TIME_COLOR = 'teal';
|
||||
const LOAD_TIME_COLOR = 'red';
|
||||
|
||||
export function renderType(r) {
|
||||
return (
|
||||
<Popup style={{ width: '100%' }} content={<div>{r.type}</div>}>
|
||||
<div>{r.type}</div>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderName(r: any) {
|
||||
return (
|
||||
<Popup style={{ width: '100%' }} content={<div>{r.url}</div>}>
|
||||
<div>{r.name}</div>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderStart(r: any) {
|
||||
return (
|
||||
<div className="flex justify-between items-center grow-0 w-full">
|
||||
<span>{Duration.fromMillis(r.time).toFormat('mm:ss.SSS')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const renderXHRText = () => (
|
||||
<span className="flex items-center">
|
||||
{XHR}
|
||||
<QuestionMarkHint
|
||||
onHover={true}
|
||||
content={
|
||||
<>
|
||||
Use our{' '}
|
||||
<a
|
||||
className="color-teal underline"
|
||||
target="_blank"
|
||||
href="https://docs.openreplay.com/plugins/fetch"
|
||||
>
|
||||
Fetch plugin
|
||||
</a>
|
||||
{' to capture HTTP requests and responses, including status codes and bodies.'} <br />
|
||||
We also provide{' '}
|
||||
<a
|
||||
className="color-teal underline"
|
||||
target="_blank"
|
||||
href="https://docs.openreplay.com/plugins/graphql"
|
||||
>
|
||||
support for GraphQL
|
||||
</a>
|
||||
{' for easy debugging of your queries.'}
|
||||
</>
|
||||
}
|
||||
className="ml-1"
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
function renderSize(r: any) {
|
||||
if (r.responseBodySize) return formatBytes(r.responseBodySize);
|
||||
let triggerText;
|
||||
let content;
|
||||
if (r.decodedBodySize == null) {
|
||||
triggerText = 'x';
|
||||
content = 'Not captured';
|
||||
} else {
|
||||
const headerSize = r.headerSize || 0;
|
||||
const encodedSize = r.encodedBodySize || 0;
|
||||
const transferred = headerSize + encodedSize;
|
||||
const showTransferred = r.headerSize != null;
|
||||
|
||||
triggerText = formatBytes(r.decodedBodySize);
|
||||
content = (
|
||||
<ul>
|
||||
{showTransferred && (
|
||||
<li>{`${formatBytes(r.encodedBodySize + headerSize)} transfered over network`}</li>
|
||||
)}
|
||||
<li>{`Resource size: ${formatBytes(r.decodedBodySize)} `}</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popup style={{ width: '100%' }} content={content}>
|
||||
<div>{triggerText}</div>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
export function renderDuration(r: any) {
|
||||
if (!r.success) return 'x';
|
||||
|
||||
const text = `${Math.floor(r.duration)}ms`;
|
||||
if (!r.isRed() && !r.isYellow()) return text;
|
||||
|
||||
let tooltipText;
|
||||
let className = 'w-full h-full flex items-center ';
|
||||
if (r.isYellow()) {
|
||||
tooltipText = 'Slower than average';
|
||||
className += 'warn color-orange';
|
||||
} else {
|
||||
tooltipText = 'Much slower than average';
|
||||
className += 'error color-red';
|
||||
}
|
||||
|
||||
return (
|
||||
<Popup style={{ width: '100%' }} content={tooltipText}>
|
||||
<div> {text} </div>
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
location: any;
|
||||
resources: any;
|
||||
domContentLoadedTime: any;
|
||||
loadTime: any;
|
||||
playing: boolean;
|
||||
domBuildingTime: any;
|
||||
fetchPresented: boolean;
|
||||
listNow: any;
|
||||
|
||||
currentIndex: any;
|
||||
time: any;
|
||||
}
|
||||
function NetworkPanel(props: Props) {
|
||||
const {
|
||||
resources,
|
||||
time,
|
||||
currentIndex,
|
||||
domContentLoadedTime,
|
||||
loadTime,
|
||||
playing,
|
||||
domBuildingTime,
|
||||
fetchPresented,
|
||||
listNow,
|
||||
} = props;
|
||||
const { showModal, hideModal } = useModal();
|
||||
const [activeTab, setActiveTab] = useState(ALL);
|
||||
const [filter, setFilter] = useState('');
|
||||
const [showOnlyErrors, setShowOnlyErrors] = useState(false);
|
||||
const onTabClick = (activeTab: any) => setActiveTab(activeTab);
|
||||
const onFilterChange = ({ target: { value } }: any) => setFilter(value);
|
||||
const additionalHeight = 0;
|
||||
|
||||
const resourcesSize = resources.reduce(
|
||||
(sum: any, { decodedBodySize }: any) => sum + (decodedBodySize || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const transferredSize = resources.reduce(
|
||||
(sum: any, { headerSize, encodedBodySize }: any) =>
|
||||
sum + (headerSize || 0) + (encodedBodySize || 0),
|
||||
0
|
||||
);
|
||||
|
||||
const filterRE = getRE(filter, 'i');
|
||||
let filtered = resources.filter(
|
||||
({ type, name }: any) =>
|
||||
filterRE.test(name) && (activeTab === ALL || type === TAB_TO_TYPE_MAP[activeTab])
|
||||
);
|
||||
const lastIndex = currentIndex || filtered.filter((item: any) => item.time <= time).length - 1;
|
||||
const referenceLines = [];
|
||||
if (domContentLoadedTime != null) {
|
||||
referenceLines.push({
|
||||
time: domContentLoadedTime.time,
|
||||
color: DOM_LOADED_TIME_COLOR,
|
||||
});
|
||||
}
|
||||
if (loadTime != null) {
|
||||
referenceLines.push({
|
||||
time: loadTime.time,
|
||||
color: LOAD_TIME_COLOR,
|
||||
});
|
||||
}
|
||||
|
||||
const onRowClick = (row: any) => {
|
||||
showModal(<FetchDetailsModal resource={row} />, { right: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BottomBlock style={{ height: 300 + additionalHeight + 'px' }} className="border">
|
||||
<BottomBlock.Header>
|
||||
<div className="flex items-center">
|
||||
<span className="font-semibold color-gray-medium mr-4">Network</span>
|
||||
<Tabs
|
||||
className="uppercase"
|
||||
tabs={TABS}
|
||||
active={activeTab}
|
||||
onClick={onTabClick}
|
||||
border={false}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
className="input-small"
|
||||
placeholder="Filter by name"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={onFilterChange}
|
||||
height={28}
|
||||
/>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<div>
|
||||
<Toggler checked={showOnlyErrors} name="test" onChange={() => setShowOnlyErrors(!showOnlyErrors)} label="4xx-5xx Only" />
|
||||
</div>
|
||||
<InfoLine>
|
||||
<InfoLine.Point label={filtered.length} value=" requests" />
|
||||
<InfoLine.Point
|
||||
label={formatBytes(transferredSize)}
|
||||
value="transferred"
|
||||
display={transferredSize > 0}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={formatBytes(resourcesSize)}
|
||||
value="resources"
|
||||
display={resourcesSize > 0}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={formatMs(domBuildingTime)}
|
||||
value="DOM Building Time"
|
||||
display={domBuildingTime != null}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={domContentLoadedTime && formatMs(domContentLoadedTime.value)}
|
||||
value="DOMContentLoaded"
|
||||
display={domContentLoadedTime != null}
|
||||
dotColor={DOM_LOADED_TIME_COLOR}
|
||||
/>
|
||||
<InfoLine.Point
|
||||
label={loadTime && formatMs(loadTime.value)}
|
||||
value="Load"
|
||||
display={loadTime != null}
|
||||
dotColor={LOAD_TIME_COLOR}
|
||||
/>
|
||||
</InfoLine>
|
||||
</div>
|
||||
<NoContent
|
||||
title={
|
||||
<div className="capitalize flex items-center mt-16">
|
||||
<Icon name="info-circle" className="mr-2" size="18" />
|
||||
No Data
|
||||
</div>
|
||||
}
|
||||
size="small"
|
||||
show={filtered.length === 0}
|
||||
>
|
||||
<TimeTable
|
||||
rows={filtered}
|
||||
referenceLines={referenceLines}
|
||||
renderPopup
|
||||
// navigation
|
||||
onRowClick={onRowClick}
|
||||
additionalHeight={additionalHeight}
|
||||
activeIndex={lastIndex}
|
||||
>
|
||||
{[
|
||||
// {
|
||||
// label: 'Start',
|
||||
// width: 120,
|
||||
// render: renderStart,
|
||||
// },
|
||||
{
|
||||
label: 'Status',
|
||||
dataKey: 'status',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
label: 'Type',
|
||||
dataKey: 'type',
|
||||
width: 90,
|
||||
render: renderType,
|
||||
},
|
||||
{
|
||||
label: 'Name',
|
||||
width: 240,
|
||||
render: renderName,
|
||||
},
|
||||
{
|
||||
label: 'Size',
|
||||
width: 60,
|
||||
render: renderSize,
|
||||
},
|
||||
{
|
||||
label: 'Time',
|
||||
width: 80,
|
||||
render: renderDuration,
|
||||
},
|
||||
]}
|
||||
</TimeTable>
|
||||
</NoContent>
|
||||
</BottomBlock.Content>
|
||||
</BottomBlock>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectPlayer((state: any) => ({
|
||||
location: state.location,
|
||||
resources: state.resourceList,
|
||||
domContentLoadedTime: state.domContentLoadedTime,
|
||||
loadTime: state.loadTime,
|
||||
// time: state.time,
|
||||
playing: state.playing,
|
||||
domBuildingTime: state.domBuildingTime,
|
||||
fetchPresented: state.fetchList.length > 0,
|
||||
listNow: state.resourceListNow,
|
||||
}))(NetworkPanel);
|
||||
96
frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx
Normal file
96
frontend/app/components/shared/DevTools/TimeTable/BarRow.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
import { Popup } from 'UI';
|
||||
import { percentOf } from 'App/utils';
|
||||
import styles from './barRow.module.css'
|
||||
import tableStyles from './timeTable.module.css';
|
||||
import React from 'react';
|
||||
|
||||
const formatTime = time => time < 1000 ? `${time.toFixed(2)}ms` : `${time / 1000}s`;
|
||||
|
||||
interface Props {
|
||||
resource: {
|
||||
time: number
|
||||
ttfb?: number
|
||||
duration?: number
|
||||
key: string
|
||||
}
|
||||
popup?: boolean
|
||||
timestart: number
|
||||
timewidth: number
|
||||
}
|
||||
|
||||
// TODO: If request has no duration, set duration to 0.2s. Enforce existence of duration in the future.
|
||||
const BarRow = ({ resource: { time, ttfb = 0, duration = 200, key }, popup = false, timestart = 0, timewidth }: Props) => {
|
||||
const timeOffset = time - timestart;
|
||||
ttfb = ttfb || 0;
|
||||
const trigger = (
|
||||
<div
|
||||
className={styles.barWrapper}
|
||||
style={{
|
||||
left: `${percentOf(timeOffset, timewidth)}%`,
|
||||
right: `${100 - percentOf(timeOffset + duration, timewidth)}%`,
|
||||
minWidth: '5px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={styles.ttfbBar}
|
||||
style={{
|
||||
width: `${percentOf(ttfb, duration)}%`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={styles.downloadBar}
|
||||
style={{
|
||||
width: `${percentOf(duration - ttfb, duration)}%`,
|
||||
minWidth: '5px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
if (!popup) return <div key={key} className={tableStyles.row} > {trigger} </div>;
|
||||
|
||||
return (
|
||||
<div key={key} className={tableStyles.row} >
|
||||
<Popup
|
||||
basic
|
||||
content={
|
||||
<React.Fragment>
|
||||
{ttfb != null &&
|
||||
<div className={styles.popupRow}>
|
||||
<div className={styles.title}>{'Waiting (TTFB)'}</div>
|
||||
<div className={styles.popupBarWrapper} >
|
||||
<div
|
||||
className={styles.ttfbBar}
|
||||
style={{
|
||||
left: 0,
|
||||
width: `${percentOf(ttfb, duration)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.time} >{formatTime(ttfb)}</div>
|
||||
</div>
|
||||
}
|
||||
<div className={styles.popupRow}>
|
||||
<div className={styles.title} >{'Content Download'}</div>
|
||||
<div className={styles.popupBarWrapper}>
|
||||
<div
|
||||
className={styles.downloadBar}
|
||||
style={{
|
||||
left: `${percentOf(ttfb, duration)}%`,
|
||||
width: `${percentOf(duration - ttfb, duration)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.time}>{formatTime(duration - ttfb)}</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
}
|
||||
size="mini"
|
||||
position="top center"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BarRow.displayName = "BarRow";
|
||||
|
||||
export default BarRow;
|
||||
318
frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx
Normal file
318
frontend/app/components/shared/DevTools/TimeTable/TimeTable.tsx
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
import React from 'react';
|
||||
import { List, AutoSizer } from 'react-virtualized';
|
||||
import cn from 'classnames';
|
||||
import { Duration } from "luxon";
|
||||
import { NoContent, IconButton, Button } from 'UI';
|
||||
import { percentOf } from 'App/utils';
|
||||
|
||||
import BarRow from './BarRow';
|
||||
import stl from './timeTable.module.css';
|
||||
|
||||
import autoscrollStl from '../autoscroll.module.css'; //aaa
|
||||
|
||||
type Timed = {
|
||||
time: number;
|
||||
};
|
||||
|
||||
type Durationed = {
|
||||
duration: number;
|
||||
};
|
||||
|
||||
type CanBeRed = {
|
||||
//+isRed: boolean,
|
||||
isRed: () => boolean;
|
||||
};
|
||||
|
||||
interface Row extends Timed, Durationed, CanBeRed {
|
||||
[key: string]: any, key: string
|
||||
}
|
||||
|
||||
type Line = {
|
||||
color: string; // Maybe use typescript?
|
||||
hint?: string;
|
||||
onClick?: any;
|
||||
} & Timed;
|
||||
|
||||
type Column = {
|
||||
label: string;
|
||||
width: number;
|
||||
dataKey?: string;
|
||||
render?: (row: any) => void
|
||||
referenceLines?: Array<Line>;
|
||||
style?: React.CSSProperties;
|
||||
} & RenderOrKey;
|
||||
|
||||
// type RenderOrKey = { // Disjoint?
|
||||
// render: Row => React.Node
|
||||
// } | {
|
||||
// dataKey: string,
|
||||
// }
|
||||
type RenderOrKey =
|
||||
| {
|
||||
render?: (row: Row) => React.ReactNode;
|
||||
key?: string;
|
||||
}
|
||||
| {
|
||||
dataKey: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
rows: Array<Row>;
|
||||
children: Array<Column>;
|
||||
tableHeight?: number
|
||||
activeIndex?: number
|
||||
renderPopup?: boolean
|
||||
navigation?: boolean
|
||||
referenceLines?: any[]
|
||||
additionalHeight?: number
|
||||
hoverable?: boolean
|
||||
onRowClick?: (row: any, index: number) => void
|
||||
};
|
||||
|
||||
type TimeLineInfo = {
|
||||
timestart: number;
|
||||
timewidth: number;
|
||||
};
|
||||
|
||||
type State = TimeLineInfo & typeof initialState;
|
||||
|
||||
//const TABLE_HEIGHT = 195;
|
||||
let _additionalHeight = 0;
|
||||
const ROW_HEIGHT = 32;
|
||||
//const VISIBLE_COUNT = Math.ceil(TABLE_HEIGHT/ROW_HEIGHT);
|
||||
|
||||
const TIME_SECTIONS_COUNT = 8;
|
||||
const ZERO_TIMEWIDTH = 1000;
|
||||
function formatTime(ms: number) {
|
||||
if (ms < 0) return '';
|
||||
if (ms < 1000) return Duration.fromMillis(ms).toFormat('0.SSS')
|
||||
return Duration.fromMillis(ms).toFormat('mm:ss');
|
||||
}
|
||||
|
||||
function computeTimeLine(rows: Array<Row>, firstVisibleRowIndex: number, visibleCount: number): TimeLineInfo {
|
||||
const visibleRows = rows.slice(firstVisibleRowIndex, firstVisibleRowIndex + visibleCount + _additionalHeight);
|
||||
let timestart = visibleRows.length > 0 ? Math.min(...visibleRows.map((r) => r.time)) : 0;
|
||||
// TODO: GraphQL requests do not have a duration, so their timeline is borked. Assume a duration of 0.2s for every GraphQL request
|
||||
const timeend = visibleRows.length > 0 ? Math.max(...visibleRows.map((r) => r.time + (r.duration ?? 200))) : 0;
|
||||
let timewidth = timeend - timestart;
|
||||
const offset = timewidth / 70;
|
||||
if (timestart >= offset) {
|
||||
timestart -= offset;
|
||||
}
|
||||
timewidth *= 1.5; // += offset;
|
||||
if (timewidth === 0) {
|
||||
timewidth = ZERO_TIMEWIDTH;
|
||||
}
|
||||
return {
|
||||
timestart,
|
||||
timewidth,
|
||||
};
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
firstVisibleRowIndex: 0,
|
||||
};
|
||||
|
||||
export default class TimeTable extends React.PureComponent<Props, State> {
|
||||
state = {
|
||||
...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount),
|
||||
...initialState,
|
||||
};
|
||||
|
||||
get tableHeight() {
|
||||
return this.props.tableHeight || 195;
|
||||
}
|
||||
|
||||
get visibleCount() {
|
||||
return Math.ceil(this.tableHeight / ROW_HEIGHT);
|
||||
}
|
||||
|
||||
scroller = React.createRef<List>();
|
||||
autoScroll = true;
|
||||
|
||||
componentDidMount() {
|
||||
if (this.scroller.current) {
|
||||
this.scroller.current.scrollToRow(this.props.activeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: any, prevState: any) {
|
||||
if (
|
||||
prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex ||
|
||||
(this.props.rows.length <= this.visibleCount + _additionalHeight && prevProps.rows.length !== this.props.rows.length)
|
||||
) {
|
||||
this.setState({
|
||||
...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount),
|
||||
});
|
||||
}
|
||||
if (this.props.activeIndex && this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current) {
|
||||
this.scroller.current.scrollToRow(this.props.activeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
onScroll = ({ scrollTop, scrollHeight, clientHeight }: { scrollTop: number; scrollHeight: number; clientHeight: number }): void => {
|
||||
const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33);
|
||||
|
||||
if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) {
|
||||
this.autoScroll = scrollHeight - clientHeight - scrollTop < ROW_HEIGHT / 2;
|
||||
this.setState({ firstVisibleRowIndex });
|
||||
}
|
||||
};
|
||||
|
||||
renderRow = ({ index, key, style: rowStyle }: any) => {
|
||||
const { activeIndex } = this.props;
|
||||
const { children: columns, rows, renderPopup, hoverable, onRowClick } = this.props;
|
||||
const { timestart, timewidth } = this.state;
|
||||
const row = rows[index];
|
||||
return (
|
||||
<div
|
||||
style={rowStyle}
|
||||
key={key}
|
||||
className={cn('border-b border-color-gray-light-shade', stl.row, {
|
||||
[stl.hoverable]: hoverable,
|
||||
'error color-red': !!row.isRed && row.isRed(),
|
||||
'cursor-pointer': typeof onRowClick === 'function',
|
||||
[stl.activeRow]: activeIndex === index,
|
||||
// [stl.inactiveRow]: !activeIndex || index > activeIndex,
|
||||
})}
|
||||
onClick={typeof onRowClick === 'function' ? () => onRowClick(row, index) : undefined}
|
||||
id="table-row"
|
||||
>
|
||||
{columns.map(({ dataKey, render, width }) => (
|
||||
<div className={stl.cell} style={{ width: `${width}px` }}>
|
||||
{render ? render(row) : row[dataKey || ''] || <i className="color-gray-light">{'empty'}</i>}
|
||||
</div>
|
||||
))}
|
||||
<div className={cn('relative flex-1 flex', stl.timeBarWrapper)}>
|
||||
<BarRow resource={row} timestart={timestart} timewidth={timewidth} popup={renderPopup} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
onPrevClick = () => {
|
||||
let prevRedIndex = -1;
|
||||
for (let i = this.state.firstVisibleRowIndex - 1; i >= 0; i--) {
|
||||
if (this.props.rows[i].isRed()) {
|
||||
prevRedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.scroller.current != null) {
|
||||
this.scroller.current.scrollToRow(prevRedIndex);
|
||||
}
|
||||
};
|
||||
|
||||
onNextClick = () => {
|
||||
let prevRedIndex = -1;
|
||||
for (let i = this.state.firstVisibleRowIndex + 1; i < this.props.rows.length; i++) {
|
||||
if (this.props.rows[i].isRed()) {
|
||||
prevRedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.scroller.current != null) {
|
||||
this.scroller.current.scrollToRow(prevRedIndex);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, rows, children: columns, navigation = false, referenceLines = [], additionalHeight = 0, activeIndex } = this.props;
|
||||
const { timewidth, timestart } = this.state;
|
||||
|
||||
_additionalHeight = additionalHeight;
|
||||
|
||||
const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT);
|
||||
const timeColumns: number[] = [];
|
||||
if (timewidth > 0) {
|
||||
for (let i = 0; i < TIME_SECTIONS_COUNT; i++) {
|
||||
timeColumns.push(timestart + i * sectionDuration);
|
||||
}
|
||||
}
|
||||
|
||||
const visibleRefLines = referenceLines.filter(({ time }) => time > timestart && time < timestart + timewidth);
|
||||
|
||||
const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0);
|
||||
|
||||
return (
|
||||
<div className={cn(className, 'relative')}>
|
||||
{navigation && (
|
||||
<div className={cn(autoscrollStl.navButtons, 'flex items-center')}>
|
||||
<Button
|
||||
variant="text-primary"
|
||||
icon="chevron-up"
|
||||
tooltip={{
|
||||
title: 'Previous Error',
|
||||
delay: 0,
|
||||
}}
|
||||
onClick={this.onPrevClick}
|
||||
/>
|
||||
<Button
|
||||
variant="text-primary"
|
||||
icon="chevron-down"
|
||||
tooltip={{
|
||||
title: 'Next Error',
|
||||
delay: 0,
|
||||
}}
|
||||
onClick={this.onNextClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={stl.headers}>
|
||||
<div className={stl.infoHeaders}>
|
||||
{columns.map(({ label, width }) => (
|
||||
<div className={stl.headerCell} style={{ width: `${width}px` }}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={stl.waterfallHeaders}>
|
||||
{timeColumns.map((time, i) => (
|
||||
<div className={stl.timeCell} key={`tc-${i}`}>
|
||||
{formatTime(time)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NoContent size="small" show={rows.length === 0}>
|
||||
<div className="relative">
|
||||
<div className={stl.timePart} style={{ left: `${columnsSumWidth}px` }}>
|
||||
{timeColumns.map((_, index) => (
|
||||
<div key={`tc-${index}`} className={stl.timeCell} />
|
||||
))}
|
||||
{visibleRefLines.map(({ time, color, onClick }) => (
|
||||
<div
|
||||
className={cn(stl.refLine, `bg-${color}`)}
|
||||
style={{
|
||||
left: `${percentOf(time - timestart, timewidth)}%`,
|
||||
cursor: typeof onClick === 'function' ? 'click' : 'auto',
|
||||
}}
|
||||
onClick={onClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }: { width: number }) => (
|
||||
<List
|
||||
ref={this.scroller}
|
||||
className={stl.list}
|
||||
height={this.tableHeight + additionalHeight}
|
||||
width={width}
|
||||
overscanRowCount={20}
|
||||
rowCount={rows.length}
|
||||
rowHeight={ROW_HEIGHT}
|
||||
rowRenderer={this.renderRow}
|
||||
onScroll={this.onScroll}
|
||||
scrollToAlignment="start"
|
||||
forceUpdateProp={timestart | timewidth | (activeIndex || 0)}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</NoContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
|
||||
|
||||
.barWrapper {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 35%;
|
||||
bottom: 35%;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.downloadBar, .ttfbBar {
|
||||
/* box-shadow: inset 0px 0px 0px 1px $teal; */
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
.ttfbBar {
|
||||
background-color: rgba(175, 226, 221, 0.8);
|
||||
}
|
||||
.downloadBar {
|
||||
background-color: rgba(133, 200, 192, 0.8);
|
||||
}
|
||||
|
||||
.popupRow {
|
||||
color: $gray-medium;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
.title {
|
||||
width: 105px;
|
||||
}
|
||||
.time {
|
||||
width: 60px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.popupBarWrapper {
|
||||
width: 220px;
|
||||
height: 15px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './TimeTable';
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
|
||||
|
||||
$offset: 10px;
|
||||
|
||||
.timeCell {
|
||||
border-left: solid thin rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.headers {
|
||||
box-shadow: 0 1px 2px 0 $gray-light;
|
||||
background-color: $gray-lightest;
|
||||
color: $gray-darkest;
|
||||
font-size: 12px;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 0 $offset;
|
||||
}
|
||||
.infoHeaders {
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
& .headerCell {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
}
|
||||
.waterfallHeaders {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
& .timeCell {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
/* TODO hide the scrollbar track */
|
||||
&::-webkit-scrollbar {
|
||||
width: 1px;
|
||||
}
|
||||
scrollbar-width: thin;
|
||||
font-size: 12px;
|
||||
font-family: 'Menlo', 'monaco', 'consolas', monospace;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
padding: 0 $offset;
|
||||
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
/*align-items: center;
|
||||
cursor: pointer;
|
||||
*/
|
||||
/* &:nth-child(even) {
|
||||
background-color: $gray-lightest;
|
||||
} */
|
||||
/* & > div:first-child {
|
||||
padding-left: 5px;
|
||||
}*/
|
||||
}
|
||||
.cell {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.hoverable {
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
transition: all 0.2s;
|
||||
color: $gray-dark;
|
||||
}
|
||||
}
|
||||
.timeBarWrapper{
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timePart {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
/*left:0;*/
|
||||
right: 0;
|
||||
display: flex;
|
||||
margin: 0 $offset;
|
||||
& .timeCell {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
& .refLine {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.activeRow {
|
||||
background-color: $teal-light;
|
||||
}
|
||||
|
||||
.inactiveRow {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
.navButtons {
|
||||
position: absolute;
|
||||
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
padding: 4px;
|
||||
|
||||
right: 24px;
|
||||
top: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -128,24 +128,60 @@ export default class FetchDetailsModal extends React.PureComponent {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
resource: { method, url, duration },
|
||||
nextClick,
|
||||
prevClick,
|
||||
first = false,
|
||||
last = false,
|
||||
} = this.props;
|
||||
const { resource, nextClick, prevClick, first = false, last = false } = this.props;
|
||||
const { method, url, duration } = resource;
|
||||
const { activeTab, tabs } = this.state;
|
||||
|
||||
const _duration = parseInt(duration)
|
||||
console.log('_duration', _duration);
|
||||
const _duration = parseInt(duration);
|
||||
console.log('_duration', resource);
|
||||
|
||||
return (
|
||||
<div className="bg-white p-5 h-screen overflow-y-auto" style={{ width: '500px' }}>
|
||||
<h5 className="mb-2">{'URL'}</h5>
|
||||
<div className={cn(stl.url, 'color-gray-darkest')}>{url}</div>
|
||||
<h5 className="mb-2 text-2xl">Network Request</h5>
|
||||
<div className="flex items-center py-1">
|
||||
<div className="font-medium">Name</div>
|
||||
<div className="rounded-lg bg-active-blue px-2 py-1 ml-2 whitespace-nowrap overflow-hidden text-clip">
|
||||
{resource.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center py-1">
|
||||
<div className="font-medium">Type</div>
|
||||
<div className="rounded bg-active-blue px-2 py-1 ml-2 whitespace-nowrap overflow-hidden text-clip">
|
||||
{resource.type}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{method && (
|
||||
<div className="flex items-center py-1">
|
||||
<div className="font-medium">Request Method</div>
|
||||
<div className="rounded bg-active-blue px-2 py-1 ml-2 whitespace-nowrap overflow-hidden text-clip">
|
||||
{resource.method}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{resource.status && (
|
||||
<div className="flex items-center py-1">
|
||||
<div className="font-medium">Status</div>
|
||||
<div className="rounded bg-active-blue px-2 py-1 ml-2 whitespace-nowrap overflow-hidden text-clip flex items-center">
|
||||
{resource.status === '200' && <div className="w-4 h-4 bg-green rounded-full mr-2"></div>}
|
||||
{resource.status}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!_duration && (
|
||||
<div className="flex items-center py-1">
|
||||
<div className="font-medium">Time</div>
|
||||
<div className="rounded bg-active-blue px-2 py-1 ml-2 whitespace-nowrap overflow-hidden text-clip">
|
||||
{_duration} ms
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* <div className={cn(stl.url, 'color-gray-darkest')}>{url}</div> */}
|
||||
<div className="flex items-start mt-4">
|
||||
{method && (
|
||||
{/* {method && (
|
||||
<div className="w-4/12">
|
||||
<div className="font-medium mb-2">Method</div>
|
||||
<div>{method}</div>
|
||||
|
|
@ -156,7 +192,7 @@ export default class FetchDetailsModal extends React.PureComponent {
|
|||
<div className="font-medium mb-2">Duration</div>
|
||||
<div>{_duration } ms</div>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ interface Props {
|
|||
leadingButton?: React.ReactNode;
|
||||
type?: string;
|
||||
rows?: number;
|
||||
height?: number;
|
||||
[x: string]: any;
|
||||
}
|
||||
const Input = React.forwardRef((props: Props, ref: any) => {
|
||||
const { className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props;
|
||||
const { height = 36, className = '', leadingButton = '', wrapperClassName = '', icon = '', type = 'text', rows = 4, ...rest } = props;
|
||||
return (
|
||||
<div className={cn({ relative: icon || leadingButton }, wrapperClassName)}>
|
||||
{icon && <Icon name={icon} className="absolute top-0 bottom-0 my-auto ml-4" size="14" />}
|
||||
|
|
@ -29,7 +30,7 @@ const Input = React.forwardRef((props: Props, ref: any) => {
|
|||
<input
|
||||
ref={ref}
|
||||
type={type}
|
||||
style={{ height: '36px' }}
|
||||
style={{ height: `${height}px` }}
|
||||
className={cn('p-2 border border-gray-light bg-white w-full rounded', className, { 'pl-10': icon })}
|
||||
{...rest}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue