diff --git a/lib/NGCP/Panel/Controller/API/SubscriberLocationMappings.pm b/lib/NGCP/Panel/Controller/API/SubscriberLocationMappings.pm new file mode 100644 index 0000000000..34cb2865e2 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/SubscriberLocationMappings.pm @@ -0,0 +1,162 @@ +package NGCP::Panel::Controller::API::SubscriberLocationMappings; +use NGCP::Panel::Utils::Generic qw(:all); + +use Sipwise::Base; + +use boolean qw(true); +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); + +sub allowed_methods{ + return [qw/GET POST OPTIONS HEAD/]; +} + +sub api_description { + return 'Defines location mappings for subscribers to terminate calls on other registrations.'; +}; + +sub query_params { + return [ + { + param => 'subscriber_id', + description => 'Filter for location mappings of a specific subscriber', + query => { + first => sub { + my $q = shift; + return { 'voip_subscriber.id' => $q }; + }, + second => sub { + return { + join => { subscriber => 'voip_subscriber' } + }; + }, + }, + }, + ]; +} + +use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::SubscriberLocationMappings/; + +sub resource_name { + return 'subscriberlocationmappings'; +} + +sub dispatch_path { + return '/api/subscriberlocationmappings/'; +} + +sub relation{ + return 'http://purl.org/sipwise/ngcp-api/#rel-subscriberlocationmappings'; +} + +__PACKAGE__->set_config({ + allowed_roles => [qw/admin reseller ccareadmin ccare/], +}); + +sub GET :Allow { + my ($self, $c) = @_; + my $page = $c->request->params->{page} // 1; + my $rows = $c->request->params->{rows} // 10; + { + my $items = $self->item_rs($c); + (my $total_count, $items, my $items_rows) = $self->paginate_order_collection($c, $items); + my (@embedded, @links); + my $form = $self->get_form($c); + for my $item (@$items_rows) { + push @embedded, $self->hal_from_item($c, $item, $form); + push @links, Data::HAL::Link->new( + relation => 'ngcp:'.$self->resource_name, + href => sprintf('/%s%d', $c->request->path, $item->id), + ); + } + push @links, + Data::HAL::Link->new( + relation => 'curies', + href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}', + name => 'ngcp', + templated => true, + ), + Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), + $self->collection_nav_links($c, $page, $rows, $total_count, $c->request->path, $c->request->query_params); + + my $hal = Data::HAL->new( + embedded => [@embedded], + links => [@links], + ); + $hal->resource({ + total_count => $total_count, + }); + my $response = HTTP::Response->new(HTTP_OK, undef, + HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json); + $c->response->headers($response->headers); + $c->response->body($response->content); + return; + } + return; +} + +sub POST :Allow { + my ($self, $c) = @_; + + my $guard = $c->model('DB')->txn_scope_guard; + { + my $resource = $self->get_valid_post_data( + c => $c, + media_type => 'application/json', + ); + last unless $resource; + + my $form = $self->get_form($c); + last unless $self->validate_form( + c => $c, + resource => $resource, + form => $form, + ); + + my $sub_rs = $c->model('DB')->resultset('voip_subscribers')->search({ + 'me.id' => $resource->{subscriber_id} + }); + if($c->user->roles eq "reseller") { + $sub_rs = $sub_rs->search({ + 'contact.reseller_id' => $c->user->reseller_id, + },{ + join => { contract => 'contact' }, + }); + } + my $sub = $sub_rs->first; + unless($sub && $sub->provisioning_voip_subscriber) { + $c->log->error("invalid subscriber_id '$$resource{subscriber_id}'"); # TODO: user, message, trace, ... + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Subscriber does not exist"); + last; + } + + $resource->{subscriber_id} = $sub->provisioning_voip_subscriber->id; + my $item; + try { + $item = $c->model('DB')->resultset('voip_subscriber_location_mappings')->create($resource); + } catch($e) { + $c->log->error("failed to create location mapping: " . $c->qs($e)); # TODO: user, message, trace, ... + $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create location mapping."); + last; + } + + last unless $self->add_create_journal_item_hal($c,sub { + my $self = shift; + my ($c) = @_; + my $_item = $self->item_by_id($c, $item->id); + return $self->hal_from_item($c, $_item, $form); }); + + $guard->commit; + + $c->response->status(HTTP_CREATED); + $c->response->header(Location => sprintf('/%s%d', $c->request->path, $item->id)); + $c->response->body(q()); + } + return; +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/SubscriberLocationMappingsItem.pm b/lib/NGCP/Panel/Controller/API/SubscriberLocationMappingsItem.pm new file mode 100644 index 0000000000..98e27ad262 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/SubscriberLocationMappingsItem.pm @@ -0,0 +1,185 @@ +package NGCP::Panel::Controller::API::SubscriberLocationMappingsItem; +use NGCP::Panel::Utils::Generic qw(:all); + +use Sipwise::Base; + +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); + +use NGCP::Panel::Utils::ValidateJSON qw(); +require Catalyst::ActionRole::ACL; +require NGCP::Panel::Role::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +sub allowed_methods{ + return [qw/GET OPTIONS HEAD PATCH PUT DELETE/]; +} + +use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::SubscriberLocationMappings/; + +sub resource_name { + return 'subscriberlocationmappings'; +} + +sub dispatch_path { + return '/api/subscriberlocationmappings/'; +} + +sub relation { + return 'http://purl.org/sipwise/ngcp-api/#rel-subscriberlocationmappings'; +} + +sub journal_query_params { + my($self,$query_params) = @_; + return $self->get_journal_query_params($query_params); +} + +__PACKAGE__->set_config({ + allowed_roles => { + Default => [qw/admin reseller ccareadmin ccare/], + Journal => [qw/admin reseller ccareadmin ccare/], + } +}); + +sub GET :Allow { + my ($self, $c, $id) = @_; + { + last unless $self->valid_id($c, $id); + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, subscriberlocationmapping => $item); + + my $hal = $self->hal_from_item($c, $item); + + my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( + (map { # XXX Data::HAL must be able to generate links with multiple relations + s|rel="(http://purl.org/sipwise/ngcp-api/#rel-\w+)"|rel="item $1"|r =~ + s/rel=self/rel="item self"/r; + } $hal->http_headers), + ), $hal->as_json); + $c->response->headers($response->headers); + $c->response->body($response->content); + return; + } + return; +} + +sub PATCH :Allow { + my ($self, $c, $id) = @_; + my $guard = $c->model('DB')->txn_scope_guard; + { + my $preference = $self->require_preference($c); + last unless $preference; + + my $json = $self->get_valid_patch_data( + c => $c, + id => $id, + media_type => 'application/json-patch+json', + ); + last unless $json; + + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, subscriberlocationmapping => $item); + my $form = $self->get_form($c); + my $old_resource = $self->resource_from_item($c, $item, $form); + my $resource = $self->apply_patch($c, $old_resource, $json); + last unless $resource; + + $item = $self->update_item($c, $item, $old_resource, $resource, $form); + last unless $item; + + my $hal = $self->hal_from_item($c, $item, $form); + last unless $self->add_update_journal_item_hal($c,$hal); + + $guard->commit; + + if ('minimal' eq $preference) { + $c->response->status(HTTP_NO_CONTENT); + $c->response->header(Preference_Applied => 'return=minimal'); + $c->response->body(q()); + } else { + #my $hal = $self->hal_from_item($c, $item, $form); + my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( + $hal->http_headers, + ), $hal->as_json); + $c->response->headers($response->headers); + $c->response->header(Preference_Applied => 'return=representation'); + $c->response->body($response->content); + } + } + return; +} + +sub PUT :Allow { + my ($self, $c, $id) = @_; + my $guard = $c->model('DB')->txn_scope_guard; + { + my $preference = $self->require_preference($c); + last unless $preference; + + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, subscriberlocationmapping => $item); + my $resource = $self->get_valid_put_data( + c => $c, + id => $id, + media_type => 'application/json', + ); + last unless $resource; + my $form = $self->get_form($c); + my $old_resource = $self->resource_from_item($c, $item, $form); + + $item = $self->update_item($c, $item, $old_resource, $resource, $form); + last unless $item; + + my $hal = $self->hal_from_item($c, $item, $form); + last unless $self->add_update_journal_item_hal($c,$hal); + + $guard->commit; + + if ('minimal' eq $preference) { + $c->response->status(HTTP_NO_CONTENT); + $c->response->header(Preference_Applied => 'return=minimal'); + $c->response->body(q()); + } else { + #my $hal = $self->hal_from_item($c, $item, $form); + my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( + $hal->http_headers, + ), $hal->as_json); + $c->response->headers($response->headers); + $c->response->header(Preference_Applied => 'return=representation'); + $c->response->body($response->content); + } + } + return; +} + +sub DELETE :Allow { + my ($self, $c, $id) = @_; + + my $guard = $c->model('DB')->txn_scope_guard; + { + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, subscriberlocationmapping => $item); + + last unless $self->add_delete_journal_item_hal($c,sub { + my $self = shift; + my ($c) = @_; + my $_form = $self->get_form($c); + return $self->hal_from_item($c, $item, $_form); }); + + $item->delete; + + $guard->commit; + + $c->response->status(HTTP_NO_CONTENT); + $c->response->body(q()); + } + return; +} + +sub get_journal_methods{ + return [qw/handle_item_base_journal handle_journals_get handle_journalsitem_get handle_journals_options handle_journalsitem_options handle_journals_head handle_journalsitem_head/]; +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/Subscriber.pm b/lib/NGCP/Panel/Controller/Subscriber.pm index 0da5079dbd..3e36231edd 100644 --- a/lib/NGCP/Panel/Controller/Subscriber.pm +++ b/lib/NGCP/Panel/Controller/Subscriber.pm @@ -5693,6 +5693,170 @@ sub header_actions_create :Chained('header_actions_list') :PathPart('create') :A ); } +sub create_location_map :Chained('base') :PathPart('preferences/locationmap/create') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(ccareadmin) :AllowedRole(ccare) { + my ($self, $c) = @_; + + my $posted = ($c->request->method eq 'POST'); + my $location_map_rs = $c->stash->{subscriber}->provisioning_voip_subscriber->location_mappings; + my $params = {}; + + my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Subscriber::LocationMapping", $c); + $form->process( + posted => $posted, + params => $posted ? $c->req->params : {} + ); + NGCP::Panel::Utils::Navigation::check_form_buttons( + c => $c, + form => $form, + fields => {}, + back_uri => $c->req->uri, + ); + + if($posted && $form->validated) { + try { + $location_map_rs->create({ + subscriber_id => $c->stash->{subscriber}->provisioning_voip_subscriber->id, + location => $form->field('location')->value, + caller_pattern => $form->field('caller_pattern')->value // '.+', + callee_pattern => $form->field('callee_pattern')->value // '.+', + mode => $form->field('mode')->value, + to_username => $form->field('to_username')->value, + external_id => $form->field('external_id')->value, + enabled => $form->field('enabled')->value, + }); + NGCP::Panel::Utils::Message::info( + c => $c, + desc => $c->loc('Successfully created location mapping'), + ); + } catch($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to create location mapping'), + ); + } + NGCP::Panel::Utils::Navigation::back_or($c, + $c->uri_for_action('/subscriber/preferences', [$c->req->captures->[0]])); + } + + $c->stash( + template => 'subscriber/preferences.tt', + edit_cf_flag => 1, + cf_description => $c->loc('Location Mapping'), + cf_form => $form, + ); +} + +sub location_map_base :Chained('base') :PathPart('preferences/locationmap') :CaptureArgs(1) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(ccareadmin) :AllowedRole(ccare) { + my ($self, $c, $map_id) = @_; + + $c->stash->{location_map} = $c->stash->{subscriber}->provisioning_voip_subscriber->location_mappings->find($map_id); + + unless($c->stash->{location_map}) { + NGCP::Panel::Utils::Message::error( + c => $c, + log => "location mapping id '$map_id' is not found for subscriber uuid ".$c->qs($c->stash->{subscriber}->uuid), + desc => $c->loc('Trusted source entry not found'), + ); + NGCP::Panel::Utils::Navigation::back_or($c, + $c->uri_for_action('/subscriber/preferences', [$c->req->captures->[0]])); + } +} + +sub edit_location_map :Chained('location_map_base') :PathPart('edit') { + my ($self, $c) = @_; + + $c->detach('/denied_page') + if(($c->user->roles eq "admin" || $c->user->roles eq "reseller" || + $c->user->roles eq "ccareadmin" || $c->user->roles eq "ccare") && $c->user->read_only); + + my $posted = ($c->request->method eq 'POST'); + my $location_map = $c->stash->{location_map}; + my $params = {}; + + if(!$posted && $location_map) { + $params = { + 'location' => $location_map->location, + 'caller_pattern' => $location_map->caller_pattern, + 'callee_pattern' => $location_map->callee_pattern, + 'mode' => $location_map->mode, + 'to_username' => $location_map->to_username, + 'external_id' => $location_map->external_id, + 'enabled' => $location_map->enabled, + }; + } + + my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Subscriber::LocationMapping", $c); + $form->process( + params => $posted ? $c->req->params : $params + ); + NGCP::Panel::Utils::Navigation::check_form_buttons( + c => $c, + form => $form, + fields => {}, + back_uri => $c->req->uri, + ); + + if($posted && $form->validated) { + try { + $location_map->update({ + location => $form->field('location')->value, + caller_pattern => $form->field('caller_pattern')->value // '.+', + callee_pattern => $form->field('callee_pattern')->value // '.+', + mode => $form->field('mode')->value, + to_username => $form->field('to_username')->value, + external_id => $form->field('external_id')->value, + enabled => $form->field('enabled')->value, + }); + NGCP::Panel::Utils::Message::info( + c => $c, + desc => $c->loc('Successfully updated location mapping'), + ); + } catch($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to update location mapping'), + ); + } + NGCP::Panel::Utils::Navigation::back_or($c, + $c->uri_for_action('/subscriber/preferences', [$c->req->captures->[0]])); + } + + $c->stash( + template => 'subscriber/preferences.tt', + edit_cf_flag => 1, + cf_description => $c->loc('Location Mapping'), + cf_form => $form, + ); +} + +sub delete_location_map :Chained('location_map_base') :PathPart('delete') :Args(0) { + my ($self, $c) = @_; + + $c->detach('/denied_page') + if(($c->user->roles eq "admin" || $c->user->roles eq "reseller" || + $c->user->roles eq "ccareadmin" || $c->user->roles eq "ccare") && $c->user->read_only); + + try { + $c->stash->{location_map}->delete; + NGCP::Panel::Utils::Message::info( + c => $c, + data => { $c->stash->{location_map}->get_inflated_columns }, + desc => $c->loc('Successfully deleted location mapping'), + ); + } catch($e) { + NGCP::Panel::Utils::Message::error( + c => $c, + error => $e, + desc => $c->loc('Failed to delete location mapping.'), + ); + } + + NGCP::Panel::Utils::Navigation::back_or($c, + $c->uri_for_action('/subscriber/preferences', [$c->req->captures->[0]])); +} + =head1 AUTHOR Andreas Granig,,, diff --git a/lib/NGCP/Panel/Form/Subscriber/LocationMapping.pm b/lib/NGCP/Panel/Form/Subscriber/LocationMapping.pm new file mode 100644 index 0000000000..19404f7b88 --- /dev/null +++ b/lib/NGCP/Panel/Form/Subscriber/LocationMapping.pm @@ -0,0 +1,114 @@ +package NGCP::Panel::Form::Subscriber::LocationMapping; + +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 'subscriber_id' => ( + type => 'Hidden', + required => 0, +); + +has_field 'location' => ( + type => 'Text', + label => 'Location URI', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Location entry SIP-URI.'] + }, +); + +has_field 'caller_pattern' => ( + type => 'Text', + label => 'Caller Pattern', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Caller Pattern'] + }, +); + +has_field 'callee_pattern' => ( + type => 'Text', + label => 'Callee Pattern', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Callee Pattern'] + }, +); + +has_field 'mode' => ( + type => 'Select', + label => 'Mode', + required => 1, + options => [ + { label => 'Add', value => 'add' }, + { label => 'Replace', value => 'replace' }, + { label => 'Offline', value => 'offline' }, + ], + element_attr => { + rel => ['tooltip'], + title => ['The location lookup mode'] + }, +); + +has_field 'to_username' => ( + type => 'Text', + label => 'To username', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Replace To username with the value'] + }, +); + +has_field 'external_id' => ( + type => 'Text', + label => 'External Id', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['External Id value'] + }, +); + +has_field 'enabled' => ( + type => 'Boolean', + label => 'Enabled', + default => 1, + element_attr => { + rel => ['tooltip'], + title => ['Enables the entry'] + }, +); + +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/subscriber_id location caller_pattern callee_pattern mode to_username external_id enabled/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Subscriber/LocationMappingAPI.pm b/lib/NGCP/Panel/Form/Subscriber/LocationMappingAPI.pm new file mode 100644 index 0000000000..2414d020fa --- /dev/null +++ b/lib/NGCP/Panel/Form/Subscriber/LocationMappingAPI.pm @@ -0,0 +1,22 @@ +package NGCP::Panel::Form::Subscriber::LocationMappingAPI; + +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::Subscriber::LocationMapping'; + +has_field 'subscriber_id' => ( + type => 'PosInteger', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber this location mapping belongs to.'] + }, +); + +has_block 'fields' => ( + tag => 'div', + class => [qw/modal-body/], + render_list => [qw/subscriber_id location caller_pattern callee_pattern mode to_username external_id enabled/], +); + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/SubscriberLocationMappings.pm b/lib/NGCP/Panel/Role/API/SubscriberLocationMappings.pm new file mode 100644 index 0000000000..4f8c3d280c --- /dev/null +++ b/lib/NGCP/Panel/Role/API/SubscriberLocationMappings.pm @@ -0,0 +1,121 @@ +package NGCP::Panel::Role::API::SubscriberLocationMappings; +use NGCP::Panel::Utils::Generic qw(:all); + +use Sipwise::Base; + +use parent 'NGCP::Panel::Role::API'; + +use boolean qw(true); +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Status qw(:constants); + +sub _item_rs { + my ($self, $c) = @_; + + my $item_rs = $c->model('DB')->resultset('voip_subscriber_location_mappings'); + if ($c->user->roles eq "admin" || $c->user->roles eq "ccareadmin") { + } elsif ($c->user->roles eq "reseller" || $c->user->roles eq "ccare") { + $item_rs = $item_rs->search({ + 'contact.reseller_id' => $c->user->reseller_id + },{ + join => { subscriber => { voip_subscriber => { contract => 'contact' } } }, + }); + } + return $item_rs; +} + +sub get_form { + my ($self, $c) = @_; + return NGCP::Panel::Form::get("NGCP::Panel::Form::Subscriber::LocationMappingAPI", $c); +} + +sub hal_from_item { + my ($self, $c, $item, $form) = @_; + + $form //= $self->get_form($c); + my $resource = $self->resource_from_item($c, $item, $form); + + my $hal = Data::HAL->new( + links => [ + Data::HAL::Link->new( + relation => 'curies', + href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}', + name => 'ngcp', + templated => true, + ), + Data::HAL::Link->new(relation => 'collection', href => sprintf("/api/%s/", $self->resource_name)), + Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), + Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), + Data::HAL::Link->new(relation => 'ngcp:subscribers', href => sprintf("/api/subscribers/%d", $item->subscriber->voip_subscriber->id)), + $self->get_journal_relation_link($c, $item->id), + ], + relation => 'ngcp:'.$self->resource_name, + ); + + + $self->validate_form( + c => $c, + resource => $resource, + form => $form, + run => 0, + ); + + $resource->{id} = int($item->id); + $hal->resource($resource); + return $hal; +} + +sub resource_from_item { + my ($self, $c, $item, $form) = @_; + + my $resource = { $item->get_inflated_columns }; + $resource->{subscriber_id} = int($item->subscriber->voip_subscriber->id); + + return $resource; +} + +sub item_by_id { + my ($self, $c, $id) = @_; + my $item_rs = $self->item_rs($c); + return $item_rs->find($id); +} + +sub update_item { + my ($self, $c, $item, $old_resource, $resource, $form) = @_; + + $form //= $self->get_form($c); + return unless $self->validate_form( + c => $c, + form => $form, + resource => $resource, + ); + + my $sub_rs = $c->model('DB')->resultset('voip_subscribers')->search({ + 'me.id' => $resource->{subscriber_id}, + }); + if($c->user->roles eq "reseller" || $c->user->roles eq "ccare") { + $sub_rs = $sub_rs->search({ + 'contact.reseller_id' => $c->user->reseller_id, + },{ + join => { contract => 'contact' }, + }); + } + my $sub = $sub_rs->first; + unless($sub && $sub->provisioning_voip_subscriber) { + $c->log->error("invalid subscriber_id '$$resource{subscriber_id}'"); # TODO: user, message, trace, ... + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Subscriber does not exist"); + return; + } + $resource->{subscriber_id} = $sub->provisioning_voip_subscriber->id; + + $resource->{caller_pattern} //= '.+'; + $resource->{callee_pattern} //= '.+'; + + $item->update($resource); + + return $item; +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/share/templates/subscriber/preferences.tt b/share/templates/subscriber/preferences.tt index 89d50b1a49..f138842aa8 100644 --- a/share/templates/subscriber/preferences.tt +++ b/share/templates/subscriber/preferences.tt @@ -993,6 +993,79 @@ $( document ).ready(function() { [% END -%] +[% UNLESS c.user.roles == 'subscriber' || c.user.roles == 'subscriberadmin' -%] +
| [% c.loc('Location SIP-URI') %] | +[% c.loc('Caller Pattern') %] | +[% c.loc('Callee Pattern') %] | +[% c.loc('Mode') %] | +[% c.loc('To Username') %] | +[% c.loc('External ID') %] | +[% c.loc('Enabled') %] | + [% # one for actions -%] ++ |
|---|---|---|---|---|---|---|---|
| + [% location_map.location %] + | ++ [% location_map.caller_pattern %] + | ++ [% location_map.callee_pattern %] + | ++ [% location_map.mode %] + | ++ [% location_map.to_username %] + | ++ [% location_map.external_id %] + | ++ [% location_map.enabled %] + | +
+
+ [% IF (c.user.roles == "admin" || c.user.roles == "reseller" ||
+ c.user.roles == "ccareadmin" || c.user.roles == "ccare") && c.user.read_only != 1 -%]
+ [% c.loc('Edit') %]
+ [% c.loc('Delete') %]
+ [% END -%]
+
+ |
+