@luminati-io/luminati-proxy
Version:
A configurable local proxy for brightdata.com
373 lines (360 loc) • 13.8 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint react:true, es6:true*/
import etask from '../../util/etask.js';
import setdb from '../../util/setdb.js';
import React from 'react';
import $ from 'jquery';
import classnames from 'classnames';
import {Loader, Warnings, Code, Preset_description, Copy_icon,
with_www_api} from './common.js';
import {Nav_tabs, Nav_tab} from './common/nav_tabs.js';
import {report_exception} from './util.js';
import presets from './common/presets.js';
import Pure_component from '/www/util/pub/pure_component.js';
import {withRouter} from 'react-router-dom';
import prism from 'prismjs';
import instructions from './instructions.js';
import Tooltip from './common/tooltip.js';
import {Textarea, Select_zone} from './common/controls.js';
import Zone_description from './common/zone_desc.js';
import {Modal} from './common/modals.js';
import {T, t} from './common/i18n.js';
import {main as Api} from './api.js';
import {Instructions, Li} from '/www/util/pub/bullets.js';
import './css/proxy_add.less';
const Proxy_add = withRouter(class Proxy_add extends Pure_component {
init_state = {
zone: '',
preset: 'session_long',
show_loader: false,
cur_tab: 'proxy_lum',
error_list: [],
step: 0,
};
state = Object.assign({}, this.init_state);
static getDerivedStateFromProps(props, state){
const is_unblocker = ()=>{
if (!state.zones)
return false;
const zone_name = state.zone || state.zones.def;
const zone = state.zones.zones.find(p=>p.name==zone_name) || {};
const plan = zone.plan || {};
return plan.type=='unblocker';
};
return {preset: is_unblocker() ? 'unblocker' :
state.preset=='unblocker' ? 'session_long' : state.preset};
}
componentDidMount(){
this.setdb_on('head.proxies_running', proxies_running=>{
if (!proxies_running)
return;
this.setState({proxies_running});
});
this.setdb_on('ws.zones', zones=>{
if (!zones)
return;
this.setState({zones});
});
}
persist = ()=>{
const form = {};
if (this.state.cur_tab=='proxy_lum')
{
form.preset = this.state.preset;
form.zone = this.state.zone;
}
else
{
form.preset = 'rotating';
form.ext_proxies = this.state.parsed_ips_list;
}
form.proxy_type = 'persist';
presets.get(form.preset).set(form);
const _this = this;
return etask(function*(){
this.on('uncaught', e=>_this.etask(function*(){
yield report_exception(e, 'proxy_add.Proxy_add.persist');
_this.setState({show_loader: false});
}));
const resp = yield Api.json.post('proxies', {proxy: form});
if (resp.errors)
return resp;
return {port: resp.data.port};
});
};
save = (opt={})=>{
const _this = this;
this.etask(function*(){
this.on('uncaught', e=>_this.etask(function*(){
yield report_exception(e, 'proxy_add.Proxy_add.save');
}));
this.finally(()=>{
_this.setState({show_loader: false});
if (resp && resp.port && !resp.errors)
_this.next_step();
});
_this.setState({show_loader: true});
const resp = yield _this.persist();
if (resp.errors)
{
_this.setState({error_list: resp.errors});
$('#add_proxy_errors').modal('show');
}
if (resp.port)
_this.setState({created_port: resp.port});
if (!resp.errors)
{
const proxies = yield Api.json.get('proxies_running');
setdb.set('head.proxies_running', proxies);
}
});
};
field_changed = id=>value=>this.setState({[id]: value});
set_tab = id=>this.setState({cur_tab: id});
on_hidden = ()=>this.setState(this.init_state);
next_step = ()=>this.setState(prev=>({step: prev.step+1}));
render(){
if (!this.state.proxies_running || !this.state.zones)
return null;
const disabled = this.state.cur_tab=='proxy_ext'&&
!this.state.valid_json;
const Footer_wrapper = <Footer
save_clicked={this.save}
disabled={disabled}
step={this.state.step}
next_step={this.next_step}
created_port={this.state.created_port}/>;
const {settings: {lpm_token='', zagent, cloud_url_address}}
= this.props;
const hostname = zagent ? cloud_url_address : undefined;
return <div className="lpm">
<Loader show={this.state.show_loader}/>
<Modal id="add_new_proxy_modal" no_header no_close
on_hidden={this.on_hidden} footer={Footer_wrapper}
className="add_proxy_modal">
<div className="proxy_form">
<Instructions>
<Li>
<Step curr_step={this.state.step} step={0}
title={t('Network')}>
<Note>
<T>Select your prefered network: Bright Data or an
external vendor?</T>
</Note>
<Nav_tabs_wrapper set_tab={this.set_tab}
cur_tab={this.state.cur_tab}/>
{this.state.cur_tab=='proxy_lum' &&
<Lum_proxy
def_zone={this.state.zones.def}
created_port={this.state.created_port}
zone={this.state.zone}
on_field_change={this.field_changed}/>
}
{this.state.cur_tab=='proxy_ext' &&
<Ext_proxy
parse_error={this.state.parse_error}
ips_list={this.state.ips_list}
on_field_change={this.field_changed}
zagent={zagent}/>
}
</Step>
</Li>
<Li>
<Step curr_step={this.state.step} step={1}
title={t('Configuration')}>
<Note>
<T>Using this proxy with a browser or a scraper?</T>
</Note>
<Nav_tabs set_tab={this.field_changed('preset')}
cur_tab={this.state.preset}>
<Preset_nav_tab id="session_long"/>
<Preset_nav_tab id="rotating"/>
</Nav_tabs>
<Preview
title={presets.get(this.state.preset).new_title}>
<Preset_description preset={this.state.preset}/>
</Preview>
</Step>
</Li>
<Li>
<Step curr_step={this.state.step} step={2}
title={t('Example')}>
<Created_port
port={this.state.created_port}
lpm_token={lpm_token.split('|')[0]}
hostname={hostname}
/>
</Step>
</Li>
</Instructions>
</div>
</Modal>
<Modal className="warnings_modal" id="add_proxy_errors"
title="Errors:" no_cancel_btn>
<Warnings warnings={this.state.error_list}/>
</Modal>
</div>;
}
});
const Note = ({children})=><div className="note">{children}</div>;
const Step = ({title, step, curr_step, children})=>{
const classes = classnames('step_content', 'animated', {
shown: step==curr_step,
fadeIn: step==curr_step,
});
return <div>
<div className="step_title">{title}</div>
<div className={classes}>{children}</div>
</div>;
};
const Created_port = ({port, hostname, lpm_token})=>{
const to_copy = instructions.code(port, lpm_token, hostname).shell;
const code = prism.highlight(to_copy, prism.languages.clike);
return <div className="howto">
<Note>
<div className="created_port_number">
<div>
<T>Congrats!!! You've created a new port</T>: {port}
</div>
<Copy_icon text={port} />
</div>
</Note>
<div>
<Note>
<T>Start using the port by running the following command</T>:
</Note>
<div className="well instructions_well">
<pre>
<Code>
<div dangerouslySetInnerHTML={{__html: code}}/>
</Code>
</pre>
</div>
</div>
</div>;
};
class Ext_proxy extends Pure_component {
state = {consts: {}};
json_example = '[\'1.1.1.2\', \'my_username:my_password@1.2.3.4:8888\']';
placeholder = 'List the IPs for the external proxies that you\'d like to '
+'connect to.\nUse format: [username:password@]ip[:port]';
componentDidMount(){
this.setdb_on('head.consts', ({consts})=>
consts && this.setState({consts}));
}
on_change_list = val=>{
const {on_field_change, zagent} = this.props;
const {consts: {MAX_EXT_PROXIES}} = this.state;
on_field_change('ips_list')(val);
try {
const parsed = JSON.parse(val.replace(/'/g, '"'));
if (!Array.isArray(parsed))
throw {message: 'Proxies list has to be an array'};
if (!parsed.length)
throw {message: 'Proxies list array can not be empty'};
if (zagent && MAX_EXT_PROXIES!==undefined &&
parsed.length>MAX_EXT_PROXIES)
{
throw {message: `Maximum external proxies size is `
+MAX_EXT_PROXIES};
}
parsed.forEach(ip=>{
if (typeof ip!='string')
throw {message: 'Wrong format of proxies list'};
if (!ip)
throw {message: 'Proxy IP can not be an empty string'};
});
on_field_change('parsed_ips_list')(parsed);
on_field_change('parse_error')(null);
on_field_change('valid_json')(true);
} catch(e){
on_field_change('parse_error')(e.message);
on_field_change('valid_json')(false);
}
};
render(){
return <div className="ext_proxy">
<Textarea rows={6} val={this.props.ips_list}
placeholder={t(this.placeholder)}
on_change_wrapper={this.on_change_list}/>
<div className="json_example">
<strong><T>Example</T>: </strong>{this.json_example}
</div>
<div className="json_error">{this.props.parse_error}</div>
</div>;
}
}
const Lum_proxy = with_www_api(props=>{
const {zone, def_zone, on_field_change} = props;
const zone_tip = `Zone that will be used by this proxy port`;
return <div className="lum_proxy">
<Field icon_class="zone_icon" title="Zone">
<Select_zone val={zone} tooltip={zone_tip}
on_change_wrapper={on_field_change('zone')}/>
</Field>
<Preview title={zone||def_zone}>
<Zone_description zone_name={zone}/>
<a className="link" href={`${props.www_api}/cp/zones`}
target="_blank" rel="noopener noreferrer"><T>Edit zone</T></a>
</Preview>
</div>;
});
const Preset_nav_tab = ({id, ...props})=>{
const preset = presets.get(id);
return <Nav_tab
title={preset.new_title}
id={id}
{...props}
/>;
};
const Preview = ({title, children})=>{
return <div className="preview">
<div className="header">{title}</div>
{children}
</div>;
};
const Nav_tabs_wrapper = ({set_tab, cur_tab})=>
<Nav_tabs set_tab={set_tab} cur_tab={cur_tab}>
<Nav_tab title="Bright Data" id="proxy_lum"
tooltip="Proxy port using your Bright Data account"/>
<Nav_tab title="External" id="proxy_ext"
tooltip="Proxy port configured with external IP and credentials"/>
</Nav_tabs>;
const Field = props=>
<div className="field">
<div className="field_header">
<div className={classnames('icon', props.icon_class)}/>
<h4>{t(props.title)}:</h4>
</div>
{props.children ||
<Tooltip title={t(props.tooltip)}>
<select onChange={e=>props.on_change(e.target.value)}
value={props.val}>
{props.options.map((o, i)=>
<option key={i} value={o.value}>
{props.i18n ? t(o.key) : o.key}
</option>)}
</select>
</Tooltip>
}
</div>;
const Footer = props=>{
const btn_labels = ['Next', 'Create proxy port', 'Close'];
const btn_clicked = ()=>{
if (props.disabled)
return;
if (props.step==1)
return props.save_clicked();
if (props.step==2)
return $('#add_new_proxy_modal').modal('hide');
props.next_step();
};
const classes = classnames('btn', 'btn_lpm', 'btn_lpm_primary',
{disabled: props.disabled});
return <div className="footer">
<button onClick={btn_clicked} className={classes}>
<T>{btn_labels[props.step]}</T>
</button>
</div>;
};
export default Proxy_add;