@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
330 lines (320 loc) • 12.3 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint react:true, es6:true*/
import etask from '../../util/etask.js';
import ajax from '../../util/ajax.js';
import setdb from '../../util/setdb.js';
import React from 'react';
import $ from 'jquery';
import classnames from 'classnames';
import {Loader, Warnings, Code, Preset_description,
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} from './common/i18n.js';
import './css/proxy_add.less';
const Proxy_add = withRouter(class Proxy_add extends Pure_component {
presets_opt = Object.keys(presets).map(p=>{
let key = presets[p].title;
if (presets[p].default)
key = `Default (${key})`;
return {key, value: p};
});
state = {
zone: '',
preset: 'session_long',
show_loader: false,
cur_tab: 'proxy_lum',
error_list: [],
};
componentDidMount(){
this.setdb_on('head.proxies_running', proxies_running=>{
if (!proxies_running)
return;
this.setState({proxies_running});
});
this.setdb_on('head.zones', zones=>{
if (!zones)
return;
this.setState({def_zone: zones.def});
});
}
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[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 proxies = yield ajax.json({url: '/api/proxies_running'});
let port = 24000;
proxies.forEach(p=>{
if (p.port>=port)
port = p.port+1;
});
form.port = port;
const raw_resp = yield window.fetch('/api/proxies', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({proxy: form}),
});
const resp = yield raw_resp.json();
if (resp.errors)
return resp;
return {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.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({show_loader: false});
if (resp.port&&!_this.state.proxies_running.length)
_this.setState({created_port: resp.port});
else
$('#add_new_proxy_modal').modal('hide');
if (!resp.errors)
{
const proxies = yield ajax.json({url: '/api/proxies_running'});
setdb.set('head.proxies_running', proxies);
window.localStorage.setItem('quickstart-first-proxy',
resp.port);
if (opt.redirect)
{
yield etask.sleep(500);
$('#add_new_proxy_modal').modal('hide');
const state_opt = {};
if (opt.field)
state_opt.field = opt.field;
_this.props.history.push({pathname: `/proxy/${resp.port}`,
state: state_opt});
}
}
});
};
rule_clicked = field=>this.save({redirect: true, field});
field_changed = id=>value=>this.setState({[id]: value});
set_tab = id=>this.setState({cur_tab: id});
on_hidden = ()=>this.setState({created_port: null});
render(){
const disabled = this.state.cur_tab=='proxy_ext'&&
!this.state.valid_json;
const Footer_wrapper = <Footer save_clicked={this.save}
disabled={disabled} created_port={this.state.created_port}/>;
if (!this.state.proxies_running)
return null;
let content;
if (this.state.cur_tab=='proxy_lum')
{
content = <Lum_proxy
def_zone={this.state.def_zone}
created_port={this.state.created_port}
zone={this.state.zone}
on_field_change={this.field_changed.bind(this)}
preset={this.state.preset}
rule_clicked={this.rule_clicked}
presets_opt={this.presets_opt}/>;
}
else if (this.state.cur_tab=='proxy_ext')
{
content = <Ext_proxy
parse_error={this.state.parse_error}
ips_list={this.state.ips_list}
on_field_change={this.field_changed.bind(this)}/>;
}
const anim_classes = classnames('proxy_form', 'animated', {
proxy_created: this.state.created_port,
fadeOutUp: this.state.created_port,
});
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={anim_classes}>
<Nav_tabs_wrapper set_tab={this.set_tab}
cur_tab={this.state.cur_tab}/>
{content}
</div>
<Created_port port={this.state.created_port}/>
</Modal>
<Modal className="warnings_modal" id="add_proxy_errors"
title="Errors:" no_cancel_btn>
<Warnings warnings={this.state.error_list}/>
</Modal>
</div>;
}
});
const Created_port = ({port})=>{
if (!port)
return null;
const to_copy = instructions.code(port).shell;
const code = prism.highlight(to_copy, prism.languages.clike);
return <div className="howto animated fadeInUp">
<h3 style={{marginBottom: 15, marginTop: -20}}>
<T>Congratulation! You just created a port</T> {port}
</h3>
<span>
<T>
You can start using the port by running the following command:
</T>
</span>
<div className="well instructions_well">
<pre>
<Code>
<div dangerouslySetInnerHTML={{__html: code}}/>
</Code>
</pre>
</div>
</div>;
};
const Ext_proxy = ({ips_list, on_field_change, parse_error})=>{
const json_example = '[\'1.1.1.2\', '
+'\'my_username:my_password@1.2.3.4:8888\']';
const placeholder = 'List of IPs to the external proxies in the following '
+'format: [username:password@]ip[:port]';
const on_change_list = val=>{
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'};
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);
}
};
return <div className="ext_proxy">
<Textarea rows={6} val={ips_list}
placeholder={placeholder}
on_change_wrapper={on_change_list}/>
<div className="json_example">
<strong>Example: </strong>{json_example}
</div>
<div className="json_error">{parse_error}</div>
</div>;
};
const Lum_proxy = with_www_api(props=>{
const {zone, def_zone, on_field_change, preset, rule_clicked,
presets_opt} = props;
const preset_tip = 'Presets is a set of preconfigured configurations '
+'for specific purposes';
const zone_tip = `Zone that will be used by this proxy port`;
return <div className="lum_proxy">
<div className="group">
<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>
<div className="group">
<Field icon_class="preset_icon" val={preset} i18n
options={presets_opt} title="Preset configuration"
on_change={on_field_change('preset')} tooltip={preset_tip}/>
<Preview title={presets_opt.find(p=>p.value==preset).key}>
<Preset_description preset={preset} rule_clicked={rule_clicked}/>
</Preview>
</div>
</div>;
});
const Preview = ({title, children})=>{
return <div className="preview">
<div className="header"><T>{title}</T></div>
{children}
</div>;
};
const Nav_tabs_wrapper = ({set_tab, cur_tab})=>
<Nav_tabs set_tab={set_tab} cur_tab={cur_tab}>
<Nav_tab title="Luminati" id="proxy_lum"
tooltip="Proxy port using your Luminati account"/>
<Nav_tab title="External" id="proxy_ext"
tooltip="Proxy port configured with external IP and credentials"/>
</Nav_tabs>;
const Field = props=>
<T>{t=>
<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>
}</T>;
const Footer = props=>{
const save_clicked = ()=>{
if (props.disabled)
return;
props.save_clicked();
};
const ok_clicked = ()=>$('#add_new_proxy_modal').modal('hide');
const classes = classnames('btn', 'btn_lpm', 'btn_lpm_primary',
{disabled: props.disabled});
return <div className="footer">
{!props.created_port &&
<button onClick={save_clicked} className={classes}>
<T>Save</T>
</button>
}
{!!props.created_port &&
<button onClick={ok_clicked} className={classes}><T>OK</T></button>
}
</div>;
};
export default Proxy_add;