@luminati-io/luminati-proxy
Version:
A configurable local proxy for luminati.io
311 lines (309 loc) • 11.6 kB
JavaScript
// LICENSE_CODE ZON ISC
'use strict'; /*jslint react:true, es6:true*/
import React from 'react';
import Pure_component from '/www/util/pub/pure_component.js';
import $ from 'jquery';
import _ from 'lodash';
import setdb from '../../../util/setdb.js';
import ajax from '../../../util/ajax.js';
import zurl from '../../../util/url.js';
import {Modal} from '../common/modals.js';
import {report_exception} from '../util.js';
import {Infinite_chrome_table} from '../chrome_widgets.js';
import conv from '../../../util/conv.js';
export default class Alloc_modal extends Pure_component {
set_field = setdb.get('head.proxy_edit.set_field');
state = {
available_list: [],
rendered_list: [],
selected_all: false,
refresh_cost: 0,
ip_filter: '',
};
constructor(props){
super(props);
let sync_rendered_list = ()=>this.setState(
{rendered_list: this.filter_ips(this.state.available_list)});
this.sync_rendered_list = _.debounce(sync_rendered_list, 300);
}
componentDidMount(){
this.setdb_on('head.proxy_edit.zone_name', ()=>{
this.setState({available_list: [], rendered_list: []});
});
this.setdb_on('head.proxies_running', proxies=>
proxies && this.setState({proxies}));
$('#allocated_ips').on('show.bs.modal', this.load);
this.load_refresh_cost();
}
componentDidUpdate(prevProps){
if (prevProps.zone!=this.props.zone)
this.load_refresh_cost();
}
static getDerivedStateFromProps(props, state){
if (!state.available_list.length)
return null;
return {
selected_all: (props.form[props.type]||[]).length==
state.available_list.length,
};
}
close = ()=>$('#allocated_ips').modal('hide');
load = ()=>{
const type = this.props.type;
const form = this.props.form;
if (this.state.available_list.length)
return;
this.loading(true);
let endpoint;
if (type=='ips')
endpoint = '/api/allocated_ips';
else
endpoint = '/api/allocated_vips';
const url = `${endpoint}?zone=${this.props.zone}`;
const _this = this;
this.etask(function*(){
this.on('uncaught', e=>_this.etask(function*(){
yield report_exception(e, 'alloc_modal.Alloc_modal.load');
_this.loading(false);
}));
const res = yield ajax.json({url});
let _available_list;
if (type=='ips')
_available_list = res.ips;
else
_available_list = res;
const available_set = new Set();
_available_list.forEach(v=>available_set.add(v));
const chosen_set = new Set();
form[type].forEach(v=>{
if (available_set.has(v))
chosen_set.add(v);
});
const not_chosen_set = new Set();
_available_list.forEach(v=>{
if (!chosen_set.has(v))
not_chosen_set.add(v);
});
const available_list = [...chosen_set, ...not_chosen_set];
_this.setState({available_list,
rendered_list: _this.filter_ips(available_list)},
_this.sync_selected_vals);
_this.loading(false);
});
};
sync_selected_vals = ()=>{
const curr_vals = this.props.form[this.props.type];
const new_vals = curr_vals.filter(v=>
this.state.available_list.includes(v));
this.set_field(this.props.type, new_vals);
};
loading = loading=>{
setdb.set('head.proxy_edit.loading', loading);
this.setState({loading});
};
checked = row=>(this.props.form[this.props.type]||[]).includes(row);
toggle = e=>{
const value = e.rowData;
const checked = !this.checked(value);
const {type, form} = this.props;
let new_alloc;
if (checked)
{
const selected = new Set();
form[type].forEach(v=>selected.add(v));
selected.add(value);
new_alloc = this.state.available_list.filter(v=>selected.has(v));
}
else
new_alloc = form[type].filter(r=>r!=value);
this.set_field(type, new_alloc);
this.update_multiply_and_pool_size(new_alloc.length);
};
unselect_all = ()=>{
this.set_field(this.props.type, []);
this.set_field('pool_size', '');
this.set_field('multiply', 1);
};
select_all = ()=>{
this.set_field(this.props.type, this.state.available_list);
this.update_multiply_and_pool_size(this.state.available_list.length);
};
refresh_chosen = ()=>{
if (this.props.type=='ips')
this.refresh(this.props.form.ips);
else
this.refresh(this.props.form.vips);
};
refresh_one = val=>{
this.refresh([val]);
};
refresh = vals=>{
const _this = this;
this.etask(function*(){
this.on('uncaught', e=>_this.etask(function*(){
yield report_exception(e,
'alloc_modal.Alloc_modal.refresh.uncaught');
}));
this.on('finally', ()=>{
_this.loading(false);
});
_this.loading(true);
const data = {zone: _this.props.zone};
let url;
if (_this.props.type=='ips')
{
data.ips = vals.map(zurl.ip2num).join(' ');
url = '/api/refresh_ips';
}
else
{
data.vips = vals;
url = '/api/refresh_vips';
}
const res = yield ajax.json({method: 'POST', url, data});
if (res.error || !res.ips && !res.vips)
{
return void (yield report_exception(res.error,
'alloc_modal.Alloc_modal.refresh'));
}
const new_vals = _this.props.type=='ips' ?
res.ips.map(i=>i.ip) : res.vips.map(v=>v.vip);
const norm_vals = _this.normalize_vals(new_vals);
const map = _this.map_vals(norm_vals);
const new_ips = _this.props.form.ips.map(val=>map[val]);
const new_vips = _this.props.form.vips.map(val=>map[val]);
_this.setState({available_list: norm_vals,
rendered_list: _this.filter_ips(norm_vals)});
_this.set_field('ips', new_ips);
_this.set_field('vips', new_vips);
yield _this.update_other_proxies(map);
});
};
update_other_proxies = map=>{
const _this = this;
return this.etask(function*(){
const proxies_to_update = _this.state.proxies.filter(p=>
p.zone==_this.props.zone && p.port!=_this.props.form.port &&
p.proxy_type=='persist');
for (let i=0; i<proxies_to_update.length; i++)
{
const proxy = proxies_to_update[i];
const new_vals = proxy[_this.props.type].map(v=>map[v]);
const data = {port: proxy.port, [_this.props.type]: new_vals};
yield ajax({method: 'POST', url: '/api/update_ips', data});
}
});
};
normalize_vals = new_vals=>{
const old_vals = this.state.available_list;
if (old_vals.length!=new_vals.length)
{
this.etask(function*(){
yield report_exception('error ips/vips length mismatch',
'alloc_modal.Alloc_modal.normalize_vals');
return;
});
}
const old_set = new Set();
old_vals.forEach(v=>old_set.add(v));
const refreshed = [];
const stable = new Set();
for (let new_val of new_vals)
{
if (old_set.has(new_val))
stable.add(new_val);
else
refreshed.push(new_val);
}
const normalized = [];
for (let old_val of old_vals)
{
if (stable.has(old_val))
normalized.push(old_val);
else
normalized.push(refreshed.shift());
}
return normalized;
};
map_vals = normalized=>{
const old_vals = this.state.available_list;
const map = {};
for (let i = 0; i < old_vals.length; i++)
map[old_vals[i]] = normalized[i];
return map;
};
update_multiply_and_pool_size = size=>{
if (!this.props.form.multiply_ips && !this.props.form.multiply_vips)
this.set_field('pool_size', size);
else
{
this.set_field('pool_size', 1);
this.set_field('multiply', size);
}
};
is_refresh_enabled = ()=>{
const {plan, type} = this.props;
return type!='ips' || !!plan.ips;
};
cols = [
{id: 'ip', title: 'IP'},
];
load_refresh_cost(){
if (!this.props.zone || !this.is_refresh_enabled())
return;
const _this = this;
this.etask(function*(){
const response = yield ajax.json({url: '/api/refresh_cost',
qs: {zone: _this.props.zone}});
_this.setState({refresh_cost: response.cost});
});
}
on_filter_change(e){
this.setState({ip_filter: e.target.value}, this.sync_rendered_list);
}
filter_ips(available_list){
let rows = available_list;
if (this.state.ip_filter)
rows = rows.filter(ip=>ip.indexOf(this.state.ip_filter)>=0);
return rows;
}
render(){
const type_label = this.props.type=='ips' ? 'IPs' : 'gIPs';
const title = 'Select the '+type_label+' ('+this.props.zone+')';
const refresh_enabled = this.is_refresh_enabled();
const selected_list = this.props.form[this.props.type]||[];
const refresh_cost = selected_list.length*this.state.refresh_cost;
const Footer = <div className="default_footer">
{refresh_enabled &&
<button onClick={this.refresh_chosen} className="btn btn_lpm"
disabled={!selected_list.length}>
Refresh
{refresh_cost>0 && ` (${conv.fmt_currency(refresh_cost)})`}
</button>
}
<button onClick={this.close}
className="btn btn_lpm btn_lpm_primary">OK</button>
</div>;
const sub_title = `IPs: ${selected_list.length}/`
+`${this.props.form.pool_size} out of`
+` ${this.state.available_list.length} available`;
return <Modal id="allocated_ips" className="allocated_ips_modal"
title={title} footer={Footer}>
<Infinite_chrome_table cols={this.cols}
title={sub_title}
toolbar={<div className="search_box">
<input value={this.state.ip_filter} placeholder="IP filter"
onChange={e=>this.on_filter_change(e)}/>
</div>}
class_name="in_modal_table"
selectable
toggle={this.toggle}
select_all={this.select_all}
unselect_all={this.unselect_all}
selected_list={selected_list}
selected_all={this.state.selected_all}
rows={this.state.rendered_list}>
</Infinite_chrome_table>
</Modal>;
}
}