MT#21543 Implement emergency mapping

Implemented web UI.
Implemented api.

Change-Id: Ieec8a8a60ace4b6c5fe0842532b5ae58964cbe9f
changes/09/7309/3
Andreas Granig 10 years ago
parent 9264a41ac5
commit a6264ed34c

@ -0,0 +1,218 @@
package NGCP::Panel::Controller::API::EmergencyMappingContainers;
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);
use NGCP::Panel::Utils::Reseller qw();
use Path::Tiny qw(path);
require Catalyst::ActionRole::ACL;
require Catalyst::ActionRole::CheckTrailingSlash;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET POST OPTIONS HEAD/];
}
sub api_description {
return 'Defines a container which holds a collection of <a href="#emergencymappings">Emergency Mappings</a>.';
};
sub query_params {
return [
{
param => 'name',
description => 'Filter for emergency mapping containers with a specific name (wildcards possible)',
query => {
first => sub {
my $q = shift;
{ name => { like => $q } };
},
second => sub {},
},
},
{
param => 'reseller_id',
description => 'Filter for emergency mapping containers for a specific reseller',
query => {
first => sub {
my $q = shift;
{ reseller_id => $q };
},
second => sub {},
},
},
];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::EmergencyMappingContainers/;
sub resource_name{
return 'emergencymappingcontainers';
}
sub dispatch_path{
return '/api/emergencymappingcontainers/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-emergencymappingcontainers';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods }
},
action_roles => [qw(+NGCP::Panel::Role::HTTPMethods)],
);
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
}
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) = $self->paginate_order_collection($c, $items);
my (@embedded, @links);
my $form = $self->get_form($c);
for my $item($items->all) {
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/'),
Data::HAL::Link->new(relation => 'self', href => sprintf('/%s?page=%s&rows=%s', $c->request->path, $page, $rows));
if(($total_count / $rows) > $page ) {
push @links, Data::HAL::Link->new(relation => 'next', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page + 1, $rows));
}
if($page > 1) {
push @links, Data::HAL::Link->new(relation => 'prev', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page - 1, $rows));
}
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 HEAD :Allow {
my ($self, $c) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Post => 'application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-'.$self->resource_name,
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
}
sub POST :Allow {
my ($self, $c) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $schema = $c->model('DB');
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,
);
if($c->user->roles eq "reseller") {
$resource->{reseller_id} = $c->user->reseller_id;
}
my $dup_item = $c->model('DB')->resultset('emergency_containers')->find({
reseller_id => $resource->{reseller_id},
name => $resource->{name},
});
if($dup_item) {
$c->log->error("emergency mapping container with name '$$resource{name}' already exists for this reseller");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "emergency mapping container with this name already exists for this reseller");
return;
}
my $item;
try {
$item = $schema->resultset('emergency_containers')->create($resource);
} catch($e) {
$c->log->error("failed to create emergency mapping container: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create emergency mapping container.");
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', $self->dispatch_path, $item->id));
$c->response->body(q());
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,210 @@
package NGCP::Panel::Controller::API::EmergencyMappingContainersItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::ValidateJSON qw();
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
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/Catalyst::Controller NGCP::Panel::Role::API::EmergencyMappingContainers/;
sub resource_name{
return 'emergencymappingcontainers';
}
sub dispatch_path{
return '/api/emergencymappingcontainers/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-emergencymappingcontainers';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods }
},
action_roles => [qw(+NGCP::Panel::Role::HTTPMethods)],
);
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
}
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, emergencymappingcontainer => $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-resellers)"|rel="item $1"|;
s/rel=self/rel="item self"/;
$_
} $hal->http_headers),
), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
return;
}
return;
}
sub HEAD :Allow {
my ($self, $c, $id) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c, $id) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Patch => 'application/json-patch+json',
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
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, emergencymappingcontainer => $item);
my $old_resource = { $item->get_inflated_columns };
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
my $form = $self->get_form($c);
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
$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, emergencymappingcontainer => $item);
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,
media_type => 'application/json',
);
last unless $resource;
my $old_resource = { $item->get_inflated_columns };
my $form = $self->get_form($c);
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
$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, emergencymappingcontainer => $item);
my $mapping_count = $item->emergency_mappings->count;
if ($mapping_count > 0) {
$c->log->error("failed to delete emergency mapping container, there are still $mapping_count emergency mappings linked to it.");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to delete emergency mapping container.");
last;
}
$item->delete;
$guard->commit;
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,268 @@
package NGCP::Panel::Controller::API::EmergencyMappings;
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);
use NGCP::Panel::Utils::DateTime;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
use NGCP::Panel::Utils::EmergencyMapping;
use NGCP::Panel::Utils::MySQL;
require Catalyst::ActionRole::ACL;
require Catalyst::ActionRole::CheckTrailingSlash;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET POST OPTIONS HEAD/];
}
sub api_description {
return 'Defines emergency mappings for an <a href="#emergencymappingscontainer">Emergency Mapping Container</a>. You can POST mappings individually one-by-one using json. To bulk-upload mappings, specify the Content-Type as "text/csv" and POST the CSV in the request body to the collection with an optional parameter "purge_existing=true", like "/api/emergencymappings/?purge_existing=true"';
};
sub query_params {
return [
{
param => 'emergency_container_id',
description => 'Filter for emergency mappings belonging to a specific emergency mapping container',
query => {
first => sub {
my $q = shift;
{ emergency_container_id => $q };
},
second => sub {},
},
},
{
param => 'code',
description => 'Filter for mappings with a specific code (wildcards possible)',
query => {
first => sub {
my $q = shift;
{ code => { like => $q } };
},
second => sub {},
},
},
];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::EmergencyMappings/;
sub resource_name{
return 'emergencymappings';
}
sub dispatch_path{
return '/api/emergencymappings/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-emergencymappings';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods }
},
action_roles => [qw(+NGCP::Panel::Role::HTTPMethods)],
);
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
}
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) = $self->paginate_order_collection($c, $items);
my (@embedded, @links);
my $form = $self->get_form($c);
for my $item ($items->all) {
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/'),
Data::HAL::Link->new(relation => 'self', href => sprintf('/%s?page=%s&rows=%s', $c->request->path, $page, $rows));
if(($total_count / $rows) > $page ) {
push @links, Data::HAL::Link->new(relation => 'next', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page + 1, $rows));
}
if($page > 1) {
push @links, Data::HAL::Link->new(relation => 'prev', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page - 1, $rows));
}
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 HEAD :Allow {
my ($self, $c) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Post => 'application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-'.$self->resource_name,
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
}
sub POST :Allow {
my ($self, $c) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $schema = $c->model('DB');
my $resource;
my $data = $self->get_valid_raw_post_data(
c => $c,
media_type => [qw#application/json text/csv#],
);
last unless $data;
if($c->request->header('Content-Type') eq 'text/csv') {
$resource = $c->req->query_params;
} else {
last unless $self->require_wellformed_json($c, 'application/json', $data);
$resource = JSON::from_json($data, { utf8 => 1 });
$data = undef;
}
if ($data) {
my($mappings, $fails, $text_success);
try {
if ($resource->{purge_existing} eq 'true') {
my ($start, $end);
$start = time;
NGCP::Panel::Utils::MySQL::truncate_table(
c => $c,
schema => $schema,
do_transaction => 0,
table => 'provisioning.emergency_mappings',
);
$schema->resultset('emergency_containers')->delete;
$end = time;
$c->log->debug("API Purging emergency mappings entries took " . ($end - $start) . "s");
}
( $mappings, $fails, $text_success ) = NGCP::Panel::Utils::EmergencyMapping::upload_csv(
c => $c,
data => \$data,
schema => $schema,
);
$c->log->info( $$text_success );
$guard->commit;
$c->response->status(HTTP_CREATED);
$c->response->body(q());
} catch($e) {
$c->log->error("failed to upload csv: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error");
last;
};
} else {
delete $resource->{purge_existing};
my $form = $self->get_form($c);
last unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
);
my $container = $c->model('DB')->resultset('emergency_containers')->find($resource->{emergency_container_id});
unless($container) {
$c->log->error("invalid emergency container id '$$resource{emergency_container_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "emergency container id does not exist");
last;
}
if ($c->model('DB')->resultset('emergency_mappings')->search({
emergency_container_id => $container->id,
code => $resource->{code}
},undef)->count > 0) {
$c->log->error("Emergency mapping code '$$resource{code}' already defined for emergency container id '$$resource{emergency_container_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "emergency mapping code already exists for emergency container");
last;
}
my $item;
try {
$item = $c->model('DB')->resultset('emergency_mappings')->create($resource);
} catch($e) {
$c->log->error("failed to create emergency mapping: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create emergency mapping.");
last;
}
$guard->commit;
$c->response->status(HTTP_CREATED);
$c->response->header(Location => sprintf('/%s%d', $c->request->path, $item->id));
$c->response->body(q());
}
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,204 @@
package NGCP::Panel::Controller::API::EmergencyMappingsItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::ValidateJSON qw();
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
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/Catalyst::Controller NGCP::Panel::Role::API::EmergencyMappings/;
sub resource_name{
return 'emergencymappings';
}
sub dispatch_path{
return '/api/emergencymappings/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-emergencymappings';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods }
},
action_roles => [qw(+NGCP::Panel::Role::HTTPMethods)],
);
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
}
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, emergencymapping => $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-resellers)"|rel="item $1"|;
s/rel=self/rel="item self"/;
$_
} $hal->http_headers),
), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
return;
}
return;
}
sub HEAD :Allow {
my ($self, $c, $id) = @_;
$c->forward(qw(GET));
$c->response->body(q());
return;
}
sub OPTIONS :Allow {
my ($self, $c, $id) = @_;
my $allowed_methods = $self->allowed_methods_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Patch => 'application/json-patch+json',
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
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, emergencymapping => $item);
my $old_resource = $self->resource_from_item($c, $item);
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
my $form = $self->get_form($c);
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
$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, emergencymapping => $item);
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,
media_type => 'application/json',
);
last unless $resource;
my $old_resource = $self->resource_from_item($c, $item);
my $form = $self->get_form($c);
$item = $self->update_item($c, $item, $old_resource, $resource, $form);
last unless $item;
$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, emergencymapping => $item);
$item->delete;
$guard->commit;
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
}
1;
# vim: set tabstop=4 expandtab:

@ -144,7 +144,7 @@ sub PUT :Allow {
last unless $preference;
my $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, emailtemplate => $item);
last unless $self->resource_exists($c, lnpnumber => $item);
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,

@ -0,0 +1,566 @@
package NGCP::Panel::Controller::EmergencyMapping;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent 'Catalyst::Controller';
use NGCP::Panel::Utils::Message;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Datatables;
use NGCP::Panel::Utils::EmergencyMapping;
use NGCP::Panel::Utils::MySQL;
use NGCP::Panel::Form::EmergencyMapping::Container;
use NGCP::Panel::Form::EmergencyMapping::ContainerAdmin;
use NGCP::Panel::Form::EmergencyMapping::Mapping;
use NGCP::Panel::Form::EmergencyMapping::Upload;
sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
$c->log->debug(__PACKAGE__ . '::auto');
NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c);
return 1;
}
sub list :Chained('/') :PathPart('emergencymapping') :CaptureArgs(0) {
my ( $self, $c ) = @_;
my $emergency_container_rs = $c->model('DB')->resultset('emergency_containers');
if($c->user->roles eq "reseller") {
$emergency_container_rs = $emergency_container_rs->search({
reseller_id => $c->user->reseller_id
});
}
$c->stash(emergency_container_rs => $emergency_container_rs);
$c->stash->{emergency_container_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => "id", "search" => 1, "title" => $c->loc("#") },
{ name => "reseller.name", "search" => 1, "title" => $c->loc("Reseller") },
{ name => "name", "search" => 1, "title" => $c->loc("Name") },
]);
my $emergency_mapping_rs = $c->model('DB')->resultset('emergency_mappings');
$c->stash(emergency_mapping_rs => $emergency_mapping_rs);
$c->stash->{emergency_mapping_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => "id", "search" => 1, "title" => $c->loc("#") },
{ name => "emergency_container.name", "search" => 1, "title" => $c->loc("Container") },
{ name => "emergency_container.reseller.name", "search" => 1, "title" => $c->loc("Reseller") },
{ name => "code", "search" => 1, "title" => $c->loc("Emergency Number") },
{ name => "prefix", "search" => 1, "title" => $c->loc("Emergency Prefix") },
]);
$c->stash(template => 'emergencymapping/list.tt');
}
sub root :Chained('list') :PathPart('') :Args(0) {
my ($self, $c) = @_;
}
sub emergency_container_ajax :Chained('list') :PathPart('emergency_container_ajax') :Args(0) {
my ($self, $c) = @_;
my $resultset = $c->stash->{emergency_container_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{emergency_container_dt_columns});
$c->detach( $c->view("JSON") );
}
sub emergency_mapping_ajax :Chained('list') :PathPart('emergency_mapping_ajax') :Args(0) {
my ($self, $c) = @_;
my $resultset = $c->stash->{emergency_mapping_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{emergency_mapping_dt_columns});
$c->detach( $c->view("JSON") );
}
sub emergency_container_base :Chained('list') :PathPart('emergency_container') :CaptureArgs(1) {
my ($self, $c, $emergency_container_id) = @_;
unless($emergency_container_id && is_int($emergency_container_id)) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $emergency_container_id },
desc => $c->loc('Invalid emergency mapping container id detected!'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
$c->response->redirect($c->uri_for());
return;
}
my $res = $c->stash->{emergency_container_rs}->find($emergency_container_id);
unless(defined($res)) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $emergency_container_id },
desc => $c->loc('Emergency mapping container does not exist!'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
$c->response->redirect($c->uri_for());
return;
}
$c->stash(emergency_container => {$res->get_inflated_columns});
$c->stash(emergency_container_result => $res);
}
sub emergency_container_edit :Chained('emergency_container_base') :PathPart('edit') {
my ($self, $c ) = @_;
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = $c->stash->{emergency_container};
$params->{reseller}{id} = delete $params->{reseller_id};
$params = merge($params, $c->session->{created_objects});
if($c->user->roles eq "reseller") {
$form = NGCP::Panel::Form::EmergencyMapping::Container->new(ctx => $c);
} else {
$form = NGCP::Panel::Form::EmergencyMapping::ContainerAdmin->new(ctx => $c);
}
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
if($c->user->roles eq "reseller") {
$form->values->{reseller_id} = $c->user->reseller_id;
} else {
$form->values->{reseller_id} = $form->values->{reseller}{id};
}
delete $form->values->{reseller};
try {
my $schema = $c->model('DB');
if ($c->model('DB')->resultset('emergency_containers')->search({
reseller_id => $form->values->{reseller_id},
name => $form->values->{name}
},undef)->count > 0) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { name => $form->values->{name} },
desc => $c->loc("Emergency mapping container with this name already exists for this reseller!"),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
return;
}
$schema->txn_do(sub {
$c->stash->{emergency_container_result}->update($form->values);
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Emergency mapping container successfully updated'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update emergency mapping container'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
}
$c->stash( 'emergency_container_edit_flag' => 1 );
$c->stash( 'emergency_container_form' => $form );
}
sub emergency_container_create :Chained('list') :PathPart('emergency_container_create') :Args(0) {
my ($self, $c) = @_;
my $schema = $c->model('DB');
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = {};
$params = merge($params, $c->session->{created_objects});
if($c->user->roles eq "reseller") {
$form = NGCP::Panel::Form::EmergencyMapping::Container->new(ctx => $c);
} else {
$form = NGCP::Panel::Form::EmergencyMapping::ContainerAdmin->new(ctx => $c);
}
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
if($c->user->roles eq "reseller") {
$form->values->{reseller_id} = $c->user->reseller_id;
} else {
$form->values->{reseller_id} = $form->values->{reseller}{id};
}
delete $form->values->{reseller};
try {
if ($c->model('DB')->resultset('emergency_containers')->search({
reseller_id => $form->values->{reseller_id},
name => $form->values->{name}
},undef)->count > 0) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { name => $form->values->{name} },
desc => $c->loc("Emergency mapping container with this name already exists for this reseller!"),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
return;
}
my $emergency_container = $c->model('DB')->resultset('emergency_containers')->create($form->values);
$c->session->{created_objects}->{emergency_container} = { id => $emergency_container->id };
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Emergency mapping container successfully created'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create emergency mapping container'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
}
$c->stash(emergency_container_create_flag => 1);
$c->stash(emergency_container_form => $form);
}
sub emergency_container_delete :Chained('emergency_container_base') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
my $emergency_container = $c->stash->{emergency_container_result};
my $emergency_mapping_count = $emergency_container->emergency_mappings->count;
if ($emergency_mapping_count > 0) {
NGCP::Panel::Utils::Message::error(
c => $c,
desc => $c->loc("Emergency mapping container still linked to emergency mappings."),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
return;
}
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
$emergency_container->delete;
});
NGCP::Panel::Utils::Message::info(
c => $c,
data => $c->stash->{emergency_container},
desc => $c->loc('Emergency mapping container successfully deleted'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
data => $c->stash->{emergency_container},
desc => $c->loc('Failed to delete emergency mapping container'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
};
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
}
sub emergency_mapping_base :Chained('list') :PathPart('emergency_mapping') :CaptureArgs(1) {
my ($self, $c, $emergency_mapping_id) = @_;
unless($emergency_mapping_id && is_int($emergency_mapping_id)) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $emergency_mapping_id },
desc => $c->loc('Invalid emergency mapping id detected!'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
$c->response->redirect($c->uri_for());
return;
}
my $res = $c->stash->{emergency_mapping_rs}->find($emergency_mapping_id);
unless(defined($res)) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $emergency_mapping_id },
desc => $c->loc('Emergency mapping does not exist!'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
$c->response->redirect($c->uri_for());
return;
}
$c->stash(emergency_mapping => {$res->get_inflated_columns});
$c->stash(emergency_mapping_result => $res);
}
sub emergency_mapping_edit :Chained('emergency_mapping_base') :PathPart('edit') {
my ($self, $c ) = @_;
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = $c->stash->{emergency_mapping};
$params->{emergency_container}{id} = delete $params->{emergency_container_id};
$params = merge($params, $c->session->{created_objects});
$form = NGCP::Panel::Form::EmergencyMapping::Mapping->new(ctx => $c);
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => { 'emergency_container.create' => $c->uri_for('/emergencymapping/emergency_container_create') },
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
$form->values->{emergency_container_id} = $form->values->{emergency_container}{id};
delete $form->values->{emergency_container};
my $emergency_container = $c->model('DB')->resultset('emergency_containers')->find($form->values->{emergency_container_id});
unless($emergency_container) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $form->values->{emergency_container_id} },
desc => $c->loc('Invalid emergency mapping container id detected!'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
return;
}
if ($c->model('DB')->resultset('emergency_mappings')->search({
emergency_container_id => $emergency_container->id,
code => $form->values->{code}
},undef)->count > 0) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { code => $form->values->{code} },
desc => $c->loc("Emergency code already defined for emergency mapping container!"),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
return;
}
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
$form->values->{prefix} = undef unless(length $form->values->{prefix});
$c->stash->{emergency_mapping_result}->update($form->values);
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Emergency mapping successfully updated'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update emergency mapping'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
}
$c->stash( 'emergency_mapping_edit_flag' => 1 );
$c->stash( 'emergency_mapping_form' => $form );
}
sub emergency_mapping_create :Chained('list') :PathPart('emergency_mapping_create') :Args(0) {
my ($self, $c) = @_;
my $schema = $c->model('DB');
my $posted = ($c->request->method eq 'POST');
my $form;
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form = NGCP::Panel::Form::EmergencyMapping::Mapping->new(ctx => $c);
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => { 'emergency_container.create' => $c->uri_for('/emergencymapping/emergency_container_create') },
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
$form->values->{emergency_container_id} = $form->values->{emergency_container}{id};
delete $form->values->{emergency_container};
my $emergency_container = $c->model('DB')->resultset('emergency_containers')->find($form->values->{emergency_container_id});
unless($emergency_container) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $form->values->{emergency_container_id} },
desc => $c->loc('Invalid emergency container id detected!'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
return;
}
if ($c->model('DB')->resultset('emergency_mappings')->search({
emergency_container_id => $emergency_container->id,
code => $form->values->{code}
},undef)->count > 0) {
NGCP::Panel::Utils::Message::error(
c => $c,
data => { code => $form->values->{code} },
desc => $c->loc("Emergency mapping already exists for this emergency mapping container!"),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
return;
}
try {
my $emergency_mapping = $emergency_container->emergency_mappings->create($form->values);
$c->session->{created_objects}->{emergency_mapping} = { id => $emergency_mapping->id };
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Emergency mapping successfully created'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create emergency mapping'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
}
$c->stash(emergency_mapping_create_flag => 1);
$c->stash(emergency_mapping_form => $form);
}
sub emergency_mapping_delete :Chained('emergency_mapping_base') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
my $emergency_mapping = $c->stash->{emergency_mapping_result};
try {
$emergency_mapping->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => $c->stash->{emergency_mapping},
desc => $c->loc('Emergency mapping successfully deleted'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
data => $c->stash->{emergency_mapping},
desc => $c->loc('Failed to delete emergency mapping'),
);
$c->flash(emergency_mapping_messages => delete $c->flash->{messages});
};
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/emergencymapping'));
}
sub emergency_mappings_upload :Chained('list') :PathPart('upload') :Args(0) {
my ($self, $c) = @_;
my $form = NGCP::Panel::Form::EmergencyMapping::Upload->new(ctx => $c);
my $upload = $c->req->upload('upload_mapping');
my $posted = $c->req->method eq 'POST';
my @params = ( upload_mapping => $posted ? $upload : undef, );
$form->process(
posted => $posted,
params => { @params },
action => $c->uri_for('/emergencymapping/upload'),
);
if($form->validated) {
# TODO: check by formhandler?
unless($upload) {
NGCP::Panel::Utils::Message::error(
c => $c,
desc => $c->loc('No emergency mapping file specified!'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
$c->response->redirect($c->uri_for('/emergencymapping'));
return;
}
my $data = $upload->slurp;
my($emergency_mappings, $fails, $text_success);
try {
my $schema = $c->model('DB');
$schema->txn_do(sub {
if($c->req->params->{purge_existing}) {
my ($start, $end);
$start = time;
NGCP::Panel::Utils::MySQL::truncate_table(
c => $c,
schema => $schema,
do_transaction => 0,
table => 'provisioning.emergency_mappings',
);
$c->stash->{emergency_container_rs}->delete;
$end = time;
$c->log->debug("Purging emergency mappings took " . ($end - $start) . "s");
}
( $emergency_mappings, $fails, $text_success ) = NGCP::Panel::Utils::EmergencyMapping::upload_csv(
c => $c,
data => \$data,
schema => $schema,
);
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $$text_success,
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to upload emergency mappings'),
);
$c->flash(emergency_container_messages => delete $c->flash->{messages});
}
$c->response->redirect($c->uri_for('/emergencymapping'));
return;
}
$c->stash(emergency_container_create_flag => 1);
$c->stash(emergency_container_form => $form);
}
sub emergency_mappings_download :Chained('list') :PathPart('download') :Args(0) {
my ($self, $c) = @_;
my $schema = $c->model('DB');
$c->response->header ('Content-Disposition' => 'attachment; filename="emergency_mapping_list.csv"');
$c->response->content_type('text/csv');
$c->response->status(200);
NGCP::Panel::Utils::EmergencyMapping::create_csv(
c => $c,
);
return;
}
__PACKAGE__->meta->make_immutable;
1;

@ -352,6 +352,8 @@ sub _handle_reseller_status_change {
#delete autoprov_devices
$reseller->autoprov_devices->delete_all;
$reseller->email_templates->delete_all;
$reseller->emergency_containers->emergency_mappings->delete_all;
$reseller->emergency_containers->delete_all;
}
}

@ -0,0 +1,33 @@
package NGCP::Panel::Field::EmergencyMappingContainer;
use Moose;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Field::Compound';
has_field 'id' => (
type => '+NGCP::Panel::Field::DataTable',
label => 'Emergency Mapping Container',
do_label => 0,
do_wrapper => 0,
required => 1,
template => 'helpers/datatables_field.tt',
ajax_src => '/emergencymapping/emergency_container_ajax',
table_titles => ['#', 'Reseller', 'Name'],
table_fields => ['id', 'reseller.name', 'name'],
);
has_field 'create' => (
type => 'Button',
do_label => 0,
value => 'Create Emergency Mapping Container',
element_class => [qw/btn btn-tertiary pull-right/],
);
sub validate {
my ( $self ) = @_;
my $value = $self->value;
$self->add_error('Emergency mapping container id must be a positive integer')
if(!$self->has_errors && $value->{id} !~ /^\d+$/);
}
no Moose;
1;

@ -0,0 +1,47 @@
package NGCP::Panel::Form::EmergencyMapping::Container;
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 'id' => (
type => 'Hidden'
);
has_field 'name' => (
type => 'Text',
required => 1,
maxlength => 31,
element_attr => {
rel => ['tooltip'],
title => ['A human readable name of the emergency mapping container.']
},
);
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/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,22 @@
package NGCP::Panel::Form::EmergencyMapping::ContainerAdmin;
use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::EmergencyMapping::Container';
has_field 'reseller' => (
type => '+NGCP::Panel::Field::Reseller',
validate_when_empty => 1,
element_attr => {
rel => ['tooltip'],
title => ['The reseller id to assign this emergency mapping container to.']
},
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/reseller name/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,73 @@
package NGCP::Panel::Form::EmergencyMapping::Mapping;
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 'emergency_container' => (
type => '+NGCP::Panel::Field::EmergencyMappingContainer',
label => 'Emergency Mapping Container',
validate_when_empty => 1,
element_attr => {
rel => ['tooltip'],
title => ['The emergency mapping container this mapping entry belongs to.']
},
);
has_field 'code' => (
type => 'Text',
required => 1,
maxlength => 31,
element_attr => {
rel => ['tooltip'],
title => ['The emergency code.']
},
);
has_field 'prefix' => (
type => 'Text',
required => 0,
maxlength => 31,
element_attr => {
rel => ['tooltip'],
title => ['An optional emergency prefix the emergency code is mapped to.']
},
);
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/emergency_container code prefix/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
sub validate_code {
my ( $self, $field ) = @_;
unless($field->value =~ /^\d+$/) {
$field->add_error($field->label . " must be a number");
}
return;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,45 @@
package NGCP::Panel::Form::EmergencyMapping::Upload;
use Sipwise::Base;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
use HTML::FormHandler::Widget::Block::Bootstrap;
use NGCP::Panel::Field::BillingZone;
has '+widget_wrapper' => ( default => 'Bootstrap' );
has '+enctype' => ( default => 'multipart/form-data');
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class { [qw/form-horizontal/] }
has_field 'upload_mapping' => (
type => 'Upload',
max_size => '2097152000', # 2GB
);
has_field 'purge_existing' => (
type => 'Boolean',
);
has_field 'save' => (
type => 'Submit',
value => 'Upload',
element_class => [qw/btn btn-primary/],
do_label => 0,
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/upload_mapping purge_existing/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,114 @@
package NGCP::Panel::Role::API::EmergencyMappingContainers;
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);
use NGCP::Panel::Form::EmergencyMapping::Container;
use NGCP::Panel::Form::EmergencyMapping::ContainerAdmin;
sub _item_rs {
my ($self, $c) = @_;
my $item_rs = $c->model('DB')->resultset('emergency_containers');
if($c->user->roles eq "reseller") {
$item_rs = $item_rs->search({
reseller_id => $c->user->reseller_id,
});
}
return $item_rs;
}
sub get_form {
my ($self, $c) = @_;
my $form;
if($c->user->roles eq "reseller") {
$form = NGCP::Panel::Form::EmergencyMapping::Container->new(ctx => $c);
} else {
$form = NGCP::Panel::Form::EmergencyMapping::ContainerAdmin->new(ctx => $c);
}
return $form;
}
sub hal_from_item {
my ($self, $c, $item, $form) = @_;
my %resource = $item->get_inflated_columns;
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:emergencymappings', href => sprintf("/api/emergencymappings/?emergency_container_id=%d", $item->id)),
Data::HAL::Link->new(relation => 'ngcp:reseller', href => sprintf("/api/resellers/%d", $item->reseller_id)),
],
relation => 'ngcp:'.$self->resource_name,
);
$form //= $self->get_form($c);
$self->validate_form(
c => $c,
resource => \%resource,
form => $form,
run => 0,
);
$resource{id} = int($item->id);
$hal->resource({%resource});
return $hal;
}
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 $reseller_item = $c->model('DB')->resultset('resellers')->find($resource->{reseller_id});
unless($reseller_item) {
$c->log->error("reseller id '$$resource{reseller_id}' does not exist");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Reseller id does not exist");
return;
}
my $dup_item = $c->model('DB')->resultset('emergency_containers')->find({
reseller_id => $resource->{reseller_id},
name => $resource->{name},
});
if($dup_item && $dup_item->id != $item->id) {
$c->log->error("emergency mapping container with name '$$resource{name}' already exists for this reseller");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Emergency mapping container with this name already exists for this reseller");
return;
}
$item->update($resource);
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,112 @@
package NGCP::Panel::Role::API::EmergencyMappings;
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);
use NGCP::Panel::Form::EmergencyMapping::Mapping;
sub _item_rs {
my ($self, $c) = @_;
my $item_rs = $c->model('DB')->resultset('emergency_mappings');
if($c->user->roles eq "reseller") {
$item_rs = $item_rs->search({
'emergency_container.reseller_id' => $c->user->reseller_id,
},{
'join' => 'emergency_container',
});
}
return $item_rs;
}
sub get_form {
my ($self, $c) = @_;
return NGCP::Panel::Form::EmergencyMapping::Mapping->new(ctx => $c);
}
sub hal_from_item {
my ($self, $c, $item, $form) = @_;
my %resource = $item->get_inflated_columns;
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:emergencymappingcontainers', href => sprintf("/api/emergencymappingcontainers/%d", $item->emergency_container_id)),
],
relation => 'ngcp:'.$self->resource_name,
);
$form //= $self->get_form($c);
$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) = @_;
my $r = { $item->get_inflated_columns };
return $r;
}
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);
$resource->{lnp_provider_id} = delete $resource->{carrier_id};
return unless $self->validate_form(
c => $c,
form => $form,
resource => $resource,
);
my $container = $c->model('DB')->resultset('emergency_containers')->find($resource->{emergency_container_id});
unless($container) {
$c->log->error("invalid emergency container id '$$resource{emergency_container_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Emergency container id does not exist");
return;
}
if ($old_resource->{code} ne $resource->{code} && $c->model('DB')->resultset('emergency_mappings')->search({
emergency_container_id => $container->id,
code => $resource->{code}
},undef)->count > 0) {
$c->log->error("Emergency mapping code '$$resource{code}' already exists for container id '$$resource{emergency_container_id}'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "emergency mapping code already exists for emergency container");
return;
}
$item->update($resource);
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,109 @@
package NGCP::Panel::Utils::EmergencyMapping;
use strict;
use warnings;
use Text::CSV_XS;
use NGCP::Panel::Utils::MySQL;
sub _insert_batch {
my ($c, $schema, $mappings, $chunk_size) = @_;
NGCP::Panel::Utils::MySQL::bulk_insert(
c => $c,
schema => $schema,
do_transaction => 0,
query => "REPLACE INTO provisioning.emergency_mappings(emergency_container_id, code, prefix)",
data => $mappings,
chunk_size => $chunk_size
);
}
sub upload_csv {
my(%params) = @_;
my ($c,$data,$schema) = @params{qw/c data schema/};
my ($start, $end);
# csv bulk upload
my $csv = Text::CSV_XS->new({ allow_whitespace => 1, binary => 1, keep_meta_info => 1 });
#my @cols = @{ $c->config->{lnp_csv}->{element_order} };
my @cols = qw/name reseller_id code prefix/;
my @fields ;
my @fails = ();
my $linenum = 0;
my @mappings = ();
my %containers = ();
open(my $fh, '<:encoding(utf8)', $data);
$start = time;
my $chunk_size = 2000;
while ( my $line = $csv->getline($fh)) {
++$linenum;
unless (scalar @{ $line } == scalar @cols) {
push @fails, $linenum;
next;
}
my $row = {};
@{$row}{@cols} = @{ $line };
my $k = $row->{name};
my $r = $row->{reseller_id};
unless(exists $containers{$k}) {
my $container = $schema->resultset('emergency_containers')->find_or_create({
name => $k,
reseller_id => $r,
});
$containers{$k} = $container->id;
}
push @mappings, [$containers{$k}, $row->{code}, $row->{prefix}];
if($linenum % $chunk_size == 0) {
_insert_batch($c, $schema, \@mappings, $chunk_size);
@mappings = ();
}
}
if(@mappings) {
_insert_batch($c, $schema, \@mappings, $chunk_size);
}
$end = time;
close $fh;
$c->log->debug("Parsing and uploading Emergency Mappings CSV took " . ($end - $start) . "s");
my $text = $c->loc('Emergency Mappings successfully uploaded');
if(@fails) {
$text .= $c->loc(", but skipped the following line numbers: ") . (join ", ", @fails);
}
return ( \@mappings, \@fails, \$text );
}
sub create_csv {
my(%params) = @_;
my($c) = @params{qw/c/};
#my @cols = @{ $c->config->{emergency_mapping_csv}->{element_order} };
my @cols = qw/name reseller_id code prefix/;
my $mapping_rs = $c->stash->{emergency_mapping_rs}->search_rs(
undef,
{
'+select' => ['emergency_container.name', 'emergency_container.reseller_id'],
'+as' => ['name', 'reseller_id'],
'join' => 'emergency_container',
}
);
my ($start, $end);
$start = time;
while(my $mapping_row = $mapping_rs->next) {
my %mapping = $mapping_row->get_inflated_columns;
delete $mapping{id};
$c->res->write_fh->write(join (",", @mapping{@cols}) );
$c->res->write_fh->write("\n");
}
$c->res->write_fh->close;
$end = time;
$c->log->debug("Creating Emergency Mapping CSV for download took " . ($end - $start) . "s");
return 1;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,68 @@
[% site_config.title = c.loc('Emergency Mappings') -%]
<div class="row">
<span>
<a class="btn btn-primary btn-large" href="[% c.uri_for('/back') %]"><i class="icon-arrow-left"></i> [% c.loc('Back') %]</a>
<a class="btn btn-primary btn-large" href="[% c.uri_for('/emergencymapping/download') %]"><i class="icon-star"></i> [% c.loc('Download CSV') %]</a>
<a class="btn btn-primary btn-large" href="[% c.uri_for('/emergencymapping/upload') %]"><i class="icon-star"></i> [% c.loc('Upload CSV') %]</a>
</span>
</div>
[% back_created = 1 -%]
<h3>[% c.loc('Emergency Containers') %]</h3>
[%
helper.name = c.loc('Emergency Containers');
helper.identifier = 'emergency_containers';
helper.messages = emergency_container_messages;
helper.dt_columns = emergency_container_dt_columns;
helper.length_change = 1;
helper.close_target = emergency_container_close_target;
helper.create_flag = emergency_container_create_flag;
helper.edit_flag = emergency_container_edit_flag;
helper.form_object = emergency_container_form;
helper.ajax_uri = c.uri_for('emergency_container_ajax');
UNLESS c.user.read_only;
helper.dt_buttons = [
{ name = c.loc('Delete'), uri = "/emergencymapping/emergency_container/'+full[\"id\"]+'/delete", class = 'btn-small btn-secondary', icon = 'icon-remove' },
{ name = c.loc('Edit'), uri = "/emergencymapping/emergency_container/'+full[\"id\"]+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' },
];
helper.top_buttons = [
{ name = c.loc('Create Emergency Container'), uri = c.uri_for('/emergencymapping/emergency_container_create'), icon = 'icon-star' },
];
END;
PROCESS 'helpers/datatables.tt';
-%]
<div class="ngcp-separator"></div>
<h3>[% c.loc('Emergency Mappings') %]</h3>
[%
helper.name = c.loc('Emergency Mappings');
helper.identifier = 'emergency_mappings';
helper.messages = emergency_mapping_messages;
helper.dt_columns = emergency_mapping_dt_columns;
helper.length_change = 1;
helper.close_target = emergency_mapping_close_target;
helper.create_flag = emergency_mapping_create_flag;
helper.edit_flag = emergency_mapping_edit_flag;
helper.form_object = emergency_mapping_form;
helper.ajax_uri = c.uri_for('emergency_mapping_ajax');
UNLESS c.user.read_only;
helper.dt_buttons = [
{ name = c.loc('Delete'), uri = "/emergencymapping/emergency_mapping/'+full[\"id\"]+'/delete", class = 'btn-small btn-secondary', icon = 'icon-remove' },
{ name = c.loc('Edit'), uri = "/emergencymapping/emergency_mapping/'+full[\"id\"]+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' },
];
helper.top_buttons = [
{ name = c.loc('Create Emergency Mapping'), uri = c.uri_for('/emergencymapping/emergency_mapping_create'), icon = 'icon-star' },
];
END;
PROCESS 'helpers/datatables.tt';
-%]
[% # vim: set tabstop=4 syntax=html expandtab: -%]

@ -73,6 +73,7 @@
<li><a href="[% c.uri_for('/maliciouscall') %]">[% c.loc('Malicious Calls') %]</a></li>
[% END -%]
<li><a href="[% c.uri_for('/lnp') %]">[% c.loc('Number Porting') %]</a></li>
<li><a href="[% c.uri_for('/emergencymapping') %]">[% c.loc('Emergency Mappings') %]</a></li>
</ul>
</li>
[% # vim: set tabstop=4 syntax=html expandtab: -%]

@ -41,6 +41,7 @@
[% IF c.config.features.malicious_call -%]
<li><a href="[% c.uri_for('/maliciouscall') %]">[% c.loc('Malicious Calls') %]</a></li>
[% END -%]
<li><a href="[% c.uri_for('/emergencymapping') %]">[% c.loc('Emergency Mappings') %]</a></li>
</ul>
</li>
[% # vim: set tabstop=4 syntax=html expandtab: -%]

Loading…
Cancel
Save