@luminati-io/luminati-proxy
Version:
A configurable local proxy for brightdata.com
426 lines (418 loc) • 16.4 kB
JavaScript
// LICENSE_CODE ZON ISC
; /*jslint react:true, es6:true*/
import React, {useState} from 'react';
import _ from 'lodash4';
import $ from 'jquery';
import {Button, IconButton} from 'uikit';
import Pure_component from '/www/util/pub/pure_component.js';
import setdb from '../../util/setdb.js';
import zurl from '../../util/url.js';
import {report_exception, get_form_toggle_transform} from './util.js';
import {Select_zone_new, Pins_new} from './common/controls.js';
import {Accept_save_changes_popup} from './common/modals.js';
import Warnings_modal from './common/warnings_modal.js';
import Logs_settings_modal from './common/logs_settings_modal.js';
import Exit_confirm_modal from './common/exit_confirm_modal.js';
import {T} from './common/i18n.js';
import {
Labeled_controller_new,
Labeled_section,
Alert,
} from './common.js';
import {main as Api} from './api.js';
import './css/settings.less';
const Settings = props=>{
const [show_alert, set_show_alert] = useState(false);
const [settings_changed, set_settings_changed] = useState(false);
return <div className="settings vbox">
<div className="cp_panel vbox force_cp_panel">
{!props.zagent &&
<div className="cp_panel_header">
<h2 className="section_title"><T>General settings</T></h2>
</div>
}
<Form show_alert={set_show_alert}
settings_changed={set_settings_changed}/>
</div>
{show_alert &&
<Alert
variant="success"
dismissible
text="Settings changes saved"
on_close={()=>set_show_alert(false)}
/>
}
<Exit_confirm_modal should_show={settings_changed} />
</div>;
};
export default Settings;
const tooltips = {
zone: `Default zone will be used automatically when creating a new
port, if you don't specify any specific zone. This value can be
overridden in each proxy port settings`,
www_whitelist_ips: `List of IPs that are allowed to access web UI
(including all API endpoints at http://localhost:22999/api) and
make changes. can also include ranges of ips like so 0.0.0.0/0.
Default value is 127.0.0.1, which means that remote access from
any other IP is blocked unless list of IPs are added in this
field.`,
whitelist_ips: `Default access grant for all proxies. Only those
IPs will be able to send requests to all proxies by default. Can
be changed per proxy`,
request_stats: `Enable saving statistics to database`,
logs: `Specify how many requests you want to keep in database. The
limit may be set as a number or maximum database size.`,
logs_settings: `Limit is 1000`,
har_limit: `Define the limit for the size of the response body to save in
the logs`,
debug: `Default value for Bright Data request details like response
timeline or peer IP that was used to send a final request`,
lpm_auth: 'Default value for "Set x-lpm-authorization header" setting',
log_level: `Define how much log you want to see in the terminal</br>
<ul>
<li><strong>error: </strong>only error messages</li>
<li><strong>warn: </strong>all the above and warnings, potential
unsuccessful operations</li>
<li><strong>notice: </strong>all the above and the essential
notifications like creating proxy ports, refreshing sessions</li>
<li><strong>info: </strong>all the above and proxy requests, API
calls</li>
<li><strong>debug: </strong>all the above and debug info</li>
</ul>`,
sync_config: `All changes on Proxy Manager instances with enabled config
synchronization will be propagated and applied immediately.`,
bw_limit_webhook_url: `URL to send webhook messages to when BW limit is
reached`,
bw_th_webhook_url: `URL to send webhook messages to when BW limit threshold
is reached`,
};
for (let f in tooltips)
tooltips[f] = tooltips[f].replace(/\s+/g, ' ').replace(/\n/g, ' ');
let har_limit_options = [
{value: -1, label: 'Disabled'},
{value: 1024, label: '1Kb (default)'},
{value: 100*1024, label: '100Kb'},
{value: 0, label: 'Unlimited'},
];
const debug_values = ['none', 'full'];
class Form extends Pure_component {
constructor(props){
super(props);
this.state = {
saving: false,
form: {},
pending_settings: {},
show_save_settings_modal: false,
show_logs_settings_modal: false,
};
this.logs_metric_opts = [
{key: 'requests', value: 'requests'},
{key: 'megabytes', value: 'megabytes'},
];
this.log_level_opts = ['error', 'warn', 'notice', 'info', 'debug'];
}
componentDidMount(){
this.setdb_on('head.settings', settings=>{
if (!settings)
return;
const c_settings = _.cloneDeep(settings);
const res_settings = {...c_settings,
...this.state.pending_settings};
this.setState({
settings: res_settings, default_settings: c_settings,
is_changed: !_.isEqual(c_settings, res_settings)});
});
this.setdb_on('head.save_settings', save_settings=>{
this.save_settings = save_settings;
if (save_settings && this.resave)
{
delete this.resave;
this.save();
}
});
}
zone_change = val=>{
this.setState(prev=>({settings: {...prev.settings, zone: val}}),
this.debounced_save);
};
prepare_change = ({field, value, opt})=>{
opt = opt||{};
const {settings} = this.state;
let val = value;
if (field=='whitelist_ips')
val = value.filter(ip=>!settings.fixed_whitelist_ips.includes(ip));
if (opt.number)
val = +val;
return {field, value: val};
};
apply_changes = changes=>{
const {settings, pending_settings, default_settings} = this.state;
let changes_obj = changes.reduce((acc, ch)=>Object.assign(acc,
{[ch.field]: ch.value}), {});
const c_settings = {...settings, ...changes_obj};
const is_changed = !_.isEqual(default_settings, c_settings);
this.setState({
settings: c_settings,
pending_settings: {...pending_settings, ...changes_obj},
is_changed
});
this.props.settings_changed(is_changed);
};
on_change_handler = (field, opt)=>value=>
this.on_multi_change_handler([{field, opt, value}]);
on_multi_change_handler = changes=>
this.apply_changes(changes.map(this.prepare_change));
remote_logs_enabled = ()=>this.state.settings
&& this.state.settings.logs_settings
&& this.state.settings.logs_settings.type;
logs_enabled = ()=>this.state.settings.logs || this.remote_logs_enabled();
toggle_logs = ()=>{
let settings = [
{field: 'logs', value: 0, opt: {number: 1}},
{field: 'logs_settings', value: {}},
];
if (this.logs_enabled())
return this.apply_changes(settings.map(this.prepare_change));
settings[0].value = 1000;
this.apply_changes(settings.map(this.prepare_change));
};
lock_nav = lock=>setdb.set('head.lock_navigation', lock);
urls_valid = ()=>{
const {bw_limit_webhook_url: limit_url,
bw_th_webhook_url: th_url} = this.state.pending_settings;
for (const url of [limit_url, th_url].filter(Boolean))
{
if (zurl.is_valid_url(url))
continue;
this.setState({error: [{msg: 'Invalid webhook url'}]},
()=>$('#upd_settings_error').modal('show'));
return false;
}
return true;
};
save = ()=>{
if (!this.urls_valid())
return;
if (this.saving || !this.save_settings)
{
this.resave = true;
return;
}
this.lock_nav(true);
this.setState({
saving: true,
show_save_settings_modal: false,
});
this.saving = true;
const _this = this;
this.etask(function*(){
this.on('uncaught', e=>_this.etask(function*(){
yield report_exception(e, 'settings.Form.save');
}));
this.on('finally', ()=>{
_this.setState({saving: false});
_this.saving = false;
_this.lock_nav(false);
if (_this.resave)
{
delete _this.resave;
_this.save();
}
});
const body = {..._this.state.pending_settings};
const save_res = yield _this.save_settings(body);
if (save_res.err)
{
return _this.setState({error: [{msg: save_res.err}]}, ()=>
$('#upd_settings_error').modal('show'));
}
_this.setState({is_changed: false, pending_settings: {},
default_settings: _this.state.settings});
_this.props.show_alert(true);
_this.props.settings_changed(false);
if (_this.state.settings.sync_config)
{
const proxies = yield Api.json.get('proxies_running');
setdb.set('head.proxies_running', proxies);
}
});
};
ch_logs_settings_modal = val=>()=>
this.setState({show_logs_settings_modal: val});
debounced_save = _.debounce(this.save, 500);
set_show_save_settings_modal = (val=true)=>
this.setState({show_save_settings_modal: val});
render(){
const s = this.state.settings;
if (!s)
return null;
const wl = s.fixed_whitelist_ips.concat(s.whitelist_ips);
const logs_data = s.zagent ? [0, 1000] : [0, 100, 1000, 10000];
const har_limit_data = s.zagent ? har_limit_options.filter(({value})=>
[-1, 1024].includes(value)) : har_limit_options;
const note_logs = this.logs_enabled() ?
<IconButton
aria-label="Icon Button"
icon="Settings"
noBackColor
onClick={this.ch_logs_settings_modal(true)}
size="xs"
tooltip="Logs settings"
variant="icon"
/> : null;
return <div className="settings_form">
<Warnings_modal
id='upd_settings_error'
warnings={this.state.error}
/>
{this.logs_enabled() &&
<Logs_settings_modal
tooltip={tooltips.logs_settings}
logs_data={logs_data}
logs_disabled_num={logs_data[0]||0}
logs_enabled_num={logs_data[1]||1000}
settings={s.logs_settings}
on_save={this.on_multi_change_handler}
remote_enabled={this.remote_logs_enabled()}
show={this.state.show_logs_settings_modal}
on_hide={this.ch_logs_settings_modal(false)}
/>
}
<Labeled_section label="Presets configuration">
<Labeled_controller_new label="Default zone"
tooltip={tooltips.zone}>
<Select_zone_new
val={s.zone}
preview
on_change_wrapper={this.on_change_handler('zone')}
/>
</Labeled_controller_new>
</Labeled_section>
<Labeled_section
label="Admin whitelisted IPs"
faq={{anchor: 'whitelisted_ips'}}
tooltip={tooltips.www_whitelist_ips}>
<Pins_new
val={s.www_whitelist_ips}
pending={s.pending_www_ips}
no_any={s.zagent}
on_change_wrapper={this.on_change_handler('www_whitelist_ips')}
/>
</Labeled_section>
<Labeled_section
label="Proxy whitelisted IPs"
faq={{anchor: 'whitelisted_ips'}}
tooltip={tooltips.whitelist_ips}>
<Pins_new
val={wl}
pending={s.pending_ips}
no_any={s.zagent}
disabled_ips={s.fixed_whitelist_ips}
on_change_wrapper={this.on_change_handler('whitelist_ips')}
/>
</Labeled_section>
{!s.zagent && <Labeled_section
label="Enable recent stats"
tooltip={tooltips.request_stats}
type="toggle"
on_change_wrapper={this.on_change_handler('request_stats')}
val={s.request_stats}
default={true}
/>}
{!s.zagent && <Labeled_section
val={s.logs}
type="select_number"
on_change_wrapper={this.on_change_handler('logs', {number: 1})}
data={logs_data}
label="Limit for request logs"
default tooltip={tooltips.logs}
/>}
{s.zagent && <Labeled_section
label="Enable request logs"
tooltip={tooltips.logs}
type="toggle"
on_change_wrapper={this.toggle_logs}
val={this.logs_enabled()}
note={note_logs}
/>}
<Labeled_section
val={s.har_limit}
type="select_number"
on_change_wrapper={this.on_change_handler('har_limit',
{number: 1})}
data={har_limit_data}
label="Response limit to save"
default={1024}
tooltip={tooltips.har_limit}
/>
<Labeled_section
label="Default requests details"
faq={{anchor: 'request_details'}}
tooltip={tooltips.debug}
type="toggle"
toggle_transform={get_form_toggle_transform(debug_values)}
on_change_wrapper={this.on_change_handler('debug')}
val={s.debug}
/>
<Labeled_section
label="Default LPM auth. header"
tooltip={tooltips.lpm_auth}
type="toggle"
toggle_transform={get_form_toggle_transform(debug_values)}
on_change_wrapper={this.on_change_handler('lpm_auth')}
val={s.lpm_auth}
/>
{!s.zagent && <Labeled_section
val={s.log}
type="select"
faq={{article: '13596408374417', anchor: 'gathering_logs'}}
on_change_wrapper={this.on_change_handler('log')}
data={this.log_level_opts}
label="Log level / API logs"
tooltip={tooltips.log_level}
/>}
<Labeled_section
label="Sync configuration"
faq={{anchor: 'sync_configuration'}}
tooltip={tooltips.sync_config}
type="toggle"
on_change_wrapper={this.on_change_handler('sync_config')}
default={!!s.zagent}
disabled={s.zagent}
val={s.sync_config}
/>
{s.zagent && <Labeled_section
val={s.bw_limit_webhook_url||''}
allow_empty_url
allow_bad_url_change
type="url"
on_change_wrapper={this.on_change_handler('bw_limit_webhook_url')}
label="BW limit webhook URL"
tooltip={tooltips.bw_limit_webhook_url}
/>}
{s.zagent && <Labeled_section
val={s.bw_th_webhook_url||''}
allow_empty_url
allow_bad_url_change
type="url"
on_change_wrapper={this.on_change_handler('bw_th_webhook_url')}
label="BW threshold webhook URL"
tooltip={tooltips.bw_th_webhook_url}
/>}
<div className='settings_btn_container'>
<Button
text="Save"
loadingText="Saving"
onClick={()=>this.set_show_save_settings_modal(true)}
disabled={!this.state.is_changed}
loading={this.state.saving}
/>
</div>
<Accept_save_changes_popup
show={this.state.show_save_settings_modal}
onOk={this.save}
onCancel={()=>this.set_show_save_settings_modal(false)}
/>
</div>;
}
}