From 48f1a3a3a5d02436111a5720b67888ce5060c61b Mon Sep 17 00:00:00 2001 From: Gerhard Jungwirth Date: Thu, 13 Jun 2013 15:24:23 +0200 Subject: [PATCH] Rewrite Rule Set and Rewrite Rule Management --- lib/NGCP/Panel/Controller/Rewrite.pm | 362 ++++++++++++++++++ lib/NGCP/Panel/Form/RewriteRule.pm | 126 ++++++ lib/NGCP/Panel/Form/RewriteRuleSet.pm | 65 ++++ share/templates/rewrite/rules_list.tt | 92 +++++ share/templates/rewrite/set_list.tt | 23 ++ .../widgets/admin_topmenu_settings.tt | 1 + t/controller_Rewrite.t | 10 + 7 files changed, 679 insertions(+) create mode 100644 lib/NGCP/Panel/Controller/Rewrite.pm create mode 100644 lib/NGCP/Panel/Form/RewriteRule.pm create mode 100644 lib/NGCP/Panel/Form/RewriteRuleSet.pm create mode 100644 share/templates/rewrite/rules_list.tt create mode 100644 share/templates/rewrite/set_list.tt create mode 100644 t/controller_Rewrite.t diff --git a/lib/NGCP/Panel/Controller/Rewrite.pm b/lib/NGCP/Panel/Controller/Rewrite.pm new file mode 100644 index 0000000000..2d0a9ff9a4 --- /dev/null +++ b/lib/NGCP/Panel/Controller/Rewrite.pm @@ -0,0 +1,362 @@ +package NGCP::Panel::Controller::Rewrite; +use Sipwise::Base; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller'; } + +use NGCP::Panel::Form::RewriteRuleSet; +use NGCP::Panel::Form::RewriteRule; + +sub set_list :Chained('/') :PathPart('rewrite') :CaptureArgs(0) { + my ( $self, $c ) = @_; + + $c->stash(has_edit => 1); + $c->stash(has_delete => 1); + $c->stash(template => 'rewrite/set_list.tt'); +} + +sub set_root :Chained('set_list') :PathPart('') :Args(0) { + my ($self, $c) = @_; +} + +sub set_ajax :Chained('set_list') :PathPart('ajax') :Args(0) { + my ($self, $c) = @_; + + my $resultset = $c->model('provisioning')->resultset('voip_rewrite_rule_sets'); + + $c->forward( "/ajax_process_resultset", [$resultset, + ["id", "name", "description"], + [1,2]]); + + $c->detach( $c->view("JSON") ); +} + +sub set_base :Chained('set_list') :PathPart('') :CaptureArgs(1) { + my ($self, $c, $set_id) = @_; + + unless($set_id && $set_id->is_integer) { + $c->flash(messages => [{type => 'error', text => 'Invalid rewrite rule set id detected!'}]); + $c->response->redirect($c->uri_for()); + $c->detach; + return; + } + + my $res = $c->model('provisioning')->resultset('voip_rewrite_rule_sets')->find($set_id); + unless(defined($res)) { + $c->flash(messages => [{type => 'error', text => 'Rewrite rule set does not exist!'}]); + $c->response->redirect($c->uri_for()); + $c->detach; + return; + } + $c->stash(set_result => $res); +} + +sub set_edit :Chained('set_base') :PathPart('edit') { + my ($self, $c) = @_; + + my $posted = ($c->request->method eq 'POST'); + my $form = NGCP::Panel::Form::RewriteRuleSet->new; + $form->process( + posted => $posted, + params => $c->request->params, + action => $c->uri_for_action('/rewrite/set_edit'), + item => $c->stash->{set_result}, + ); + if($form->validated) { + $c->flash(messages => [{type => 'success', text => 'Rewrite Rule Set successfully changed!'}]); + $c->response->redirect($c->uri_for()); + return; + } + + $c->stash(form => $form); + $c->stash(edit_flag => 1); +} + +sub set_delete :Chained('set_base') :PathPart('delete') { + my ($self, $c) = @_; + + try { + $c->stash->{set_result}->delete; + $c->flash(messages => [{type => 'success', text => 'Rewrite Rule Set successfully deleted!'}]); + } catch (DBIx::Class::Exception $e) { + $c->flash(messages => [{type => 'error', text => 'Delete failed.'}]); + $c->log->info("Delete failed: " . $e); + }; + $c->response->redirect($c->uri_for()); +} + +sub set_create :Chained('set_list') :PathPart('create') :Args(0) { + my ($self, $c) = @_; + + my $form = NGCP::Panel::Form::RewriteRuleSet->new; + $form->process( + posted => ($c->request->method eq 'POST'), + params => $c->request->params, + action => $c->uri_for_action('/rewrite/set_create'), + item => $c->model('provisioning')->resultset('voip_rewrite_rule_sets')->new_result({}), + ); + if($form->validated) { + $c->flash(messages => [{type => 'success', text => 'Rewrite Rule Set successfully created!'}]); + $c->response->redirect($c->uri_for_action('/rewrite/set_root')); + return; + } + + $c->stash(close_target => $c->uri_for()); + $c->stash(create_flag => 1); + $c->stash(form => $form); +} + +sub rules_list :Chained('set_base') :PathPart('rules') :CaptureArgs(0) { + my ( $self, $c ) = @_; + + my $rules_rs = $c->stash->{set_result}->voip_rewrite_rules; + $c->stash(rules_rs => $rules_rs); + $c->stash(rules_uri => $c->uri_for_action("/rewrite/rules_root", [$c->req->captures->[0]])); + + $c->stash(has_edit => 1); + $c->stash(has_delete => 1); + $c->stash(template => 'rewrite/rules_list.tt'); +} + +sub rules_root :Chained('rules_list') :PathPart('') :Args(0) { + my ($self, $c) = @_; + + my $rules_rs = $c->stash->{rules_rs}; + my $param_move = $c->req->params->{move}; + my $param_where = $c->req->params->{where}; + + my $elem = $rules_rs->find($param_move) + if ($param_move && $param_move->is_integer && $param_where); + if($elem) { + my $use_next = ($param_where eq "down") ? 1 : 0; + my $swap_elem = $rules_rs->search({ + field => $elem->field, + direction => $elem->direction, + priority => { ($use_next ? '>' : '<') => $elem->priority }, + },{ + order_by => {($use_next ? '-asc' : '-desc') => 'priority'} + })->first; + if ($swap_elem) { + my $tmp_priority = $swap_elem->priority; + $swap_elem->priority($elem->priority); + $elem->priority($tmp_priority); + $swap_elem->update; + $elem->update; + } + } + + my @caller_in = $rules_rs->search({ + field => 'caller', + direction => 'in', + },{ + order_by => 'priority', + })->all; + + my @callee_in = $rules_rs->search({ + field => 'callee', + direction => 'in', + },{ + order_by => 'priority', + })->all; + + my @caller_out = $rules_rs->search({ + field => 'caller', + direction => 'out', + },{ + order_by => 'priority', + })->all; + + my @callee_out = $rules_rs->search({ + field => 'callee', + direction => 'out', + },{ + order_by => 'priority', + })->all; + + for my $row (@caller_in, @callee_in, @caller_out, @callee_out) { + my $mp = $row->match_pattern; + my $rp = $row->replace_pattern; + $mp =~ s/\$avp\(s\:(\w+)\)/\${$1}/g; + $rp =~ s/\$avp\(s\:(\w+)\)/\${$1}/g; + $row->match_pattern($mp); + $row->replace_pattern($rp); + } + + $c->stash(rules => { + caller_in => \@caller_in, + callee_in => \@callee_in, + caller_out => \@caller_out, + callee_out => \@callee_out, + }); + return; +} + +sub rules_base :Chained('rules_list') :PathPart('') :CaptureArgs(1) { + my ($self, $c, $rule_id) = @_; + + unless($rule_id && $rule_id->is_integer) { + $c->flash(messages => [{type => 'error', text => 'Invalid rewrite rule id detected!'}]); + $c->response->redirect($c->stash->{rules_uri}); + $c->detach; + return; + } + + my $res = $c->stash->{rules_rs}->find($rule_id); + unless(defined($res)) { + $c->flash(messages => [{type => 'error', text => 'Rewrite rule does not exist!'}]); + $c->response->redirect($c->stash->{rules_uri}); + $c->detach; + return; + } + $c->stash(rule_result => $res); +} + +sub rules_edit :Chained('rules_base') :PathPart('edit') { + my ($self, $c) = @_; + + my $posted = ($c->request->method eq 'POST'); + my $form = NGCP::Panel::Form::RewriteRule->new; + $form->process( + posted => $posted, + params => $c->request->params, + action => $c->uri_for_action('/rewrite/rules_edit', $c->req->captures), + item => $c->stash->{rule_result}, + ); + if($form->validated) { + $c->flash(messages => [{type => 'success', text => 'Rewrite Rule successfully changed!'}]); + $c->response->redirect($c->stash->{rules_uri}); + return; + } + + $c->stash(form => $form); + $c->stash(edit_flag => 1); +} + +sub rules_delete :Chained('rules_base') :PathPart('delete') { + my ($self, $c) = @_; + + try { + $c->stash->{rule_result}->delete; + $c->flash(messages => [{type => 'success', text => 'Rewrite Rule successfully deleted!'}]); + } catch (DBIx::Class::Exception $e) { + $c->flash(messages => [{type => 'error', text => 'Delete failed.'}]); + $c->log->info("Delete failed: " . $e); + }; + $c->response->redirect($c->stash->{rules_uri}); +} + +sub rules_create :Chained('rules_list') :PathPart('create') :Args(0) { + my ($self, $c) = @_; + + my $form = NGCP::Panel::Form::RewriteRule->new; + $form->process( + posted => ($c->request->method eq 'POST'), + params => $c->request->params, + action => $c->uri_for_action('/rewrite/rules_create', $c->req->captures), + item => $c->stash->{rules_rs}->new_result({}), + ); + if($form->validated) { + $c->flash(messages => [{type => 'success', text => 'Rewrite Rule successfully created!'}]); + $c->response->redirect($c->stash->{rules_uri}); + return; + } + + $c->stash(create_flag => 1); + $c->stash(form => $form); +} + +__PACKAGE__->meta->make_immutable; + +1; + +=head1 NAME + +NGCP::Panel::Controller::Rewrite - Manage Rewrite Rules + +=head1 DESCRIPTION + +Show/Edit/Create/Delete Rewrite Rule Sets. + +Show/Edit/Create/Delete Rewrite Rules within Rewrite Rule Sets. + +=head1 METHODS + +=head2 set_list + +Basis for provisioning.voip_rewrite_rule_sets. + +=head2 set_root + +Display rewrite rule sets through F template. + +=head2 set_ajax + +Get provisioning.voip_rewrite_rule_sets from the database and +output them as JSON. +The format is meant for parsing with datatables. + +=head2 set_base + +Fetch a provisioning.voip_rewrite_rule_set from the database by its id. + +=head2 set_edit + +Show a modal to edit a rewrite rule set determined by L. +The form used is L. + +=head2 set_delete + +Delete a rewrite rule set determined by L. + +=head2 set_create + +Show a modal to create a new rewrite rule set using the form +L. + +=head2 rules_list + +Basis for provisioning.voip_rewrite_rules. Chained from L and +therefore handles only rules for a certain rewrite rule set. + +=head2 rules_root + +Display rewrite rule sets through F template. +The rules are stashed to rules hashref which contains the keys +"caller_in", "callee_in", "caller_out", "callee_out". + +It swaps priority of two elements if "move" and "where" GET params are set. + +It modifies match_pattern and replace_pattern field to a certain output format +using regex. + +=head2 rules_base + +Fetch a rewrite rule from the database by its id. Will only find rules under +the current rule_set. + +=head2 rules_edit + +Show a modal to edit a rewrite rule determined by L. +The form used is L. + +=head2 rules_delete + +Delete a rewrite rule determined by L. + +=head2 rules_create + +Show a modal to create a new rewrite rule using the form +L. + +=head1 AUTHOR + +Gerhard Jungwirth C<< >> + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/RewriteRule.pm b/lib/NGCP/Panel/Form/RewriteRule.pm new file mode 100644 index 0000000000..d2a3573703 --- /dev/null +++ b/lib/NGCP/Panel/Form/RewriteRule.pm @@ -0,0 +1,126 @@ +package NGCP::Panel::Form::RewriteRule; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler::Model::DBIC'; +use Moose::Util::TypeConstraints; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +sub build_render_list {[qw/fields actions/]} +sub build_form_element_class { [qw/form-horizontal/] } + +has_field 'match_pattern' => ( + type => '+NGCP::Panel::Field::Regexp', + required => 1, + inflate_default_method => \&inflate_pattern, +); + +has_field 'replace_pattern' => ( + type => 'Text', + required => 1, + label => 'Replacement Pattern', + inflate_default_method => \&inflate_pattern, +); + +has_field 'description' => ( + type => 'Text', + required => 0, +); + +has_field 'direction' => ( + type => 'Select', + options => [ + { label => 'Inbound', value => 'in'}, + { label => 'Outbound', value => 'out'}, + ], +); + +has_field 'field' => ( + type => 'Select', + options => [ + { label => 'Callee', value => 'callee'}, + { label => 'Caller', value => 'caller'}, + ], +); + +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/match_pattern replace_pattern description direction field/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +before 'update_model' => sub { + my $self = shift; + $self->value->{match_pattern} =~ s/\$\{(\w+)\}/\$avp(s:$1)/g; + $self->value->{replace_pattern} =~ s/\$\{(\w+)\}/\$avp(s:$1)/g; +}; + +sub inflate_pattern { + my ($self, $value) = @_; + + $value =~ s/\$avp\(s\:(\w+)\)/\${$1}/g; + return $value; +} + +sub validate { + my $self = shift; + my $s = $self->field('match_pattern')->value // ""; + my $r = $self->field('replace_pattern')->value // ""; + my $_ = ""; + my $re = "s/$s/$r/"; + eval { use warnings FATAL => qw(all); m/$re/; }; + + if( $@ && $self->field('match_pattern')->num_errors < 1 ) { + my $err_msg = 'Match pattern and Replace Pattern do not work together.'; + $self->field('match_pattern')->add_error($err_msg); + $self->field('replace_pattern')->add_error($err_msg); + } +} + +1; + +=head1 NAME + +NGCP::Panel::Form::RewriteRule + +=head1 DESCRIPTION + +Form to modify a provisioning.rewrite_rules row. + +=head1 METHODS + +=head2 inflate_pattern + +Inflates match_pattern and replace_pattern from the database by using a +regex before their display. + +=head2 validate + +Do some special validation for match_pattern and replace_pattern together. + +=head1 AUTHOR + +Gerhard Jungwirth + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/RewriteRuleSet.pm b/lib/NGCP/Panel/Form/RewriteRuleSet.pm new file mode 100644 index 0000000000..38e34e7f27 --- /dev/null +++ b/lib/NGCP/Panel/Form/RewriteRuleSet.pm @@ -0,0 +1,65 @@ +package NGCP::Panel::Form::RewriteRuleSet; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler::Model::DBIC'; +use Moose::Util::TypeConstraints; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +sub build_render_list {[qw/fields actions/]} +sub build_form_element_class { [qw/form-horizontal/] } + +has_field 'name' => ( + type => 'Text', + required => 1, +); + +has_field 'description' => ( + type => 'Text', + required => 0, +); + +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/name description/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +1; + +=head1 NAME + +NGCP::Panel::Form::RewriteRuleSet + +=head1 DESCRIPTION + +Form to modify a provisioning.rewrite_rule_sets row. + +=head1 METHODS + +=head1 AUTHOR + +Gerhard Jungwirth + +=head1 LICENSE + +This library is free software. You can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut + +# vim: set tabstop=4 expandtab: diff --git a/share/templates/rewrite/rules_list.tt b/share/templates/rewrite/rules_list.tt new file mode 100644 index 0000000000..14adff0aa2 --- /dev/null +++ b/share/templates/rewrite/rules_list.tt @@ -0,0 +1,92 @@ +[% site_config.title = 'Rewrite Rules for ' _ set_result.name -%] + +< Back + + + +[% IF messages -%] +
+ [% FOREACH m IN messages -%] +
[% m.text %]
+ [% END -%] +
+[% END -%] + +
+ +[% MACRO show_rules BLOCK -%] + + + + + + + + + + + + [% FOR r IN m_rules %] + + + + + + + + [% END %] + +
Match PatternReplacement PatternDescription
+ + + + + + + [% r.match_pattern %][% r.replace_pattern %][% r.description %] + +
+[% END -%] + +

Inbound Rewrite Rules for Caller

+ +[% show_rules(m_rules = rules.caller_in) %] + +

Inbound Rewrite Rules for Callee

+ +[% show_rules(m_rules = rules.callee_in) %] + +

Outbound Rewrite Rules for Caller

+ +[% show_rules(m_rules = rules.caller_out) %] + +

Outbound Rewrite Rules for Callee

+ +[% show_rules(m_rules = rules.callee_out) %] + +[% IF edit_flag || create_flag -%] +[% + PROCESS "helpers/modal.tt"; + modal_header(m.name = 'Rule', + m.create_flag = create_flag); +-%] + +[% form.render() %] + +[% + modal_footer(); + modal_script(m.close_target = rules_uri); +-%] +[% END -%] + + +[% # vim: set tabstop=4 syntax=html expandtab: -%] diff --git a/share/templates/rewrite/set_list.tt b/share/templates/rewrite/set_list.tt new file mode 100644 index 0000000000..a84de118fa --- /dev/null +++ b/share/templates/rewrite/set_list.tt @@ -0,0 +1,23 @@ +[% META title = 'SIP Peering Groups' -%] +[% + helper.name = 'Rewrite Rule Sets'; + helper.messages = messages; + helper.column_titles = [ '#', 'Name', 'Description' ]; + helper.column_fields = [ 'id', 'name', 'description' ]; + + helper.close_target = close_target; + helper.create_flag = create_flag; + helper.edit_flag = edit_flag; + helper.form_object = form; + helper.has_edit = has_edit; + helper.has_delete = has_delete; + helper.ajax_uri = c.uri_for_action( "/rewrite/set_ajax" ); + helper.base_uri = c.uri_for_action( "/rewrite/set_root" ); + + helper.extra_buttons = [ + [ 'Rules', 'rules'], + ]; + + PROCESS 'helpers/datatables.tt'; +-%] +[% # vim: set tabstop=4 syntax=html expandtab: -%] diff --git a/share/templates/widgets/admin_topmenu_settings.tt b/share/templates/widgets/admin_topmenu_settings.tt index d810f15633..2025624ebb 100644 --- a/share/templates/widgets/admin_topmenu_settings.tt +++ b/share/templates/widgets/admin_topmenu_settings.tt @@ -10,6 +10,7 @@
  • Domains
  • Billing
  • Peerings
  • +
  • Rewrite Rule Sets
  • [% # vim: set tabstop=4 syntax=html expandtab: -%] diff --git a/t/controller_Rewrite.t b/t/controller_Rewrite.t new file mode 100644 index 0000000000..a25ef0fa84 --- /dev/null +++ b/t/controller_Rewrite.t @@ -0,0 +1,10 @@ +use strict; +use warnings; +use Test::More; + + +use Catalyst::Test 'NGCP::Panel'; +use NGCP::Panel::Controller::Rewrite; + +ok( request('/rewrite')->is_success || request('/rewrite')->is_redirect, 'Request should succeed' ); +done_testing();