@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
1,009 lines (985 loc) • 36.4 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint react:true, es6:true*/
import React from 'react';
import Pure_component from '/www/util/pub/pure_component.js';
import ReactDOM from 'react-dom';
import {withRouter} from 'react-router-dom';
import ajax from '../../util/ajax.js';
import setdb from '../../util/setdb.js';
import zescape from '../../util/escape.js';
import csv from '../../util/csv.js';
import etask from '../../util/etask.js';
import classnames from 'classnames';
import filesaver from 'file-saver';
import {get_static_country, report_exception} from './util.js';
import _ from 'lodash';
import $ from 'jquery';
import Proxy_blank from './proxy_blank.js';
import {Checkbox, any_flag, flag_with_title,
Tooltip_bytes, Loader_small} from './common.js';
import Zone_description from './common/zone_desc.js';
import {Modal_dialog, Modal} from './common/modals.js';
import {T} from './common/i18n.js';
import {Toolbar_container, Toolbar_row, Toolbar_button,
Devider, Search_box} from './chrome_widgets.js';
import {AutoSizer, Table, Column} from 'react-virtualized';
import 'react-virtualized/styles.css';
import Tooltip from './common/tooltip.js';
import {OverlayTrigger, Tooltip as B_tooltip} from 'react-bootstrap';
import './css/proxies.less';
const is_local = ()=>{
const href = window.location.href;
return href.includes('localhost') || href.includes('127.0.0.1');
};
const Actions_cell = ({proxy, mgr, scrolling, open_delete_dialog})=>{
return <Actions proxy={proxy} get_status={mgr.get_status}
update_proxies={mgr.update} scrolling={scrolling}
open_delete_dialog={open_delete_dialog}/>;
};
class Targeting_cell extends Pure_component {
render(){
const {proxy, mgr} = this.props;
const zones = mgr.state.zones;
const static_country = get_static_country(proxy, zones);
if (static_country && static_country!='any' && static_country!='*')
{
return flag_with_title(static_country,
static_country.toUpperCase());
}
let val = proxy.country;
if (!val||val=='any'||val=='*')
return any_flag;
val = val.toUpperCase();
const state = proxy.state&&proxy.state.toUpperCase();
if (!state)
return flag_with_title(proxy.country, val);
if (!proxy.city)
return flag_with_title(proxy.country, `${val} (${state})`);
return flag_with_title(proxy.country,
`${val} (${state}), ${proxy.city}`);
}
}
const Status_cell = ({proxy})=>{
const status = proxy.status;
const status_details = proxy.status_details;
let details = (status_details||[]).map(d=>d.msg).join(',');
if (!details.length && status!='ok')
details = status;
if (status=='testing')
return <Tooltip title="Status is being tested">Testing</Tooltip>;
else if (status && status!='ok')
return <Tooltip title={details}>{details}</Tooltip>;
else if (status=='ok' && details)
{
return <Tooltip title={details}>
<span>OK<div className="ic_warning"/></span>
</Tooltip>;
}
else if (status=='ok' && !details)
return <Tooltip title="This proxy works correctly">OK</Tooltip>;
return <Tooltip title="Status of this proxy is unknown">?</Tooltip>;
};
const Boolean_cell = ({proxy, col})=>{
if (proxy[col]===true)
return <img src="/img/ic_checkmark.svg"/>;
return <img src="/img/ic_off.svg"/>;
};
const Static_ip_cell = ({proxy, mgr})=>{
if (proxy.ip)
return proxy.ip;
const curr_zone = mgr.state.zones.zones.find(z=>z.name==proxy.zone);
const curr_plan = curr_zone && curr_zone.plan;
const is_static = curr_plan && (curr_plan.type||'').startsWith('static');
if (is_static && proxy.pool_size)
{
if (Array.isArray(proxy.ips) && proxy.ips.length==1)
return proxy.ips[0];
return `Pool of ${proxy.pool_size} IP${proxy.pool_size==1 ? '' : 's'}`;
}
return null;
};
class Type_cell extends React.Component {
render(){
if (this.props.scrolling)
return 'Luminati';
const proxy = this.props.proxy;
let val, tip;
if (proxy.ext_proxies)
{
val = 'External';
tip = 'Proxy port configured with external IP and credentials';
}
else
{
val = 'Luminati';
tip = 'Proxy port using your Luminati account';
}
return <T>{t=><Tooltip title={t(tip)}>{t(val)}</Tooltip>}</T>;
}
}
const Port_cell = ({proxy, mgr, scrolling})=>{
if (scrolling)
return proxy.port;
let val;
if (!mgr.props.master_port && proxy.multiply && proxy.multiply>1)
val = proxy.port+':1..'+proxy.multiply;
else
val = proxy.port;
const title = `${proxy.port} is a proxy port that refers to a specific
virtual location on a computer. You can use it as a virtual proxy to
send requests`;
return <Tooltip title={title}>{val}</Tooltip>;
};
const Success_rate_cell = ({proxy})=>{
const val = !proxy.reqs ? '—' :
(proxy.success/proxy.reqs*100).toFixed(2)+'%';
return <T>{t=>
<Tooltip title={`${t('Total')}: ${proxy.reqs||0}, ${
t('Success')}: ${proxy.success||0}`}>{val}</Tooltip>
}</T>;
};
const Reqs_cell = ({proxy})=>{
const reqs = proxy.reqs||0;
return <Tooltip title={`${reqs} requests sent through this proxy port`}>
{reqs}</Tooltip>;
};
const Zone_cell = ({proxy, mgr, scrolling})=>{
if (scrolling)
return proxy.zone;
const zones = mgr.state.zones;
return <div>
<OverlayTrigger
placement="top"
overlay={
<B_tooltip id={`${proxy.port}-zone-tooltip`}>
<div className="zone_tooltip">
<Zone_description zones={zones} zone_name={proxy.zone}/>
</div>
</B_tooltip>
}
>
<span>{proxy.zone}</span>
</OverlayTrigger>
</div>;
};
const columns = [
{
key: 'actions',
title: 'lpm_ports_actions',
tooltip: `Delete/duplicate/refresh sessions/open browser`,
ext: true,
sticky: true,
render: Actions_cell,
width: 80,
shrink: 0,
grow: 0,
csv: false,
},
{
key: 'internal_name',
title: 'Internal name',
tooltip: `An internal name is used for proxy ports to be easily \
distinguished. Those don't change any proxy behavior and it's only
cosmetic`,
ext: true,
calc_show: proxies=>proxies.some(p=>p.internal_name),
},
{
key: 'port',
title: 'Proxy port',
sticky: true,
render: Port_cell,
tooltip: 'A proxy port is a number that refers to a specific virtual '
+'location on a computer. Create and configure proxy ports, then '
+'connect the crawler to send requests through the port',
ext: true,
dynamic: true,
width: 90,
},
{
key: 'proxy_type',
title: 'Type',
render: Type_cell,
tooltip: 'Type of connected proxy - Luminati proxy or external proxy '
+'(non-Luminati)',
default: true,
ext: true,
width: 70,
},
{
key: 'status',
title: 'Status',
type: 'status',
render: Status_cell,
default: true,
tooltip: 'Real time proxy status',
ext: true,
dynamic: true,
width: 55,
},
{
key: 'iface',
title: 'Interface',
type: 'options',
tooltip: 'Specific network interface on which the local machine is '
+'running. Switch interfaces in the proxy configuration page',
ext: true,
},
{
key: 'multiply',
title: 'Multiply',
type: 'number',
tooltip: 'Number of multiplied proxy ports. A proxy port can be '
+'multiplied in the proxy configuration page',
ext: true,
},
{
key: 'ssl',
title: 'SSL Analyzing',
render: Boolean_cell,
tooltip: 'In order to log HTTPS requests, enable SSL request logs in '
+'proxy configuration',
ext: true,
},
{
key: 'proxy_connection_type',
title: 'Proxy connection type',
tooltip: 'Connection type between LPM and Super Proxy',
ext: true,
},
{
key: 'zone',
title: 'Zone',
type: 'options',
default: true,
tooltip: 'Specific Luminati zone configured for this proxy. Switch '
+'zones in proxy configuration page.',
render: Zone_cell,
},
{
key: 'country',
title: 'Targeting',
type: 'options',
default: true,
render: Targeting_cell,
tooltip: 'Exit node (IP) GEO location',
width: 62,
},
{
key: 'asn',
title: 'ASN',
type: 'number',
tooltip: 'ASN uniquely identifies each network on the internet. You '
+'can target exit nodes (IPs) on specific ASNs',
},
{
key: 'max_requests',
title: 'Max requests',
type: 'text',
ext: true,
},
{
key: 'session_duration',
title: 'Session duration (sec)',
type: 'text',
ext: true,
},
{
key: 'pool_size',
title: 'Pool size',
type: 'number',
ext: true,
},
{
key: 'sticky_ip',
title: 'Sticky IP',
render: Boolean_cell,
ext: true,
},
{
key: 'race_reqs',
title: 'Parallel race requests',
type: 'number',
},
{
key: 'dns',
title: 'DNS',
type: 'options',
},
{
key: 'ip',
title: 'Static IPs',
type: 'text',
calc_show: (proxies, master)=>
master && proxies.some(p=>p.multiply_ips),
render: Static_ip_cell,
},
{
key: 'user',
title: 'User',
type: 'text',
ext: true,
calc_show: (proxies, master)=>
master && proxies.some(p=>p.multiply_users),
},
{
key: 'vip',
title: 'gIP',
type: 'number',
tooltip: 'A gIP is a group of exclusive residential IPs. Using gIPs '
+'ensures that nobody else uses the same IPs with the same target '
+'sites as you do.',
},
{
key: 'proxy',
title: 'Super Proxy',
type: 'text'
},
{
key: 'throttle',
title: 'Throttle concurrent connections',
type: 'number',
ext: true,
},
{
key: 'success',
title: 'Success',
tooltip: 'The ratio of successful requests out of total requests. A '
+'request is considered as successful if the server of the '
+'destination website responded',
render: Success_rate_cell,
default: true,
ext: true,
dynamic: true,
width: 58,
},
{
key: 'in_bw',
title: 'BW up',
render: ({proxy})=>Tooltip_bytes({bytes: proxy.out_bw}),
ext: true,
tooltip: 'Data transmitted to destination website. This includes'
+'request headers, request data, response headers, response data',
dynamic: true,
width: 90,
},
{
key: 'out_bw',
title: 'BW down',
render: ({proxy})=>Tooltip_bytes({bytes: proxy.in_bw}),
ext: true,
tooltip: 'Data transmitted to destination website. This includes'
+'request headers, request data, response headers, response data',
dynamic: true,
width: 90,
},
{
key: 'bw',
title: 'BW',
render: ({proxy})=>Tooltip_bytes({bytes: proxy.bw,
bytes_out: proxy.out_bw, bytes_in: proxy.in_bw}),
default: true,
ext: true,
tooltip: 'Data transmitted to destination website. This includes'
+'request headers, request data, response headers, response data',
dynamic: true,
width: 90,
},
{
key: 'reqs',
title: 'Requests',
render: Reqs_cell,
default: true,
ext: true,
tooltip: 'Number of all requests sent from this proxy port',
dynamic: true,
grow: 0,
width: 60,
},
];
const columns_obj = Object.keys(columns)
.reduce((acc, col)=>({...acc, [columns[col].key]: columns[col]}), {});
class Columns_modal extends Pure_component {
state = {selected_cols: []};
componentDidMount(){
const selected_cols = this.props.selected_cols.reduce((acc, e)=>
Object.assign(acc, {[e]: true}), {});
this.setState({selected_cols, saved_cols: selected_cols});
}
on_change = ({target: {value}})=>{
this.setState(prev=>({selected_cols: {
...prev.selected_cols,
[value]: !prev.selected_cols[value],
}}));
};
click_ok = ()=>{
const new_columns = Object.keys(this.state.selected_cols).filter(c=>
this.state.selected_cols[c]);
this.props.update_selected_cols(new_columns);
window.localStorage.setItem('columns', JSON.stringify(
this.state.selected_cols));
};
click_cancel = ()=>this.setState({selected_cols: this.state.saved_cols});
select_all = ()=>{
this.setState({selected_cols: columns.filter(c=>!c.sticky)
.reduce((acc, e)=>Object.assign(acc, {[e.key]: true}), {})});
};
select_none = ()=>this.setState({selected_cols: {}});
select_default = ()=>{
this.setState({selected_cols: columns.filter(c=>!c.sticky&&c.default)
.reduce((acc, e)=>Object.assign(acc, {[e.key]: true}), {})});
};
render(){
const header = <div className="header_buttons">
<button onClick={this.select_all} className="btn btn_lpm">
<T>Check all</T></button>
<button onClick={this.select_none} className="btn btn_lpm">
<T>Uncheck all</T></button>
<button onClick={this.select_default} className="btn btn_lpm">
<T>Default</T></button>
</div>;
return <Modal id="edit_columns" custom_header={header}
click_ok={this.click_ok} cancel_clicked={this.click_cancel}>
<div className="row columns">
{columns.filter(col=>!col.sticky).map(col=>
<div key={col.key} className="col-md-6">
<T>{t=>
<Checkbox text={t(col.title)} value={col.key}
on_change={this.on_change}
checked={!!this.state.selected_cols[col.key]}/>
}</T>
</div>
)}
</div>
</Modal>;
}
}
const Proxies = withRouter(class Proxies extends Pure_component {
update_window_dimensions = ()=>
this.setState({height: window.innerHeight});
constructor(props){
super(props);
let from_storage = JSON.parse(window.localStorage.getItem(
'columns'))||{};
from_storage = Object.keys(from_storage).filter(c=>from_storage[c]);
const default_cols = columns.filter(c=>c.default).map(col=>col.key);
this.state = {
selected_cols: from_storage.length && from_storage || default_cols,
proxies: [],
selected_proxies: {},
checked_all: false,
proxy_filter: '',
filtered_proxies: [],
loaded: false,
height: window.innerHeight,
open_delete_dialog: false,
delete_proxies: [],
};
setdb.set('head.proxies.update', this.update);
}
componentDidMount(){
this.setdb_on('head.proxies_running', proxies=>{
if (!proxies)
return;
let proxy_filter = proxies.length ? this.state.proxy_filter : '';
proxies = this.prepare_proxies(proxies);
const filtered_proxies = this.filter_proxies(
proxies, this.props.master_port, proxy_filter);
this.setState(
{proxies, filtered_proxies, proxy_filter, loaded: true});
});
this.setdb_on('head.locations', locations=>{
if (!locations)
return;
const countries = locations.countries_by_code;
this.setState({countries});
});
this.setdb_on('head.zones', zones=>{
if (!zones)
return;
this.setState({zones});
});
this.timeout_id = window.setTimeout(this.req_status);
this.update_window_dimensions();
window.addEventListener('resize', this.update_window_dimensions);
}
componentDidUpdate(prev_props){
if (prev_props.master_port!=this.props.master_port)
{
const filtered_proxies = this.filter_proxies(this.state.proxies,
this.props.master_port);
this.setState({filtered_proxies});
}
}
willUnmount(){
window.clearTimeout(this.timeout_id);
window.removeEventListener('resize', this.update_window_dimensions);
}
filter_proxies = (proxies, mp, proxy_filter)=>{
if (proxy_filter===undefined)
proxy_filter = this.state.proxy_filter;
return proxies.filter(p=>{
if (proxy_filter && !(p.internal_name||'').includes(proxy_filter))
return false;
if (mp)
return ''+p.port==mp||''+p.master_port==mp;
return p.proxy_type!='duplicate';
});
};
prepare_proxies = proxies=>{
proxies.sort(function(a, b){ return a.port>b.port ? 1 : -1; });
for (let i=0; i<proxies.length; i++)
{
const cur = proxies[i];
if (Array.isArray(cur.proxy)&&cur.proxy.length==1)
cur.proxy = cur.proxy[0];
cur.status_details = cur.status_details||[];
}
return proxies;
};
req_status = ()=>{
const _this = this;
this.etask(function*(){
this.on('uncaught', e=>_this.etask(function*(){
yield report_exception(e, 'proxies.Proxies.req_status');
}));
const params = {};
if (_this.props.master_port)
params.master_port = _this.props.master_port;
const url = zescape.uri('/api/recent_stats', params);
const stats = yield ajax.json({url});
setdb.set('head.recent_stats', stats);
if (!_this.state.proxies.length ||
_.isEqual(stats, _this.state.stats))
{
yield etask.sleep(1000);
_this.req_status();
return;
}
_this.setState(prev=>{
const new_proxies = prev.proxies.map(p=>{
let stat = {reqs: 0, success: 0, in_bw: 0, out_bw: 0,
bw: 0};
if (''+p.port in stats.ports)
stat = stats.ports[p.port];
p.in_bw = stat.in_bw;
p.out_bw = stat.out_bw;
p.bw = p.in_bw+p.out_bw;
p.reqs = stat.reqs;
p.success = stat.success;
p.last_req = {url: stat.url, ts: stat.timestamp};
return p;
}).reduce((acc, p)=>({...acc, [p.port]: p}), {});
if (!_this.props.master_port)
{
for (let p of Object.values(new_proxies))
{
if (p.proxy_type=='duplicate')
{
const master_port = new_proxies[p.master_port];
master_port.in_bw += p.in_bw;
master_port.out_bw += p.out_bw;
master_port.bw += p.bw;
master_port.reqs += p.reqs;
master_port.success += p.success;
if (!master_port.last_req.ts ||
p.last_req.ts &&
p.last_req.ts>master_port.last_req.ts)
{
master_port.last_req = p.last_req;
}
}
}
}
const filtered_proxies = _this.filter_proxies(
Object.values(new_proxies), _this.props.master_port);
return {proxies: Object.values(new_proxies), filtered_proxies,
stats};
});
yield etask.sleep(1000);
_this.req_status();
});
};
update = ()=>{
this.setState({selected_proxies: {}, checked_all: false});
return this.etask(function*(){
const proxies = yield ajax.json({url: '/api/proxies_running'});
setdb.set('head.proxies_running', proxies);
});
};
download_csv = ()=>{
const cols = this.get_cols().filter(c=>c.csv==undefined || c.csv);
const titles = [cols.map(col=>col.title)];
const data = titles.concat(this.state.proxies.map(p=>{
return cols.map(col=>{
const val = _.get(p, col.key);
if (val==undefined)
return '-';
return val;
});
}));
filesaver.saveAs(csv.to_blob(data), 'proxies.csv');
};
edit_columns = ()=>$('#edit_columns').modal('show');
update_selected_columns = new_columns=>
this.setState({selected_cols: new_columns});
proxy_add = ()=>$('#add_new_proxy_modal').modal('show');
select_renderer = function Select_renderer(props){
if (props.rowData=='filler')
return <div className="chrome_td"></div>;
const {selected_proxies} = this.state;
const checked = !!selected_proxies[props.rowData.port];
return <Checkbox checked={checked}
on_change={()=>this.on_row_select(props.rowData)}
on_click={e=>e.stopPropagation()}/>;
};
on_row_select = proxy=>{
const {selected_proxies} = this.state;
const new_selected = Object.assign({}, selected_proxies);
if (selected_proxies[proxy.port])
delete new_selected[proxy.port];
else
new_selected[proxy.port] = proxy;
this.setState({selected_proxies: new_selected});
};
all_rows_select = ()=>{
const checked_all = !this.state.checked_all;
const selected_proxies = checked_all ?
this.state.filtered_proxies.reduce((obj, p)=>{
obj[p.port] = p;
return obj;
}, {}) : {};
this.setState({selected_proxies, checked_all});
};
cell_renderer = function Cell_renderer(props){
return <Cell {...props} mgr={this}
open_delete_dialog={this.open_delete_dialog}/>;
};
on_row_click = e=>{
const proxy = e.rowData;
if (proxy.proxy_type!='persist')
return;
if (!this.props.master_port && proxy.multiply && proxy.multiply>1)
this.props.history.push(`/overview/${proxy.port}`);
else
this.props.history.push(`/proxy/${proxy.port}`);
};
get_cols = ()=>{
if (!is_local())
{
const actions_idx = columns.findIndex(col=>col.key=='actions');
columns[actions_idx].width = 60;
}
return columns.filter(col=>this.state.selected_cols.includes(col.key)
|| col.sticky || col.calc_show && col.calc_show(
this.state.filtered_proxies, this.props.master_port));
};
open_delete_dialog = proxies=>{
this.setState({delete_proxies: proxies, open_delete_dialog: true});
};
close_delete_dialog = ()=>{
this.setState({open_delete_dialog: false});
};
set_proxy_filter(e){
let proxy_filter = e.target.value;
const filtered_proxies = this.filter_proxies(this.state.proxies,
this.props.master_port, proxy_filter);
this.setState({proxy_filter, filtered_proxies, selected_proxies: {}});
}
render(){
const cols = this.get_cols();
if (!this.state.zones || !this.state.countries)
{
return <div className="proxies_panel chrome">
<div className="main_panel vbox">
<Loader_small show loading_msg="Loading..."/>
</div>
</div>;
}
let {proxies, proxy_filter} = this.state;
let show_table = !!proxies.length;
if (this.state.loaded && !show_table)
return <Proxy_blank/>;
return <div className="proxies_panel chrome">
<div className="main_panel vbox">
<Toolbar proxy_add={this.proxy_add}
edit_columns={this.edit_columns}
download_csv={this.download_csv}
selected={this.state.selected_proxies}
open_delete_dialog={this.open_delete_dialog}
proxy_filter={proxy_filter}
on_proxy_filter_change={e=>this.set_proxy_filter(e)}/>
{this.state.loaded && show_table &&
<React.Fragment>
<div className="chrome chrome_table vbox">
<div className="tables_container header_container hack">
<div className="chrome_table">
<AutoSizer>
{({height, width})=>
<T>{t=><Table width={width}
height={height}
onRowClick={this.on_row_click}
onHeaderClick={({dataKey})=>dataKey=='select' &&
this.all_rows_select()}
gridClassName="chrome_grid"
headerHeight={27}
headerClassName="chrome_th"
rowClassName="chrome_tr"
rowHeight={22}
rowCount={this.state.filtered_proxies.length+1}
rowGetter={({index})=>
this.state.filtered_proxies[index]||'filler'}>
<Column key="select"
cellRenderer={this.select_renderer.bind(this)}
label={<Checkbox checked={this.state.checked_all}
on_change={()=>null}/>}
dataKey="select"
className="chrome_td"
flexGrow={0}
flexShrink={1}
width={27}/>
{cols.map(col=>
<Column key={col.key}
cellRenderer={this.cell_renderer.bind(this)}
label={t(col.title)}
className="chrome_td"
dataKey={col.key}
flexGrow={col.grow!==undefined ? col.grow : 1}
flexShrink={col.shrink!==undefined ?
col.shrink : 1}
width={col.width||100}/>
)}
</Table>}</T>
}
</AutoSizer>
</div>
</div>
</div>
</React.Fragment>
}
</div>
<Columns_modal selected_cols={this.state.selected_cols}
update_selected_cols={this.update_selected_columns}/>
<Delete_dialog open={this.state.open_delete_dialog}
close_dialog={this.close_delete_dialog}
proxies={this.state.delete_proxies}
update_proxies={this.update}/>
</div>;
}
});
class Delete_dialog extends Pure_component {
delete_proxies = e=>{
e.stopPropagation();
const _this = this;
const ports = _this.props.proxies.map(p=>p.port);
this.etask(function*(){
yield window.fetch('/api/proxies/delete', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ports}),
});
yield _this.props.update_proxies();
_this.props.close_dialog();
});
};
render(){
const {proxies, open, close_dialog} = this.props;
let title = 'Are you sure you want to delete ';
if (proxies.length==1)
title += `proxy port ${proxies[0].port}?`;
else
title += `${proxies.length} proxy ports?`;
return <Portal>
<Modal_dialog open={open}
title={title}
ok_clicked={this.delete_proxies}
cancel_clicked={close_dialog} />
</Portal>;
}
}
class Toolbar extends Pure_component {
state = {
filters: false,
};
open_delete_dialog_with_proxies = e=>{
e.stopPropagation();
this.props.open_delete_dialog(this.get_to_delete());
};
get_to_delete = ()=>{
const {selected} = this.props;
return Object.values(selected).filter(p=>p.proxy_type=='persist');
};
toggle_filters(){
this.setState({filters: !this.state.filters});
}
render(){
const {proxy_add, edit_columns, download_csv} = this.props;
const to_delete = this.get_to_delete();
return <Toolbar_container>
<Toolbar_row>
<T>{t=><Toolbar_button id="add" tooltip={t('Add new proxy')}
on_click={proxy_add}>
<span style={{marginRight: 5, position: 'relative',
top: -3}}><T>Add new proxy</T></span>
</Toolbar_button>}</T>
<Devider/>
<Toolbar_button tooltip="Filters"
on_click={()=>this.toggle_filters()} id="filters"/>
<Toolbar_button tooltip="Edit columns" on_click={edit_columns}
id="actions"/>
<Toolbar_button tooltip="Download all proxy ports as CSV"
on_click={download_csv} id="download"/>
{!!to_delete.length &&
<Toolbar_button tooltip="Delete selected proxies"
on_click={this.open_delete_dialog_with_proxies} id="trash"/>
}
</Toolbar_row>
{this.state.filters && <Toolbar_row>
<Search_box val={this.props.proxy_filter}
on_change={this.props.on_proxy_filter_change}/>
</Toolbar_row>}
</Toolbar_container>;
}
}
class Cell extends React.Component {
shouldComponentUpdate(next_props){
if (this.props.dataKey=='last_req.url')
return true;
return this.props.cellData!=next_props.cellData ||
this.props.rowData!=next_props.rowData;
}
render(){
const props = this.props;
const col = columns_obj[props.dataKey];
const proxy = props.rowData;
if (props.rowData=='filler')
return <div className="chrome_td"></div>;
else if (!col.ext && proxy.ext_proxies)
return '—';
else if (col.render)
{
const S_cell = col.render;
return <S_cell proxy={props.rowData} mgr={props.mgr}
col={props.dataKey}
open_delete_dialog={props.open_delete_dialog}/>;
}
return props.cellData||'';
}
}
class Actions extends Pure_component {
componentDidMount(){
if (!this.props.proxy.status)
this.get_status();
}
// XXX krzysztof: this logic is a mess, rewrite it
get_status = (opt={})=>{
const proxy = this.props.proxy;
if (!opt.force && proxy.status=='ok')
return;
return this.etask(function*(){
this.on('uncaught', e=>{
proxy.status = 'error';
proxy.status_details = [{msg: e.message}];
setdb.emit_path('head.proxies_running');
});
const params = {};
if (!proxy.status)
proxy.status = 'testing';
if (opt.force)
{
proxy.status = 'testing';
params.force = true;
}
if (proxy.status=='testing')
setdb.emit_path('head.proxies_running');
const uri = '/api/proxy_status/'+proxy.port;
const url = zescape.uri(uri, params);
const res = yield ajax.json({url, timeout: 25000});
if (res.status!='ok')
{
const errors = res.status_details||[];
res.status_details = errors.length ?
errors : [{msg: res.status}];
res.status = 'error';
}
proxy.status = res.status;
proxy.status_details = res.status_details||[];
setdb.emit_path('head.proxies_running');
});
};
refresh_sessions = e=>{
e.stopPropagation();
const _this = this;
this.etask(function*(){
const url = '/api/refresh_sessions/'+_this.props.proxy.port;
yield ajax.json({url, method: 'POST'});
yield _this.get_status({force: true});
});
};
duplicate = event=>{
event.stopPropagation();
const _this = this;
this.etask(function*(){
this.on('uncaught', e=>_this.etask(function*(){
yield report_exception(e, 'proxies.Actions.duplicate');
}));
yield window.fetch('/api/proxy_dup', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({port: _this.props.proxy.port}),
});
yield _this.props.update_proxies();
});
};
open_browser = e=>{
e.stopPropagation();
const _this = this;
this.etask(function*(){
const url = `/api/browser/${_this.props.proxy.port}`;
const res = yield window.fetch(url);
if (res.status==206)
$('#fetching_chrome_modal').modal();
});
};
open_delete_dialog_with_port = e=>{
e.stopPropagation();
this.props.open_delete_dialog([this.props.proxy]);
};
render(){
const persist = this.props.proxy.proxy_type=='persist';
return <div className={is_local() ? 'proxies_actions' :
'proxies_actions_3'}>
{!!persist &&
<React.Fragment>
<Action_icon id="trash" scrolling={this.props.scrolling}
on_click={this.open_delete_dialog_with_port}
tooltip="Delete" invisible={!persist}/>
<Action_icon id="duplicate" on_click={this.duplicate}
tooltip="Duplicate proxy port" invisible={!persist}
scrolling={this.props.scrolling}/>
</React.Fragment>
}
<Action_icon id="refresh" scrolling={this.props.scrolling}
on_click={this.refresh_sessions}
tooltip="Refresh Sessions"/>
{is_local() &&
<Action_icon id="browser" scrolling={this.props.scrolling}
on_click={this.open_browser}
tooltip="Open browser configured with this port"/>
}
</div>;
}
}
const Portal = props=>ReactDOM.createPortal(props.children,
document.getElementById('del_modal'));
const Action_icon = props=>{
let {on_click, invisible, id, tooltip, scrolling} = props;
const classes = classnames('action_icon', 'chrome_icon', id,
{invisible});
if (scrolling)
return <div className={classes}/>;
return <T>{t=><Tooltip title={t(tooltip)}>
<div onClick={on_click} className={classes}/>
</Tooltip>}</T>;
};
export default Proxies;