App-Manoc/lib/App/Manoc/Controller/Device.pm
package App::Manoc::Controller::Device;
#ABSTRACT: Device Controller
use Moose;
our $VERSION = '2.99.4'; ##TRIAL VERSION
use namespace::autoclean;
BEGIN { extends 'Catalyst::Controller'; }
with
"App::Manoc::ControllerRole::CommonCRUD",
"App::Manoc::ControllerRole::JSONView" => { -excludes => 'get_json_object', },
"App::Manoc::ControllerRole::CSVView";
use Text::Diff;
use App::Manoc::Form::Device::Edit;
use App::Manoc::Form::DeviceNWInfo;
use App::Manoc::Form::Cabling;
use App::Manoc::Form::Uplink;
use App::Manoc::Form::Device::Decommission;
use App::Manoc::Netwalker::Config;
use App::Manoc::Netwalker::ControlClient;
# moved App::Manoc::Netwalker::DeviceUpdater to conditional block in refresh
# where we need it
__PACKAGE__->config(
# define PathPart
action => {
setup => {
PathPart => 'device',
}
},
class => 'ManocDB::Device',
form_class => 'App::Manoc::Form::Device::Edit',
view_object_perm => undef,
json_columns => [ 'id', 'name' ],
object_list_options => {
prefetch => [ { 'rack' => 'building' }, 'mng_url_format', 'hwasset', 'netwalker_info', ]
},
create_page_title => 'New device',
edit_page_title => 'Edit device',
);
sub view : Chained('object') : PathPart('') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
# uplinks
$c->stash( uplinks => [ map { $_->interface } $device->uplinks->all() ] );
# prepare template
$c->stash( template => 'device/view.tt' );
}
sub ifstatus : Chained('object') : PathPart('ifstatus') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
my $id = $device->id;
# TODO prefetch cabling
my %cabling = map { $_->interface1 => 1 } $device->cablings;
# prefetch interfaces last activity
my %if_last_mat;
my ( $e, $it );
$it = $c->model('ManocDB::DeviceIface')->search_mat_last_activity($id);
while ( $e = $it->next ) {
$if_last_mat{ $e->get_column('interface') } = $e->get_column('lastseen');
}
my @iface_info = $device->interfaces->all;
foreach my $r ( $device->interfaces->all ) {
$r->{last_mat} = $if_last_mat{ $r->name },;
}
@iface_info =
sort { $a->controller cmp $b->controller || $a->port <=> $b->port } @iface_info;
$c->stash->{no_wrapper} = 1;
$c->stash->{iface_info} = \@iface_info;
}
sub neighs : Chained('object') : PathPart('neighs') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
my $time_limit = $c->config->{Device}->{cdp_age} || 3600 * 12; #12 hours
my @neighs =
map +{
expired => time - $_->last_seen > $time_limit,
local_iface => $_->from_interface,
remote_iface => $_->to_interface,
to_device => $_->to_device,
to_device_info => $_->to_device_info,
remote_id => $_->remote_id,
remote_type => $_->remote_type,
date => $_->last_seen,
},
$device->neighs( {}, { prefetch => 'to_device_info' } )->all();
$c->stash->{no_wrapper} = 1;
$c->stash->{neighs} = \@neighs;
}
sub ssids : Chained('object') : PathPart('ssids') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
# wireless info
# ssid
my @ssid_list = map +{
interface => $_->interface,
ssid => $_->ssid,
broadcast => $_->broadcast ? 'yes' : 'no',
channel => $_->channel
},
$device->ssids;
$c->stash->{ssid_list} = \@ssid_list;
$c->stash->{no_wrapper} = 1;
}
sub dot11clients : Chained('object') : PathPart('dot11clients') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
my @dot11_clients = map +{
ssid => $_->ssid,
macaddr => $_->macaddr,
ipaddr => $_->ipaddr,
vlan => $_->vlan,
quality => $_->quality . '/100',
state => $_->state,
},
$device->dot11clients;
$c->stash->{dot11_clients} = \@dot11_clients;
$c->stash->{no_wrapper} = 1;
}
sub cablings : Chained('object') : PathPart('cablings') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
$c->require_permission( $device, 'view' );
$c->stash->{no_wrapper} = 1;
$c->stash->{cablings} = [ $device->cablings->all ];
my $form = App::Manoc::Form::Cabling->new(
{
ctx => $c,
}
);
$c->stash( form => $form );
}
sub refresh : Chained('object') : PathPart('refresh') : Args(0) {
my ( $self, $c ) = @_;
my $device_id = $c->stash->{object}->id;
my $config = App::Manoc::Netwalker::Config->new( $c->config->{Netwalker} || {} );
my $client = App::Manoc::Netwalker::ControlClient->new( config => $config );
my $status = $client->enqueue_device($device_id);
if ( !$status ) {
$c->flash( error_msg => "An error occurred while scheduling device refresh" );
}
else {
$c->flash( message => "Device refresh scheduled" );
}
$c->response->redirect( $c->uri_for_action( '/device/view', [$device_id] ) );
$c->detach();
}
sub uplinks : Chained('object') : PathPart('uplinks') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
$c->require_permission( $device, 'edit' );
my $form = App::Manoc::Form::Uplink->new( { device => $device, ctx => $c } );
if ( $device->interfaces->count() == 0 ) {
$c->flash( error_msg => 'No known interfaces on this device' );
$c->uri_for_action( 'device/view', [ $device->id ] );
$c->detach();
}
$c->stash(
form => $form,
action => $c->uri_for( $c->action, $c->req->captures ),
);
return
unless $form->process( params => $c->req->parameters, );
$c->response->redirect( $c->uri_for_action( 'device/view', [ $device->id ] ) );
$c->detach();
}
sub nwinfo : Chained('object') : PathPart('nwinfo') : Args(0) {
my ( $self, $c ) = @_;
$c->require_permission( $c->stash->{object}, 'netwalker_config' );
my $device_id = $c->stash->{object_pk};
my $nwinfo = $c->model('ManocDB::DeviceNWinfo')->find($device_id);
$nwinfo or $nwinfo = $c->model('ManocDB::DeviceNWInfo')->new_result( {} );
my $form = App::Manoc::Form::DeviceNWInfo->new(
{
device => $device_id,
ctx => $c,
}
);
$c->stash( form => $form );
return unless $form->process(
params => $c->req->params,
item => $nwinfo
);
$c->response->redirect( $c->uri_for_action( 'device/view', [$device_id] ) );
$c->detach();
}
sub iface : Chained('object') : PathPart('iface') : Args(1) {
my ( $self, $c, $name ) = @_;
my $iface = $c->model('ManocDB::DeviceIface')->find(
{
device_id => $c->stash->{device_id},
name => $name
}
);
$c->response->redirect( $c->uri_for_action( 'deviceiface/view', [ $iface->id ] ) );
$c->detach();
}
sub show_config : Chained('object') : PathPart('config') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{object};
$c->require_permission( $device, 'show_config' );
my $config = $device->config;
my $prev_config = $config->prev_config;
my $curr_config = $config->config;
#Get diff and modify diff string
my $diff = diff( \$prev_config, \$curr_config );
#Clear "@@...@@" stuff
$diff =~ s/@@[^@]*@@/<hr>/g;
#Insert HTML "font" tag to color "+" and "-" rows
$diff =~ s/^\+(.*)$/<font color=\"green\"> $1<\/font>/mg;
$diff =~ s/^\-(.*)$/<font color=\"red\"> $1<\/font>/mg;
#Prepare template
$c->stash(
config => $config,
diff => $diff,
);
$c->stash( template => 'device/show_run.tt' );
}
before 'create' => sub {
my ( $self, $c ) = @_;
my $rack_id = $c->req->query_parameters->{'rack'};
if ( defined($rack_id) ) {
$c->log->debug("new device in rack $rack_id") if $c->debug;
$c->stash( form_defaults => { rack => $rack_id } );
}
};
sub decommission : Chained('object') : PathPart('decommission') : Args(0) {
my ( $self, $c ) = @_;
$c->require_permission( $c->stash->{object}, 'edit' );
my $form = App::Manoc::Form::Device::Decommission->new( { ctx => $c } );
$c->stash(
form => $form,
action => $c->uri_for( $c->action, $c->req->captures ),
);
return unless $form->process(
item => $c->stash->{object},
params => $c->req->parameters,
);
$c->response->redirect( $c->uri_for_action( 'device/view', [ $c->stash->{object_pk} ] ) );
$c->detach();
}
sub restore : Chained('object') : PathPart('restore') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{object};
$c->require_permission( $device, 'edit' );
if ( !$device->decommissioned ) {
$c->response->redirect( $c->uri_for_action( 'device/view', [ $device->id ] ) );
$c->detach();
}
if ( $c->req->method eq 'POST' ) {
$device->restore;
$device->update();
$c->flash( message => "Device restored" );
$c->response->redirect( $c->uri_for_action( 'device/view', [ $device->id ] ) );
$c->detach();
}
# show confirm page
$c->stash(
title => 'Restore network device',
confirm_message => 'Restore decommissione device ' . $device->name . '?',
template => 'generic_confirm.tt',
);
}
sub update_from_nwinfo : Chained('object') : PathPart('from_nwinfo') : Args(0) {
my ( $self, $c ) = @_;
my $device = $c->stash->{object};
$c->require_permission( $device, 'edit' );
my $response = {};
$response->{success} = 0;
if ( !$device->decommissioned &&
defined( $device->netwalker_info ) &&
$c->req->method eq 'POST' )
{
my $nwinfo = $device->netwalker_info;
my $what = lc( $c->req->params->{what} );
if ( $what eq 'name' ) {
$nwinfo->name and $device->name( $nwinfo->name );
}
$device->update();
$response->{success} = 1;
}
$c->stash( json_data => $response );
$c->forward('View::JSON');
}
sub ifacecreate : Chained('object') : PathPart('ifacecreate') : Args(0) {
my ( $self, $c ) = @_;
# device is already in stash
$c->forward('/deviceiface/create');
}
sub ifacepopulate : Chained('object') : PathPart('ifacepopulate') : Args(0) {
my ( $self, $c ) = @_;
# device is already in stash
$c->forward('/deviceiface/populate');
}
sub get_object {
my ( $self, $c, $id ) = @_;
my $object = $c->stash->{resultset}->find($id);
if ( !defined($object) ) {
$object = $c->stash->{resultset}->find( { mng_address => $id } );
}
if ($object) {
$c->stash(
device => $object,
device_id => $id,
);
}
return $object;
}
sub delete_object {
my ( $self, $c ) = @_;
my $device = $c->stash->{'object'};
my $name = $device->name;
my $has_related_info = $device->interfaces->count() ||
$device->uplinks->count() ||
$device->mat_assocs()->count() ||
$device->dot11assocs->count() ||
$device->neighs->count();
if ($has_related_info) {
$c->flash(
error_msg => "Device '$device' has some associated info and cannot be deleted." );
return;
}
return $device->delete;
}
sub get_json_object {
my ( $self, $c, $device ) = @_;
my $r = $self->prepare_json_object( $c, $device );
$r->{rack} = $device->rack->id, return $r;
}
__PACKAGE__->meta->make_immutable;
1;
# Local Variables:
# mode: cperl
# indent-tabs-mode: nil
# cperl-indent-level: 4
# cperl-indent-parens-as-block: t
# End:
__END__
=pod
=head1 NAME
App::Manoc::Controller::Device - Device Controller
=head1 VERSION
version 2.99.4
=head1 CONSUMED ROLES
=over 4
=item *
App::Manoc::ControllerRole::CommonCRUD
=item *
App::Manoc::ControllerRole::JSONView
=item *
App::Manoc::ControllerRole::CSVView
=back
=head1 ACTIONS
=head2 view
=head2 ifstatus
Called via xhr by view
=head2 neighs
Called via xhr by view
=head2 ssids
Called via xhr by view
=head2 dot11clients
Called via xhr by view
=head2 cablings
Called via xhr by view
=head2 refresh
=head2 uplinks
=head2 nwinfo
=head2 show_config
Show running configuration
=head2 create
Override in order to manage rack parameter.
=head2 decommission
=head2 restore
=head2 update_from_nwinfo
=head2 ifacecreate
Redirect to deviceiface method
=head2 ifacepopulate
Redirect to deviceiface method
=head1 METHODS
=head2 iface
Get interface by names
=head2 get_object
Find by id or mng_address.
=head2 delete_object
=head2 get_json_object
=head1 AUTHORS
=over 4
=item *
Gabriele Mambrini <gmambro@cpan.org>
=item *
Enrico Liguori
=back
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2017 by Gabriele Mambrini.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut