diff --git a/lib/NGCP/Panel/Controller/Peering.pm b/lib/NGCP/Panel/Controller/Peering.pm index 20f6ff549f..da2e2e6218 100644 --- a/lib/NGCP/Panel/Controller/Peering.pm +++ b/lib/NGCP/Panel/Controller/Peering.pm @@ -6,6 +6,7 @@ use parent 'Catalyst::Controller'; use NGCP::Panel::Form::Peering::Group; use NGCP::Panel::Form::Peering::Rule; +use NGCP::Panel::Form::Peering::InboundRule; use NGCP::Panel::Form::Peering::Server; use NGCP::Panel::Utils::DialogicImg; use NGCP::Panel::Utils::Message; @@ -95,6 +96,15 @@ sub base :Chained('group_list') :PathPart('') :CaptureArgs(1) { { name => 'description', search => 1, title => $c->loc('Description') }, { name => 'enabled', search => 1, title => $c->loc('Enabled') }, ]); + $c->stash->{inbound_rules_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ + { name => 'priority', search => 0, title => $c->loc('Priority') }, + { name => 'id', search => 1, title => $c->loc('#') }, + { name => 'field', search => 1, title => $c->loc('Field') }, + { name => 'pattern', search => 1, title => $c->loc('Pattern') }, + { name => 'reject_code', search => 1, title => $c->loc('Reject Code') }, + { name => 'reject_reason', search => 1, title => $c->loc('Reject Reason') }, + { name => 'enabled', search => 1, title => $c->loc('Enabled') }, + ]); $c->stash(group => {$res->get_columns}); @@ -107,7 +117,7 @@ sub edit :Chained('base') :PathPart('edit') { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); - my $form = NGCP::Panel::Form::Peering::Group->new; + my $form = NGCP::Panel::Form::Peering::Group->new(ctx => $c); my $params = { $c->stash->{group_result}->get_inflated_columns }; $params->{contract}{id} = delete $params->{peering_contract_id}; $params = merge($params, $c->session->{created_objects}); @@ -176,7 +186,7 @@ sub create :Chained('group_list') :PathPart('create') :Args(0) { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); - my $form = NGCP::Panel::Form::Peering::Group->new; + my $form = NGCP::Panel::Form::Peering::Group->new(ctx => $c); my $params = {}; $params = merge($params, $c->session->{created_objects}); $form->process( @@ -568,7 +578,7 @@ sub rules_create :Chained('rules_list') :PathPart('create') :Args(0) { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); - my $form = NGCP::Panel::Form::Peering::Rule->new; + my $form = NGCP::Panel::Form::Peering::Rule->new(ctx => $c); $form->process( posted => $posted, params => $c->request->params, @@ -640,7 +650,7 @@ sub rules_edit :Chained('rules_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); - my $form = NGCP::Panel::Form::Peering::Rule->new; + my $form = NGCP::Panel::Form::Peering::Rule->new(ctx => $c); $form->process( posted => $posted, params => $c->request->params, @@ -701,6 +711,230 @@ sub rules_delete :Chained('rules_base') :PathPart('delete') :Args(0) { return; } +sub inbound_rules_list :Chained('base') :PathPart('inboundrules') :CaptureArgs(0) { + my ($self, $c) = @_; + + my $sr_list_uri = $c->uri_for_action( + '/peering/servers_root', [$c->req->captures->[0]]); + $c->stash(sr_list_uri => $sr_list_uri); + $c->stash(template => 'peering/servers_rules.tt'); + return; +} + +sub inbound_rules_ajax :Chained('inbound_rules_list') :PathPart('r_ajax') :Args(0) { + my ($self, $c) = @_; + + my $resultset = $c->stash->{group_result}->voip_peer_inbound_rules->search(undef, { + order_by => {'-asc' => 'priority'}, + }); + NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{inbound_rules_dt_columns}); + $c->detach( $c->view("JSON") ); + return; +} + +sub inbound_rules_create :Chained('inbound_rules_list') :PathPart('create') :Args(0) { + my ($self, $c) = @_; + + my $posted = ($c->request->method eq 'POST'); + my $form = NGCP::Panel::Form::Peering::InboundRule->new(ctx => $c); + $form->process( + posted => $posted, + params => $c->request->params, + ); + NGCP::Panel::Utils::Navigation::check_form_buttons( + c => $c, + form => $form, + fields => {}, + back_uri => $c->req->uri, + ); + if($posted && $form->validated) { + try { + my $last_priority = $c->stash->{group_result}->voip_peer_inbound_rules->get_column('priority')->max() || 49; + $form->values->{priority} = $last_priority + 1; + $c->stash->{group_result}->voip_peer_inbound_rules->create($form->values); + $c->stash->{group_result}->update({has_inbound_rules => 1}); + NGCP::Panel::Utils::Message::info( + c => $c, + desc => $c->loc('Inbound peering rule successfully created'), + ); + } catch ($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to create inbound peering rule'), + ); + }; + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/peering/servers_root', [$c->req->captures->[0]])); + } + + $c->stash( + close_target => $c->uri_for_action('/peering/servers_root', [$c->req->captures->[0]]), + inbound_rules_create_flag => 1, + inbound_rules_form => $form, + ); + return; +} + +sub inbound_rules_base :Chained('inbound_rules_list') :PathPart('') :CaptureArgs(1) { + my ($self, $c, $rule_id) = @_; + + unless($rule_id && is_int($rule_id)) { + NGCP::Panel::Utils::Message::error( + c => $c, + log => 'Invalid inbound peering rule id detected', + desc => $c->loc('Invalid inbound peering rule id detected'), + ); + $c->response->redirect($c->stash->{sr_list_uri}); + $c->detach; + return; + } + + my $res = $c->stash->{group_result}->voip_peer_inbound_rules->find($rule_id); + unless(defined($res)) { + NGCP::Panel::Utils::Message::error( + c => $c, + log => 'Inbound Peering Rule does not exist', + desc => $c->loc('Inbound Peering Rule does not exist'), + ); + $c->response->redirect($c->stash->{sr_list_uri}); + $c->detach; + return; + } + $c->stash(inbound_rule => {$res->get_columns}); + $c->stash(inbound_rule_result => $res); + return; +} + +sub inbound_rules_edit :Chained('inbound_rules_base') :PathPart('edit') :Args(0) { + my ($self, $c) = @_; + + my $posted = ($c->request->method eq 'POST'); + my $form = NGCP::Panel::Form::Peering::InboundRule->new(ctx => $c); + $form->process( + posted => $posted, + params => $c->request->params, + item => $c->stash->{inbound_rule}, + ); + NGCP::Panel::Utils::Navigation::check_form_buttons( + c => $c, + form => $form, + fields => {}, + back_uri => $c->req->uri, + ); + if($posted && $form->validated) { + try { + $c->stash->{inbound_rule_result}->update($form->values); + NGCP::Panel::Utils::Message::info( + c => $c, + desc => $c->loc('Inbound peering rule successfully changed'), + ); + } catch ($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to update inbound peering rule'), + ); + }; + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/peering/servers_root', [$c->req->captures->[0]])); + } + + $c->stash( + close_target => $c->uri_for_action('/peering/servers_root', [$c->req->captures->[0]]), + inbound_rules_form => $form, + inbound_rules_edit_flag => 1, + ); + return; +} + +sub inbound_rules_delete :Chained('inbound_rules_base') :PathPart('delete') :Args(0) { + my ($self, $c) = @_; + + try { + $c->stash->{inbound_rule_result}->delete; + unless($c->stash->{group_result}->voip_peer_inbound_rules->count) + { + $c->stash->{group_result}->update({has_inbound_rules => 0}); + } + NGCP::Panel::Utils::Message::info( + c => $c, + data => { $c->stash->{inbound_rule_result}->get_inflated_columns }, + desc => $c->loc('Inbound peering rule successfully deleted'), + ); + } catch ($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to delete inbound peering rule'), + ); + }; + NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/peering/servers_root', [$c->req->captures->[0]])); + return; +} + +sub inbound_rules_up :Chained('inbound_rules_base') :PathPart('up') :Args(0) { + my ($self, $c) = @_; + + my $elem = $c->stash->{inbound_rule_result}; + + my $swap_elem = $c->stash->{group_result}->voip_peer_inbound_rules->search({ + priority => { '<' => $elem->priority }, + },{ + order_by => {'-desc' => 'priority'}, + })->first; + try { + if ($swap_elem) { + my $tmp_priority = $swap_elem->priority; + $swap_elem->priority($elem->priority); + $elem->priority($tmp_priority); + $swap_elem->update; + $elem->update; + } else { + my $last_priority = $c->stash->{group_result}->voip_peer_inbound_rules->get_column('priority')->min() || 1; + $elem->priority(int($last_priority) - 1); + $elem->update; + } + } catch($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to move inbound peering rule up.'), + ); + } + return; +} + +sub inbound_rules_down :Chained('inbound_rules_base') :PathPart('down') :Args(0) { + my ($self, $c) = @_; + + my $elem = $c->stash->{inbound_rule_result}; + + my $swap_elem = $c->stash->{group_result}->voip_peer_inbound_rules->search({ + priority => { '>' => $elem->priority }, + },{ + order_by => {'-asc' => 'priority'}, + })->first; + try { + if ($swap_elem) { + my $tmp_priority = $swap_elem->priority; + $swap_elem->priority($elem->priority); + $elem->priority($tmp_priority); + $swap_elem->update; + $elem->update; + } else { + my $last_priority = $c->stash->{group_result}->voip_peer_inbound_rules->get_column('priority')->max() || 49; + $elem->priority(int($last_priority) + 1); + $elem->update; + } + } catch($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to move inbound peering rule down.'), + ); + } + return; +} + __PACKAGE__->meta->make_immutable; diff --git a/lib/NGCP/Panel/Form/Peering/InboundRule.pm b/lib/NGCP/Panel/Form/Peering/InboundRule.pm new file mode 100644 index 0000000000..fd0cb7b026 --- /dev/null +++ b/lib/NGCP/Panel/Form/Peering/InboundRule.pm @@ -0,0 +1,112 @@ +package NGCP::Panel::Form::Peering::InboundRule; +use Sipwise::Base; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +has_field 'submitid' => ( type => 'Hidden' ); +sub build_render_list {[qw/submitid fields actions/]} +sub build_form_element_class {[qw(form-horizontal)]} + +has_field 'field' => ( + type => 'Select', + label => 'Match Field', + options => [ + { value => 'from_user', label => 'From-User' }, + { value => 'from_domain', label => 'From-Domain' }, + { value => 'from_uri', label => 'From-URI' }, + { value => 'to_user', label => 'To-User' }, + { value => 'to_domain', label => 'To-Domain' }, + { value => 'to_uri', label => 'To-URI' }, + { value => 'ruri_user', label => 'RURI-User' }, + { value => 'ruri_domain', label => 'RURI-Domain' }, + { value => 'ruri_uri', label => 'RURI-URI' }, + { value => 'pai_user', label => 'PAI-User' }, + { value => 'pai_domain', label => 'PAI-Domain' }, + { value => 'pai_uri', label => 'PAI-URI' }, + ], + element_attr => { + rel => ['tooltip'], + title => ['The field of the inbound SIP message to match the pattern against'] + }, +); + +has_field 'pattern' => ( + type => '+NGCP::Panel::Field::Regexp', + max_length => 1023, + element_attr => { + rel => ['tooltip'], + title => [q!A POSIX regex matching against the specified field (e.g. '^sip:.+@example\.org$' or '^sip:431') when matching against a full URI!] + }, +); + +has_field 'reject_code' => ( + type => 'PosInteger', + not_nullable => 0, + range_start => 400, + range_end => 699, + element_attr => { + rel => ['tooltip'], + title => ['If specified, the call is rejected if the source IP of the request is found in a peering server of the group, but the given pattern does not match; Range of 400-699'] + }, +); + +has_field 'reject_reason' => ( + type => 'Text', + not_nullable => 0, + max_length => 64, + element_attr => { + rel => ['tooltip'], + title => ['If reject code is specified and the call is rejected, the reason in the response is taken from this value'] + }, +); + +has_field 'enabled' => ( + type => 'Boolean', + label => 'Enabled', + default => 1, + element_attr => { + rel => ['tooltip'], + title => ['Rule enabled state.'], + }, +); + +has_field 'save' => ( + type => 'Submit', + value => 'Save', + element_class => [qw/btn btn-primary/], + label => '', +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/field pattern reject_code reject_reason enabled/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +sub validate { + my ($self) = @_; + my $c = $self->ctx; + + my $reason = $self->field('reject_reason'); + my $code = $self->field('reject_code'); + + if(defined $code->value && !defined $reason->value) { + return $reason->add_error($c->loc('reject reason must be filled if reject code is filled')); + } elsif(defined $reason->value && !defined $code->value) { + return $code->add_error($c->loc('reject code must be filled if reject reason is filled')); + } + + return; +} + +1; + diff --git a/share/templates/peering/servers_rules.tt b/share/templates/peering/servers_rules.tt index ba971169ee..80166f9f95 100644 --- a/share/templates/peering/servers_rules.tt +++ b/share/templates/peering/servers_rules.tt @@ -5,6 +5,7 @@ helper.identifier = 'peering_servers'; helper.messages = messages; helper.dt_columns = server_dt_columns; + helper.length_change = 1; helper.close_target = close_target; helper.create_flag = servers_create_flag; @@ -32,9 +33,10 @@ -%]
-

[% c.loc('Peering Rules') %]

+

[% c.loc('Outbound Peering Rules') %]

+

ANY of the rules must match to choose the peering group for outbound calls.

[% - helper.name = c.loc('Peering Rule'); + helper.name = c.loc('Outbound Peering Rule'); helper.identifier = 'PeeringRules'; helper.messages = rules_messages; helper.dt_columns = rules_dt_columns; @@ -42,6 +44,7 @@ helper.create_flag = rules_create_flag; helper.edit_flag = rules_edit_flag; helper.form_object = rules_form; + helper.length_change = 1; helper.ajax_uri = c.uri_for_action( "/peering/rules_ajax", [c.req.captures.0] ); helper.tmpuri = c.uri_for(group.id, "rules"); @@ -51,7 +54,41 @@ { name = c.loc('Delete'), uri = helper.tmpuri _ "/'+full[\"id\"]+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' }, ]; helper.top_buttons = [ - { name = c.loc('Create Peering Rule'), uri = helper.tmpuri _ "/create", icon = 'icon-star' }, + { name = c.loc('Create Outbound Peering Rule'), uri = helper.tmpuri _ "/create", icon = 'icon-star' }, + ]; + ELSE; + helper.dt_buttons = []; + helper.top_buttons = []; + END; + + PROCESS 'helpers/datatables.tt'; +-%] + +
+

[% c.loc('Inbound Peering Rules') %]

+

ALL of the rules must match to choose the peering group for inbound calls.

+[% + helper.name = c.loc('Inbound Peering Rule'); + helper.identifier = 'InboundPeeringRules'; + helper.messages = inbound_rules_messages; + helper.dt_columns = inbound_rules_dt_columns; + helper.close_target = close_target; + helper.create_flag = inbound_rules_create_flag; + helper.edit_flag = inbound_rules_edit_flag; + helper.form_object = inbound_rules_form; + helper.length_change = 1; + helper.ajax_uri = c.uri_for_action( "/peering/inbound_rules_ajax", [c.req.captures.0] ); + + helper.tmpuri = c.uri_for(group.id, "inboundrules"); + UNLESS c.user.read_only; + helper.dt_buttons = [ + { name = c.loc('Up'), uri = helper.tmpuri _ "/'+full[\"id\"]+'/up", class = 'btn-small btn-primary', icon = 'icon-arrow-up' }, + { name = c.loc('Down'), uri = helper.tmpuri _ "/'+full[\"id\"]+'/down", class = 'btn-small btn-primary', icon = 'icon-arrow-down' }, + { name = c.loc('Edit'), uri = helper.tmpuri _ "/'+full[\"id\"]+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' }, + { name = c.loc('Delete'), uri = helper.tmpuri _ "/'+full[\"id\"]+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' }, + ]; + helper.top_buttons = [ + { name = c.loc('Create Inbound Peering Rule'), uri = helper.tmpuri _ "/create", icon = 'icon-star' }, ]; ELSE; helper.dt_buttons = [];