@luminati-io/luminati-proxy
Version:
A configurable local proxy for brightdata.com
319 lines (303 loc) • 11.4 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint react:true, es6:true*/
import React from 'react';
import {withRouter} from 'react-router-dom';
import filesaver from 'file-saver';
import React_tooltip from 'react-tooltip';
import etask from '../../util/etask.js';
import Pure_component from '/www/util/pub/pure_component.js';
import setdb from '../../util/setdb.js';
import {capitalize} from '../../util/string.js';
import {to_blob} from '../../util/csv.js';
import Tooltip from './common/tooltip.js';
import {T} from './common/i18n.js';
import {status_codes, bytes_format} from './util.js';
import {Tooltip_bytes, Loader_small, Toolbar_button} from './common.js';
import ws from './ws.js';
import {main as Api} from './api.js';
export const Logs_context = React.createContext(true);
export class Stats extends Pure_component {
state = {stats: {}};
componentDidMount(){
this.setdb_on('head.recent_stats', stats=>{
if (stats)
this.setState({stats});
});
this.setdb_on('head.settings', s=>{
if (s)
this.setState({show: s.request_stats});
});
}
toggle_stats = show=>this.setState({show});
render(){
const {show, stats} = this.state;
if (show!==undefined && !show)
{
return <Stats_off_btn
turn_on_stats={()=>this.toggle_stats(true)}/>;
}
return <div className="stats_wrapper">
<div className="stats_panel cp_panel vbox">
<Header_panel stats={stats}
disable_stats={()=>this.toggle_stats(false)}/>
{!show &&
<Loader_small show loading_msg="Loading..."/>
}
{show && <React.Fragment>
<Stat_table stats={stats} tooltip="Status code"
style={{flex: 1, overflowY: 'auto'}}
row_key="status_code" logs="code" title="Code"/>
<Stat_table stats={stats} tooltip="Domain name"
style={{flex: 1, overflowY: 'auto'}}
row_key="hostname" logs="domain" title="Domain"/>
<Stat_table stats={stats} tooltip="Protocol"
style={{flex: 'none', overflowY: 'auto'}}
ssl_warning={stats.ssl_warning} row_key="protocol"
logs="protocol" title="Protocol"/>
</React.Fragment>}
</div>
</div>;
}
}
const Header_panel = props=>
<div className="cp_panel_header">
<h2>Statistics</h2>
<Toolbar stats={props.stats} disable_stats={props.disable_stats}/>
</div>;
const Stats_off_btn = props=>
<Tooltip title="Show recent stats"
placement="left">
<button className="enable_btn enable_btn_stats" disabled={props.disabled}
onClick={props.turn_on_stats}>
<i className="fa fa-chevron-left"/>
</button>
</Tooltip>;
const Empty_row = ()=>
<tr className="empty_row">
<td>{'—'}</td><td>{'—'}</td><td>{'—'}</td><td>{'—'}</td>
</tr>;
// XXX krzysztof: merge with enable_ssl in har/viewer.js
const enable_ssl_click = port=>etask(function*(){
yield Api.json.post('enable_ssl');
const proxies = yield Api.json.get('proxies_running');
setdb.set('head.proxies_running', proxies);
});
const Key_cell = ({title, warning, row_key})=>{
return <td>
<Cell row_key={row_key} onClick={e=>e.stopPropagation()}>
{title}
</Cell>
{warning &&
<span onClick={e=>e.stopPropagation()}>
<React_tooltip id="ssl_warn" type="info" effect="solid"
delayHide={100} delayShow={0} delayUpdate={500}
offset={{top: -10}}>
<div>
Some of your proxy ports don't have SSL analyzing enabled and
there are connections on HTTPS protocol detected.
</div>
<div style={{marginTop: 10}}>
<a onClick={enable_ssl_click} className="link">
Enable SSL analyzing
</a>
<span>
to see {name} and other information about requests
</span>
</div>
</React_tooltip>
<span data-tip="React-tooltip" data-for="ssl_warn">
<div className="ic_warning"/>
</span>
</span>
}
</td>;
};
const Cell = ({row_key, children})=>{
if (row_key=='status_code')
{
return <Tooltip title={children+' '+status_codes[children]}>
<div className="disp_value">{children}</div>
</Tooltip>;
}
return <Tooltip title={children}>
<div className="disp_value">{children}</div>
</Tooltip>;
};
class Stat_table extends Pure_component {
state = {sorting: {col: 0, dir: 1}};
sort = col=>{
const cur_sorting = this.state.sorting;
const dir = cur_sorting.col==col ? -1*cur_sorting.dir : 1;
this.setState({sorting: {dir, col}});
};
render(){
const {title, stats, row_key, logs, ssl_warning} = this.props;
const cur_stats = stats[row_key]||[];
const cols = [{id: 'key'}, {id: 'bw'}, {id: 'bypass_bw'},
{id: 'reqs'}];
return <div className="tables_container vbox">
<Header_container title={title} cols={cols}
tooltip={this.props.tooltip}
sorting={this.state.sorting} sort={this.sort}/>
<Data_container stats={cur_stats} row_key={row_key} logs={logs}
ssl_warning={ssl_warning} cols={cols}
sorting={this.state.sorting}/>
</div>;
}
}
const Header_container = ({title, cols, sorting, sort, tooltip})=>
<div className="header_container">
<table className="chrome_table">
<colgroup>
{(cols||[]).map((c, idx)=><col key={idx} style={{width: c.width}}/>)}
</colgroup>
<tbody>
<tr>
<Header sort={sort} id={0} label={title} sorting={sorting}
tooltip={tooltip}/>
<Header sort={sort} id={1} label="Total BW" sorting={sorting}
tooltip="Total bandwith sent through Proxy Manager"/>
<Header sort={sort} id={2} label="Saved BW" sorting={sorting}
tooltip="Saved bandwidth represents the traffic sent through
your local IP or external proxy. Go to the Rules tab
to configure bandwidth saving rules"/>
<Header sort={sort} id={3} label="Requests" sorting={sorting}
tooltip="Number of sent requests"/>
</tr>
</tbody>
</table>
</div>;
const Header = ({sort, sorting, id, label, tooltip})=>
<T>{t=>
<Tooltip title={t(tooltip)}>
<th onClick={()=>sort(id)}>
<div>{t(label)}</div>
</th>
</Tooltip>
}</T>;
const Data_container = ({stats, row_key, logs, ssl_warning, cols, sorting})=>{
if (!cols)
return null;
const sorted = stats.slice().sort((a, b)=>{
const field = cols[sorting.col].id;
const val_a = a[field];
const val_b = b[field];
if (val_a==val_b)
return 0;
let res = val_a>val_b ? 1 : -1;
return sorting.dir==-1 ? res : -res;
});
return <div className="data_container">
<table className="chrome_table">
<colgroup>
{(cols||[]).map((c, idx)=>
<col key={idx} style={{width: c.width}}/>
)}
</colgroup>
<tbody>
{!sorted.length && <Empty_row/>}
{sorted.map(s=>
<Row stat={s} key={s.key} row_key={row_key} logs={logs}
warning={ssl_warning&&s.key=='https'}/>
)}
<tr className="filler">
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>;
};
const Row = withRouter(class Row extends Pure_component {
click = ()=>{
const url = `/logs?${this.props.logs}=${this.props.stat.key}`;
this.props.history.push(url);
};
render(){
const {stat, row_key, warning} = this.props;
return <tr className={!this.context ? 'disabled' : ''}
onClick={this.context ? this.click : null}>
<Key_cell row_key={row_key} title={stat.key} warning={warning}/>
<td>
<Tooltip_bytes
chrome_style
bytes_in={stat.in_bw}
bytes_out={stat.out_bw}
bytes={stat.in_bw+stat.out_bw}/>
</td>
<td>
<Tooltip_bytes bytes={stat.bypass_bw} cost={stat.bypass_cost}/>
</td>
<td><Cell>{stat.reqs||'—'}</Cell></td>
</tr>;
}
});
Row.WrappedComponent.contextType = Logs_context;
class Toolbar extends Pure_component {
clear = ()=>{
this.etask(function*(){
yield Api.put('logs_reset');
setdb.emit_path('head.har_viewer.reset_reqs');
});
};
download_csv = key=>{
if (typeof key!='string' || !key)
{
ws.post_event('Download Statistic');
this.download_csv('status_code');
this.download_csv('hostname');
this.download_csv('protocol');
return;
}
const stats = this.props.stats[key] || [];
if (key=='hostname')
key = 'domain';
else if (key=='status_code')
key = 'code';
const titles = [[capitalize(key), 'Total BW', 'Saved BW', 'Requests']];
const data = titles.concat(stats.map(s=>[
s.key,
bytes_format(s.in_bw+s.out_bw),
s.bypass_bw||'–',
s.reqs||'–'
]));
filesaver.saveAs(to_blob(data), 'pm_stats_'+key+'.csv');
};
render(){
return <div className="toolbar">
<Success_ratio total={this.props.stats.total}
success={this.props.stats.success}/>
<Toolbar_button id="remove" tooltip="Clear"
on_click={this.clear}/>
<Toolbar_button id="download" tooltip="Download CSV"
on_click={this.download_csv}/>
<Toolbar_button id="arrow_down" tooltip="Hide"
placement="left" on_click={this.props.disable_stats}/>
</div>;
}
}
const Success_ratio = ({total=0, success=0})=>{
const ratio = total==0 ? NaN : success/total*100;
const tooltip = 'Ratio of successful requests out of total '
+'requests, where successful requests are calculated as 2xx, '
+'3xx or 404 HTTP status codes';
return <div className="title_wrapper">
<div className="success_title">
<T>{t=>
<Tooltip title={t(tooltip)}>
<span>{t('Success rate')}:</span>
</Tooltip>
}</T>
</div>
<div className="success_value">
<T>{t=>
<Tooltip
title={`${t('Total')}: ${total}, ${t('Success')}: ${success}`}>
{isNaN(ratio) ? '-' : ratio.toFixed(2)+'%'}
</Tooltip>
}</T>
</div>
</div>;
};