App-Netdisco

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

  * #1015 API job queue management - get list, submit, delete some or all, get backend names

  [BUG FIXES]

  * fix cosmetic bug in search text strikethrough when in tenant

2.061000 - 2023-03-29

  [NEW FEATURES]

  * #29 sidebar support for custom reports with bind params; add show_sidebar setting
  * #1001 support for FQDN node search while domain_suffix is set; add fallback to IPv4 host lookup search
  * #1002 implement ignore_layers, force_macsuck, force_arpnip config settings

  [ENHANCEMENTS]

  * allow 3min for port last_change compare to uptime, do not assume wrapped

  [BUG FIXES]

  * fix nonimpacting error in template html

Changes  view on Meta::CPAN


  [BUG FIXES]

  * Fix typo in the Device By Location report template

2.029010 - 2014-10-07

  [NEW FEATURES]

  * Administration (SSH, Telnet, Web) links for devices
  * [#143] Pass parameter(s) to custom reports via bind_params config

  [BUG FIXES]

  * Require old DBIC version to fix deploy problem

2.029009 - 2014-09-27

  [ENHANCEMENTS]

  * Defaults now exist for all expire tasks

lib/App/Netdisco.pm  view on Meta::CPAN

 ~/bin/netdisco-deploy
 
 # restart web service (if you run it)
 ~/bin/netdisco-web restart
 
 # restart the backend workers (wherever you run them)
 ~/bin/netdisco-backend restart

Furthermore, whenever you upgrade your Operating System, you must delete the
C<~/perl5> directory and re-run the following command, to update Netdisco's C
library bindings (and then run all of the above upgrade commands again):

 curl -L https://cpanmin.us/ | perl - --notest --local-lib ~/perl5 App::Netdisco

=head1 Tips and Tricks

=head2 Searching

The main black navigation bar has a search box which is smart enough to work
out what you're looking for in most cases. For example device names, node IP
or MAC addresses, VLAN numbers, and so on.

lib/App/Netdisco/Configuration.pm  view on Meta::CPAN

if (setting('reports') and ref {} eq ref setting('reports')) {
    config->{'reports'} = [ map {{
        tag => $_,
        %{ setting('reports')->{$_} }
    }} keys %{ setting('reports') } ];
}

# add system_reports onto reports
config->{'reports'} = [ @{setting('system_reports')}, @{setting('reports')} ];

# upgrade bare bind_params to dict
foreach my $r ( @{setting('reports')} ) {
    next unless exists $r->{bind_params};
    my $new_bind_params = [ map {ref $_ ? $_ : {param => $_}} @{ $r->{bind_params} } ];
    $r->{'bind_params'} = $new_bind_params;
}

# set swagger ui location
#config->{plugins}->{Swagger}->{ui_dir} =
  #dir(dist_dir('App-Netdisco'), 'share', 'public', 'swagger-ui')->absolute;

# setup helpers for when request->uri_for() isn't available
# (for example when inside swagger_path())
config->{url_base}
  = URI::Based->new((config->{path} eq '/') ? '' : config->{path});

lib/App/Netdisco/DB/ResultSet/Admin.pm  view on Meta::CPAN


=cut

sub skipped {
  my ($rs, $backend, $max_deferrals, $retry) = @_;
  $backend ||= 'fqdn-undefined';
  $max_deferrals ||= (2**30); # not really 'disabled'
  $retry ||= '100 years'; # not really 'disabled'

  return $rs->correlate('device_skips')->search(undef,{
    # NOTE: bind param list order is significant
    bind => [[deferrals => $max_deferrals], [last_defer => $retry], [backend => $backend]],
  });
}

=head2 with_times

This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized columns to the result set:

=over 4

lib/App/Netdisco/DB/ResultSet/Device.pm  view on Meta::CPAN


=head2 device_ips_with_address_or_name( $address_or_name )

Returns a correlated subquery for the set of C<device_ip> entries for each
device. The IP alias or dns matches the supplied C<address_or_name>, using
C<ILIKE>.

=cut

sub device_ips_with_address_or_name {
  my ($rs, $q, $ipbind) = @_;
  $q ||= '255.255.255.255/32';

  return $rs->search(undef,{
    # NOTE: bind param list order is significant
    join => ['device_ips_by_address_or_name'],
    bind => [$q, $ipbind, $q],
  });
}

=head2 with_module_serials

Adds the C<module_serials.serial> field to the results using
C<module_serials> relation.

=cut

lib/App/Netdisco/DB/ResultSet/Device.pm  view on Meta::CPAN

Returns a correlated subquery for the set of C<device_port> entries for each
device. The port MAC address matches the supplied C<mac>, using C<ILIKE>.

=cut

sub ports_with_mac {
  my ($rs, $mac) = @_;
  $mac ||= '00:00:00:00:00:00';

  return $rs->search(undef,{
    # NOTE: bind param list order is significant
    join => ['ports_by_mac'],
    bind => [$mac],
  });
}

=head2 with_times

This is a modifier for any C<search()> (including the helpers below) which
will add the following additional synthesized columns to the result set:

=over 4

lib/App/Netdisco/DB/ResultSet/Device.pm  view on Meta::CPAN

    die "missing param to search_fuzzy\n"
      unless $q;
    $q = "\%$q\%" if $q !~ m/\%/;
    (my $qc = $q) =~ s/\%//g;

    # basic IP check is a string match
    my $ip_clause = [
        'me.ip::text'  => { '-ilike' => $q },
        'device_ips_by_address_or_name.alias::text' => { '-ilike' => $q },
    ];
    my $ipbind = '255.255.255.255/32';

    # but also allow prefix search
    if ($qc =~ m{^(?:$RE{net}{IPv4}|$RE{net}{IPv6})(?:/\d+)?$}i
        and my $ip = NetAddr::IP::Lite->new($qc)) {

        $ip_clause = [
            'me.ip'  => { '<<=' => $ip->cidr },
            'device_ips_by_address_or_name.alias' => { '<<=' => $ip->cidr },
        ];
        $ipbind = $ip->cidr;
    }

    # get IEEE MAC format
    my $mac = NetAddr::MAC->new(mac => ($q || ''));
    undef $mac if
      ($mac and $mac->as_ieee
      and (($mac->as_ieee eq '00:00:00:00:00:00')
        or ($mac->as_ieee !~ m/^$RE{net}{MAC}$/i)));
    $mac = ($mac ? $mac->as_ieee : $q);

    return $rs->ports_with_mac($mac)
              ->device_ips_with_address_or_name($q, $ipbind)
              ->search(
      {
        -or => [
          'me.contact'     => { '-ilike' => $q },
          'me.serial'      => { '-ilike' => $q },
          'me.chassis_id'  => { '-ilike' => $q },
          'me.location'    => { '-ilike' => $q },
          'me.name'        => { '-ilike' => $q },
          'me.description' => { '-ilike' => $q },
          'me.ip' => { '-in' =>

lib/App/Netdisco/DB/ResultSet/DeviceBrowser.pm  view on Meta::CPAN

Returns a correlated subquery for the set of C<snmp_object> entry for 
the walked data row.

=cut

sub with_snmp_object {
  my ($rs, $ip) = @_;
  $ip ||= '255.255.255.255';

  return $rs->search(undef,{
    # NOTE: bind param list order is significant
    join => ['snmp_object'],
    bind => [$ip],
    prefetch => 'snmp_object',
  });
}

1;

lib/App/Netdisco/DB/SetOperations.pm  view on Meta::CPAN

 
   for (@operands) {
      $self->throw_exception("ResultClass of ResultSets do not match!")
         unless $self->result_class eq $_->result_class;
 
      my $attrs = $_->_resolved_attrs;
 
      $self->throw_exception('ResultSets do not all have the same selected columns!')
         unless $self->_compare_arrays($as, $attrs->{as});
 
      my ($sql, @bind) = @{${$_->as_query}};
      # $sql =~ s/^\s*\((.*)\)\s*$/$1/;
      $sql = q<(> . $sql . q<)>;
 
      push @sql, $sql;
      push @params, @bind;
   }
 
   my $query = q<(> . join(" $operation ", @sql). q<)>;

   my $attrs = $self->_resolved_attrs;
   return $self->result_source->resultset->search(undef, {
      alias => $self->current_source_alias,
      from => [{
         $self->current_source_alias => \[ $query, @params ],
         -alias                      => $self->current_source_alias,

lib/App/Netdisco/JobQueue/PostgreSQL.pm  view on Meta::CPAN

}

sub jq_getsome {
  my $num_slots = shift;
  return () unless $num_slots and $num_slots > 0;

  my $jobs = schema(vars->{'tenant'})->resultset('Admin');
  my @returned = ();

  my $tasty = schema(vars->{'tenant'})->resultset('Virtual::TastyJobs')
    ->search(undef,{ bind => [
      setting('workers')->{'BACKEND'}, setting('job_prio')->{'high'},
      setting('workers')->{'BACKEND'}, setting('workers')->{'max_deferrals'},
      setting('workers')->{'retry_after'}, $num_slots,
    ]});

  while (my $job = $tasty->next) {
    if ($job->device
      and not scalar grep {$job->action eq $_} @{ setting('job_targets_prefix') }) {

      # need to handle device discovered since backend daemon started

lib/App/Netdisco/Util/PortMAC.pm  view on Meta::CPAN

addresses supplied as array reference

=cut

sub get_port_macs {
    my ($fw_mac_list) = $_[0];
    my $port_macs = {};
    return {} unless ref [] eq ref $fw_mac_list and @{$fw_mac_list} >= 1;
    $fw_mac_list = [ grep {defined and length} @$fw_mac_list ];

    my $bindarray = [ { sqlt_datatype => "array" }, $fw_mac_list ];

    my $macs
        = schema(vars->{'tenant'})->resultset('Virtual::PortMacs')->search({},
        { bind => [$bindarray, $bindarray], select => [ 'mac', 'ip' ], group_by => [ 'mac', 'ip' ] } );
    my $cursor = $macs->cursor;
    while ( my @vals = $cursor->next ) {
        $port_macs->{ $vals[0] } = $vals[1];
    }

    return $port_macs;
}

1;

lib/App/Netdisco/Web/Auth/Provider/DBIC.pm  view on Meta::CPAN


    # this method returns a list of current user roles
    # but for API with trust_remote_user, trust_x_remote_user, and no_auth
    # we need to fake that there is a valid API key

    my $api_requires_key =
      (setting('trust_remote_user') or setting('trust_x_remote_user') or setting('no_auth'))
        eq '1' ? 'false' : 'true';

    return [ try {
      $user->$roles->search({}, { bind => [
          $api_requires_key, setting('api_token_lifetime'),
          $api_requires_key, setting('api_token_lifetime'),
        ] })->get_column( $role_column )->all;
    } ];
}

sub match_password {
    my($self, $password, $user) = @_;
    return unless $user;

lib/App/Netdisco/Web/Auth/Provider/DBIC.pm  view on Meta::CPAN


sub match_with_ldap {
    my($self, $pass, $user) = @_;

    return unless setting('ldap') and ref {} eq ref setting('ldap');
    my $conf = setting('ldap');

    my $ldapuser = $conf->{user_string};
    $ldapuser =~ s/\%USER\%?/$user/egi;

    # If we can bind as anonymous or proxy user,
    # search for user's distinguished name
    if ($conf->{proxy_user}) {
        my $user   = $conf->{proxy_user};
        my $pass   = $conf->{proxy_pass};
        my $attrs  = ['distinguishedName'];
        my $result = _ldap_search($ldapuser, $attrs, $user, $pass);
        $ldapuser  = $result->[0] if ($result->[0]);
    }
    # otherwise, if we can't search and aren't using AD and then construct DN
    # by appending base

lib/App/Netdisco/Web/Auth/Provider/DBIC.pm  view on Meta::CPAN


    foreach my $server (@{$conf->{servers}}) {
        my $opts = $conf->{opts} || {};
        my $ldap = Net::LDAP->new($server, %$opts) or next;
        my $msg  = undef;

        if ($conf->{tls_opts} ) {
            $msg = $ldap->start_tls(%{$conf->{tls_opts}});
        }

        $msg = $ldap->bind($ldapuser, password => $pass);
        $ldap->unbind(); # take down session

        return 1 unless $msg->code();
    }

    return undef;
}

sub _ldap_search {
    my ($filter, $attrs, $user, $pass) = @_;
    my $conf = setting('ldap');

lib/App/Netdisco/Web/Auth/Provider/DBIC.pm  view on Meta::CPAN

    foreach my $server (@{$conf->{servers}}) {
        my $opts = $conf->{opts} || {};
        my $ldap = Net::LDAP->new($server, %$opts) or next;
        my $msg  = undef;

        if ($conf->{tls_opts}) {
            $msg = $ldap->start_tls(%{$conf->{tls_opts}});
        }

        if ( $user and $user ne 'anonymous' ) {
            $msg = $ldap->bind($user, password => $pass);
        }
        else {
            $msg = $ldap->bind();
        }

        $msg = $ldap->search(
          base   => $conf->{base},
          filter => "($filter)",
          attrs  => $attrs,
        );

        $ldap->unbind(); # take down session

        my $entries = [$msg->entries];
        return $entries unless $msg->code();
    }

    return undef;
}

sub match_with_radius {
  my($self, $pass, $user) = @_;

lib/App/Netdisco/Web/Device.pm  view on Meta::CPAN

  cookie('nd_ports-form' => $uri->query(), expires => '365 days');
};

get '/device' => require_login sub {
    my $q = param('q');
    my $devices = schema(vars->{'tenant'})->resultset('Device');

    # we are passed either dns or ip
    my $dev = $devices->search({
        -or => [
            \[ 'host(me.ip) = ?' => [ bind_value => $q ] ],
            'me.dns' => $q,
        ],
    });

    if ($dev->count == 0) {
        return redirect uri_for('/', {nosuchdevice => 1, device => $q})->path_query;
    }

    # if passed dns, need to check for duplicates
    # and use only ip for q param, if there are duplicates.

lib/App/Netdisco/Web/GenericReport.pm  view on Meta::CPAN

foreach my $report (@{setting('reports')}) {
  my $r = $report->{tag};

  register_report({
    tag => $r,
    label => $report->{label},
    category => ($report->{category} || 'My Reports'),
    ($report->{hidden} ? (hidden => true) : ()),
    provides_csv => true,
    api_endpoint => true,
    bind_params  => [ map {ref $_ ? $_->{param} : $_} @{ $report->{bind_params} } ],
    api_parameters => $report->{api_parameters},
  });

  get "/ajax/content/report/$r" => require_login sub {
      # TODO: this should be done by creating a new Virtual Result class on
      # the fly (package...) and then calling DBIC register_class on it.

      my $schema = ($report->{database} || vars->{'tenant'});
      my $rs = schema($schema)->resultset('Virtual::GenericReport')->result_source;
      (my $query = $report->{query}) =~ s/;$//;

lib/App/Netdisco/Web/GenericReport.pm  view on Meta::CPAN


      $rs->view_definition($query);
      $rs->remove_columns($rs->columns);
      $rs->add_columns( exists $report->{query_columns}
        ? @{ $report->{query_columns} } : @column_order
      );

      my $set = schema($schema)->resultset('Virtual::GenericReport')
        ->search(undef, {
          result_class => 'DBIx::Class::ResultClass::HashRefInflator',
          ( (exists $report->{bind_params})
            ? (bind => [map { param($_) } map {ref $_ ? $_->{param} : $_} @{ $report->{bind_params} }]) : () ),
        });
      @data = $set->all;

      # Data Munging support...

      my $compartment = Safe->new;
      $config = $report; # closure for the config of this report
      $compartment->share(qw/$config @data/);
      $compartment->permit_only(qw/:default sort/);

lib/App/Netdisco/Web/Plugin.pm  view on Meta::CPAN

          if ($config->{api_endpoint}) {
              (my $category_path = lc $config->{category}) =~ s/ /-/g;
              my $params_copy = dclone ($config->{api_parameters} || []); # swagger plugin nukes it?

              swagger_path {
                tags => ['Reports'],
                path => setting('api_base')."/report/$category_path/$tag",
                description => $config->{label} .' Report',
                parameters =>
                  ($config->{api_parameters} ||
                  ($config->{bind_params} ? [map { $_ => {} } @{ $config->{bind_params} }] : [])),
                responses => 
                  ($config->{api_responses} || { default => {} }),
              },

              get "/api/v1/report/$category_path/$tag" => require_role api => sub {
                # #1360 workaround for swagger missing that False is false
                foreach my $spec (pairs @{ $params_copy }) {
                    my ($param, $conf) = @$spec;
                    next unless exists $conf->{type} and $conf->{type} eq 'boolean';
                    next unless exists request->{'_query_params'}->{$param}

lib/App/Netdisco/Web/Plugin/Device/SNMP.pm  view on Meta::CPAN

        { node => \%data, munge => $munge, mungers => \@mungers },
        { layout => 'noop' };
};

sub _get_snmp_data {
    my ($ip, $base, $recurse) = @_;
    my @parts = grep {length} split m/\./, $base;

    my %meta = map { ('.'. join '.', @{$_->{oid_parts}}) => $_ }
               schema(vars->{'tenant'})->resultset('Virtual::FilteredSNMPObject')
                                 ->search({}, { bind => [
                                     $ip,
                                     (scalar @parts + 1),
                                     (scalar @parts + 1),
                                     $base,
                                 ] })->hri->all;

    my @items = map {{
        id => $_,
        mib  => $meta{$_}->{mib},  # accessed via node.original.mib
        leaf => $meta{$_}->{leaf}, # accessed via node.original.leaf

lib/App/Netdisco/Web/Plugin/Report/DeviceDnsMismatch.pm  view on Meta::CPAN

        api_endpoint => 1,
    }
);

get '/ajax/content/report/devicednsmismatch' => require_login sub {

    (my $suffix = '***:'. setting('domain_suffix')) =~ s|\Q(?^\Eu?|(?|g;

    my @results
        = schema(vars->{'tenant'})->resultset('Virtual::DeviceDnsMismatch')
        ->search( undef, { bind => [ $suffix, $suffix ] } )
        ->columns( [qw/ ip dns name location contact /] )->hri->all;

    return unless scalar @results;

    if ( request->is_ajax ) {
        my $json = to_json( \@results );
        template 'ajax/report/devicednsmismatch.tt', { results => $json }, { layout => 'noop' };
    }
    else {
        header( 'Content-Type' => 'text/comma-separated-values' );

lib/App/Netdisco/Web/Plugin/Report/IpInventory.pm  view on Meta::CPAN

        }
    )->hri;

    my $rs_union = $rs1->union( [ $rs2, $rs3 ] );

    if ( $never ) {
        $subnet = NetAddr::IP::Lite->new('0.0.0.0/32') if ($subnet->bits ne 32);

        my $rs4 = schema(vars->{'tenant'})->resultset('Virtual::CidrIps')->search(
            undef,
            {   bind => [ $subnet->cidr ],
                columns   => [qw( ip mac time_first time_last dns active node age vendor nbname )],
            }
        )->hri;

        $rs_union = $rs_union->union( [$rs4] );
    }

    my $rs_sub = $rs_union->search(
        { ip => { '<<' => $subnet->cidr } },
        {   select   => [

lib/App/Netdisco/Web/Plugin/Report/PortUtilization.pm  view on Meta::CPAN

        ],
    }
);

get '/ajax/content/report/portutilization' => require_login sub {
    return unless schema(vars->{'tenant'})->resultset('Device')->count;

    my $age_num = param('age_num') || 3;
    my $age_unit = param('age_unit') || 'months';
    my @results = schema(vars->{'tenant'})->resultset('Virtual::PortUtilization')
      ->search(undef, { bind => [ "$age_num $age_unit", "$age_num $age_unit", "$age_num $age_unit" ] })->hri->all;

    if (request->is_ajax) {
        my $json = to_json (\@results);
        template 'ajax/report/portutilization.tt', { results => $json }, { layout => 'noop' };
    }
    else {
        header( 'Content-Type' => 'text/comma-separated-values' );
        template 'ajax/report/portutilization_csv.tt', { results => \@results, }, { layout => 'noop' };
    }
};

lib/App/Netdisco/Web/Plugin/Report/PortVLANMismatch.pm  view on Meta::CPAN

        label    => 'Mismatched VLANs',
        provides_csv => 1,
        api_endpoint => 1,
    }
);

get '/ajax/content/report/portvlanmismatch' => require_login sub {
    return unless schema(vars->{'tenant'})->resultset('Device')->count;
    my @results = schema(vars->{'tenant'})
      ->resultset('Virtual::PortVLANMismatch')->search({},{
          bind => [ setting('sidebar_defaults')->{'device_ports'}->{'p_hide1002'}->{'default'}
                      ? (1002, 1003, 1004, 1005) : (0, 0, 0, 0) ],
      })
      ->hri->all;

#    # note that the generated list is rendered without HTML escape,
#    # so we MUST sanitise here with the grep
#    foreach my $res (@results) {
#        my @left  = grep {m/^(?:n:)?\d+$/} map {s/\s//g; $_} split ',', $res->{left_vlans};
#        my @right = grep {m/^(?:n:)?\d+$/} map {s/\s//g; $_} split ',', $res->{right_vlans};
#

lib/App/Netdisco/Web/Plugin/Report/SubnetUtilization.pm  view on Meta::CPAN

    my $agenot = param('age_invert') || '0';

    my $daterange = param('daterange')
      || ('1970-01-01 to '. strftime('%Y-%m-%d', gmtime));
    my ( $start, $end ) = $daterange =~ /(\d+-\d+-\d+)/gmx;
    $start = $start . ' 00:00:00';
    $end   = $end . ' 23:59:59';

    my @results = schema(vars->{'tenant'})->resultset('Virtual::SubnetUtilization')
      ->search(undef,{
        bind => [ $subnet, $start, $end, $start, $subnet, $start, $start ],
      })->hri->all;

    return unless scalar @results;

    if ( request->is_ajax ) {
        template 'ajax/report/subnets.tt', { results => \@results }, { layout => 'noop' };
    }
    else {
        header( 'Content-Type' => 'text/comma-separated-values' );
        template 'ajax/report/subnets_csv.tt', { results => \@results }, { layout => 'noop' };

lib/App/Netdisco/Worker/Plugin/Arpwalk.pm  view on Meta::CPAN

        device  => '255.255.255.255',
      })->count();

  return Status->done('Arpwalk is able to run');
});

register_worker({ phase => 'main' }, sub {
  my ($job, $workerconf) = @_;

  my @walk = schema(vars->{'tenant'})->resultset('Virtual::WalkJobs')
    ->search(undef,{ bind => [
      'arpnip', 'arpnip',
      setting('workers')->{'max_deferrals'},
      setting('workers')->{'retry_after'},
    ]})->get_column('ip')->all;

  jq_insert([
    map {{
      device => $_,
      action => 'arpnip',
      username => $job->username,

lib/App/Netdisco/Worker/Plugin/DiscoverAll.pm  view on Meta::CPAN

        device  => '255.255.255.255',
      })->count();

  return Status->done('Discoverall is able to run');
});

register_worker({ phase => 'main' }, sub {
  my ($job, $workerconf) = @_;

  my @walk = schema(vars->{'tenant'})->resultset('Virtual::WalkJobs')
    ->search(undef,{ bind => [
      'discover', 'discover',
      setting('workers')->{'max_deferrals'},
      setting('workers')->{'retry_after'},
    ]})->get_column('ip')->all;

  jq_insert([
    map {{
      device => $_,
      action => 'discover',
      username => $job->username,

lib/App/Netdisco/Worker/Plugin/Macwalk.pm  view on Meta::CPAN

        device  => '255.255.255.255',
      })->count();

  return Status->done('Macwalk is able to run');
});

register_worker({ phase => 'main' }, sub {
  my ($job, $workerconf) = @_;

  my @walk = schema(vars->{'tenant'})->resultset('Virtual::WalkJobs')
    ->search(undef,{ bind => [
      'macsuck', 'macsuck',
      setting('workers')->{'max_deferrals'},
      setting('workers')->{'retry_after'},
    ]})->get_column('ip')->all;

  jq_insert([
    map {{
      device => $_,
      action => 'macsuck',
      username => $job->username,

lib/App/Netdisco/Worker/Plugin/Nbtwalk.pm  view on Meta::CPAN

        device  => '255.255.255.255',
      })->count();

  return Status->done('Nbtwalk is able to run');
});

register_worker({ phase => 'main' }, sub {
  my ($job, $workerconf) = @_;

  my @walk = schema(vars->{'tenant'})->resultset('Virtual::WalkJobs')
    ->search(undef,{ bind => [
      'macsuck', 'macsuck',
      setting('workers')->{'max_deferrals'},
      setting('workers')->{'retry_after'},
    ]})->get_column('ip')->all;

  jq_insert([
    map {{
      device => $_,
      action => 'nbtstat',
      username => $job->username,

lib/App/Netdisco/Worker/Plugin/Scheduler.pm  view on Meta::CPAN

  my ($job, $workerconf) = @_;

  my $coder = JSON::PP->new->utf8(0)->allow_nonref(1)->allow_unknown(1);
  my $sched = $coder->decode( $job->extra || {} );
  my $action = $sched->{action} || $sched->{label};

  return Status->error("Missing label of Scheduler entry")
    unless $action;

  my @walk = schema(vars->{'tenant'})->resultset('Virtual::WalkJobs')
    ->search(undef,{ bind => [
      $action, ('scheduled-'. $sched->{label}),
      setting('workers')->{'max_deferrals'},
      setting('workers')->{'retry_after'},
    ]})->get_column('ip')->all;

  jq_insert([
    map {{
      device => $_,
      action => $action,
      port      => $sched->{port},

lib/App/Netdisco/Worker/Plugin/Snapshot.pm  view on Meta::CPAN

    set(net_snmp_options => {
      %{ setting('net_snmp_options') },
      'UseLongNames' => 1,	   # Return full OID tags
      'UseSprintValue' => 0,
      'UseEnums'	=> 0,	   # Don't use enumerated vals
      'UseNumeric' => 1,	   # Return dotted decimal OID
    });

    my $snmp = App::Netdisco::Transport::SNMP->reader_for($device);
    my $sess = $snmp->session();
    my $from = SNMP::Varbind->new([ $extra || '.1' ]);

    my $vars = [];
    my $errornum = 0;
    my %store = ();

    debug sprintf 'bulkwalking %s from %s', $device->ip, ($extra || '.1');
    ($vars) = $sess->bulkwalk( 0, $snmp->{BulkRepeaters}, $from );

    if ( $sess->{ErrorNum} ) {
        return Status->error(

share/config.yml  view on Meta::CPAN

      - { ip: 'Device IP', _searchable: true }
      - { devname: 'Name' }
      - { model: 'Model' }
      - { vendor: 'Vendor' }
      - { creation: 'Date Added' }
      - { os: 'Operating System' }
      - { os_ver: 'OS Version' }
      - { location: 'Location' }
      - { contact: 'Contact' }
      - { serial: 'Serial' }
    bind_params:
      - { param: 'since', default: '2 months' }
    query: |
      SELECT ip, COALESCE(NULLIF(dns,''), NULLIF(name,''), '') AS devname,
          model, vendor, creation, os, os_ver, location, contact, serial
        FROM device
        WHERE creation > (LOCALTIMESTAMP - COALESCE(NULLIF(?,''), '2 months')::interval)
        ORDER BY creation DESC
  - tag: portswithmostvlans
    category: Port
    label: 'Ports with the most VLANs'
    columns:
      - { ip: 'Device IP', _searchable: true }
      - { port: 'Port' }
      - { vlans: 'VLAN Count' }
    bind_params:
      - { param: 'threshold', default: 1, type: 'number' }
    query: |
      SELECT ip, port, count(vlan) AS vlans
        FROM device_port_vlan
        GROUP BY ip, port
        HAVING count(vlan) > COALESCE(NULLIF(?,''), '1') ::integer
        ORDER BY vlans DESC, ip ASC, port ASC
  - tag: duplicateprivatenetworks
    category: IP
    label: 'Duplicate Private Networks'

share/config.yml  view on Meta::CPAN

          OR subnet << 'fd00::/8')
        GROUP BY subnet
        HAVING count(subnet) > 1
        ORDER BY subnet
  - tag: vlansonlyuplinks
    category: VLAN
    label: 'VLANs Only On Uplinks'
    columns:
      - { ip: 'Device IP', _searchable: true }
      - { vlans: 'VLAN List' }
    bind_params:
      - { param: 'chunk_size', default: 20, type: 'number' }
    query: |
      SELECT ip, array_agg(vlans) AS vlans FROM (
        SELECT ip, array_to_string(array_agg(vlan), ', ') AS vlans, (x / COALESCE(NULLIF(?,''), '20') ::integer) AS chunk FROM (
          SELECT *, (row_number() over (partition by ip)) AS x FROM (

            SELECT DISTINCT ip, vlan
              FROM device_port_vlan dpv
              WHERE native IS false
                AND vlan <> 1

share/config.yml  view on Meta::CPAN

            ORDER BY ip, vlan) s2

        ) s1 GROUP BY ip, chunk
      ) s0 GROUP BY ip ORDER BY ip
  - tag: vlansneverconfigured
    category: VLAN
    label: 'VLANs Known but Not Configured'
    columns:
      - { ip: 'Device IP', _searchable: true }
      - { vlans: 'VLAN List' }
    bind_params:
      - { param: 'chunk_size', default: 20, type: 'number' }
    query: |
      SELECT ip, array_agg(vlans) AS vlans FROM (
        SELECT ip, array_to_string(array_agg(vlan), ', ') AS vlans, (x / COALESCE(NULLIF(?,''), '20') ::integer) AS chunk FROM (
          SELECT *, (row_number() over (partition by ip)) AS x FROM (

            SELECT DISTINCT ip, vlan
              FROM device_vlan dv
              WHERE vlan <> 1
                AND NOT EXISTS (

share/config.yml  view on Meta::CPAN

              ORDER BY ip, vlan) s2

        ) s0 GROUP BY ip, chunk
      ) s1 GROUP BY ip ORDER BY ip
  - tag: vlansunused
    category: VLAN
    label: 'VLANs No Longer Used'
    columns:
      - { ip: 'Device IP', _searchable: true }
      - { vlans: 'VLAN List' }
    bind_params:
      - { param: 'chunk_size', default: 20, type: 'number' }
      - { param: 'since', default: '3 months' }
    query: |
      SELECT ip, array_agg(vlans) AS vlans FROM (
        SELECT ip, array_to_string(array_agg(vlan), ', ') AS vlans, (x / COALESCE(NULLIF(?,''), '20') ::integer) AS chunk FROM (
          SELECT *, (row_number() over (partition by ip)) AS x FROM (

            SELECT DISTINCT ip, vlan
              FROM device_port_vlan dpv
              WHERE dpv.native IS false

share/public/javascripts/d3-3.5.17.min.js  view on Meta::CPAN

!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){ret...
r(),S.point=c,S.lineEnd=s}function c(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,o,t),S.lineEnd=a,a()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:a,polygonStart:function(){t.polygonStart(),S.l...
return g}:En(r),w=u===i?function(){return p}:En(i);++y<M;)a.call(this,h=t[y],y)?(d.push([g=+x.call(this,h,y),p=+b.call(this,h,y)]),m.push([+_.call(this,h,y),+w.call(this,h,y)])):d.length&&(l(),d=[],m=[]);return d.length&&l(),v.length?v.join(""):null}...
shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov...
if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++i<a;)u=n[i],u.x=o,u.y=c,u.dy=s,o+=u.dx=Math.min(e.x+e.dx-o,s?l(u.area/s):0);u.z=!0,u.dx+=e.x+e.dx-o,e.y+=s,e.dy-=s}else{for((r||s>e.dx)&&(s=e.dx);++i<a;)u=n[i],u.x=o,u.y=c,u.dx=s,c+=u.dy=Math.min(e.y+e.dy-c,s?l...

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

                .style("left", v.dom.customizePosition.left + "px")
                .style("top", v.dom.customizePosition.top + "px");
            v.dom.customize.append("span")
                .attr("class", "drag")
                .call(v.tools.customizeDrag)
                .append("span")
                .attr("class", "title")
                .text("Customize \"" + v.dom.containerId + "\"");
            v.dom.customize.append("a")
                .attr("class", "close focus")
                .attr("tabindex", 1)
                .text("Close")
                .on("click", function() {
                    v.status.customize = false;
                    v.tools.removeCustomizeWizard();
                    v.tools.createCustomizeLink();
                })
                .on("keydown", function() {
                    if (d3.event.keyCode === 13) {
                        v.status.customize = false;
                        v.tools.removeCustomizeWizard();

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

            v.dom.customizeMenu = gridCell.append("span");
            v.dom.customizeOptionsTable = gridCell.append("table");
            for (key in v.confDefaults) {
                if (v.confDefaults.hasOwnProperty(key) && v.confDefaults[key].display) {
                    i += 1;
                    row = v.dom.customizeOptionsTable.append("tr")
                        .attr("class", v.confDefaults[key].relation + "-related");
                    row.append("td")
                        .attr("class", "label")
                        .html("<a href=\"https://ogobrecht.github.io/d3-force-apex-plugin/module-API.html#." +
                            key + "\" target=\"github_d3_force\" tabindex=\"" + i + 100 + "\">" +
                            key + "</a>");
                    td = row.append("td");
                    form = td.append("select")
                        .attr("id", v.dom.containerId + "_" + key)
                        .attr("name", key)
                        .attr("value", v.conf[key])
                        .attr("tabindex", i + 1)
                        .classed("warning", v.confDefaults[key].internal)
                        .on("change", onSelectChange);
                    valueInOptions = false;
                    appendOptionsToSelect(key);
                    // append current value if not existing in default options
                    if (!valueInOptions) {
                        form.append("option")
                            .attr("value", v.conf[key])
                            .attr("selected", "selected")
                            .text(v.conf[key]);

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

                .style("vertical-align", "top")
                .style("padding-left", "5px");
            gridCell.append("span")
                .html("Your Configuration Object<p style=\"font-size:10px;margin:0;\">" +
                    (v.status.apexPluginId ?
                        "To save your options please copy<br>this to your plugin region attributes.<br>" +
                        "Only non-default options are shown.</p>" :
                        "Use this to initialize your graph.<br>Only non-default options are shown.</p>")
                );
            v.dom.customizeConfObject = gridCell.append("textarea")
                .attr("tabindex", i + 5)
                .attr("readonly", "readonly");
            gridCell.append("span").html("<br><br>Current Positions<br>");
            v.dom.customizePositions = gridCell.append("textarea")
                .attr("tabindex", i + 6)
                .attr("readonly", "readonly")
                .text((v.status.forceRunning ? "Force started - wait for end event to show positions..." :
                    JSON.stringify(graph.positions())));
            gridCell.append("span").html("<br><br>Debug Log (descending)<br>");
            v.dom.customizeLog = gridCell.append("textarea")
                .attr("tabindex", i + 7)
                .attr("readonly", "readonly");
            gridRow = grid.append("tr");
            gridCell = gridRow.append("td")
                .attr("colspan", 2)
                .html("Copyrights:");
            gridRow = grid.append("tr");
            gridCell = gridRow.append("td")
                .attr("colspan", 2)
                .html("<table><tr><td style=\"padding-right:20px;\">" +
                    "<a href=\"https://github.com/ogobrecht/d3-force-apex-plugin\" target=\"_blank\" " +
                    "tabindex=\"" + (i + 8) + "\">D3 Force APEX Plugin</a> (" + v.version +
                    ")<br>Ottmar Gobrecht</td><td style=\"padding-right:20px;\">" +
                    "<a href=\"https://github.com/mbostock/d3\" target=\"d3js_org\" tabindex=\"" + (i + 9) +
                    "\">D3.js</a> (" + d3.version + ") and " +
                    "<a href=\"https://github.com/d3/d3-plugins/tree/master/lasso\" target=\"_blank\" tabindex=\"" +
                    (i + 10) + "\">D3 Lasso Plugin</a> (modified)<br>Mike Bostock" +
                    "</td></tr><tr><td colspan=\"3\">" +
                    "<a href=\"https://github.com/tinker10/D3-Labeler\" target=\"github_d3_labeler\" " +
                    "tabindex=\"" + (i + 11) +
                    "\">D3 Labeler Plugin</a> (automatic label placement using simulated annealing)" +
                    "<br>Evan Wang</td></tr></table>"); // https://github.com/tinker10/D3-Labeler
            v.tools.createCustomizeMenu(v.status.customizeCurrentMenu);
            v.tools.writeConfObjectIntoWizard();
            if (v.status.customizeCurrentTabPosition) {
                document.getElementById(v.status.customizeCurrentTabPosition).focus();
            }
        }
    };

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

        if (v.status.customizeCurrentMenu === "nodes") {
            v.dom.customizeMenu.append("span").style("font-weight", "bold").style("margin-left", "10px").text("NODES");
            v.dom.customizeOptionsTable.selectAll("tr.node-related").classed("hidden", false);
            v.dom.customizeOptionsTable.selectAll("tr.label-related,tr.link-related,tr.graph-related")
                .classed("hidden", true);
        } else {
            v.dom.customizeMenu.append("a")
                .style("font-weight", "bold")
                .style("margin-left", "10px")
                .text("NODES")
                .attr("tabindex", 2)
                .on("click", function() {
                    v.tools.createCustomizeMenu("nodes");
                    v.dom.customizeOptionsTable.selectAll("tr.node-related").classed("hidden", false);
                    v.dom.customizeOptionsTable.selectAll("tr.label-related,tr.link-related,tr.graph-related")
                        .classed("hidden", true);
                })
                .on("keydown", function() {
                    if (d3.event.keyCode === 13) {
                        v.tools.createCustomizeMenu("nodes");
                        v.dom.customizeOptionsTable.selectAll("tr.node-related").classed("hidden", false);

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

        if (v.status.customizeCurrentMenu === "labels") {
            v.dom.customizeMenu.append("span").style("font-weight", "bold").style("margin-left", "10px").text("LABELS");
            v.dom.customizeOptionsTable.selectAll("tr.label-related").classed("hidden", false);
            v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.link-related,tr.graph-related")
                .classed("hidden", true);
        } else {
            v.dom.customizeMenu.append("a")
                .style("font-weight", "bold")
                .style("margin-left", "10px")
                .text("LABELS")
                .attr("tabindex", 2)
                .on("click", function() {
                    v.tools.createCustomizeMenu("labels");
                    v.dom.customizeOptionsTable.selectAll("tr.label-related").classed("hidden", false);
                    v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.link-related,tr.graph-related")
                        .classed("hidden", true);
                })
                .on("keydown", function() {
                    if (d3.event.keyCode === 13) {
                        v.tools.createCustomizeMenu("labels");
                        v.dom.customizeOptionsTable.selectAll("tr.label-related").classed("hidden", false);

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

        if (v.status.customizeCurrentMenu === "links") {
            v.dom.customizeMenu.append("span").style("font-weight", "bold").style("margin-left", "10px").text("LINKS");
            v.dom.customizeOptionsTable.selectAll("tr.link-related").classed("hidden", false);
            v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.label-related,tr.graph-related")
                .classed("hidden", true);
        } else {
            v.dom.customizeMenu.append("a")
                .style("font-weight", "bold")
                .style("margin-left", "10px")
                .text("LINKS")
                .attr("tabindex", 3)
                .on("click", function() {
                    v.tools.createCustomizeMenu("links");
                    v.dom.customizeOptionsTable.selectAll("tr.link-related").classed("hidden", false);
                    v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.label-related,tr.graph-related")
                        .classed("hidden", true);
                })
                .on("keydown", function() {
                    if (d3.event.keyCode === 13) {
                        v.tools.createCustomizeMenu("links");
                        v.dom.customizeOptionsTable.selectAll("tr.link-related").classed("hidden", false);

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

        if (v.status.customizeCurrentMenu === "graph") {
            v.dom.customizeMenu.append("span").style("font-weight", "bold").style("margin-left", "10px").text("GRAPH");
            v.dom.customizeOptionsTable.selectAll("tr.graph-related").classed("hidden", false);
            v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.label-related,tr.link-related")
                .classed("hidden", true);
        } else {
            v.dom.customizeMenu.append("a")
                .style("font-weight", "bold")
                .style("margin-left", "10px")
                .text("GRAPH")
                .attr("tabindex", 4)
                .on("click", function() {
                    v.tools.createCustomizeMenu("graph");
                    v.dom.customizeOptionsTable.selectAll("tr.graph-related").classed("hidden", false);
                    v.dom.customizeOptionsTable.selectAll("tr.node-related,tr.label-related,tr.link-related")
                        .classed("hidden", true);
                })
                .on("keydown", function() {
                    if (d3.event.keyCode === 13) {
                        v.tools.createCustomizeMenu("graph");
                        v.dom.customizeOptionsTable.selectAll("tr.graph-related").classed("hidden", false);

share/public/javascripts/d3-force-network-chart.js  view on Meta::CPAN

    graph.version = function() {
        return v.version;
    };

    /*******************************************************************************************************************
     * Startup code - runs one time after the initialization of a new chart - example:
     * var myChart = net_gobrechts_d3_force( domContainerId, pConf, apexPluginId ).start();
     */

    if (v.status.apexPluginId) {
        // bind to the apexrefresh event, so that this region can be refreshed by a dynamic action
        apex.jQuery("#" + v.dom.containerId).bind("apexrefresh", function() {
            graph.start();
        });
        //rerender on window resize
        apex.jQuery(window).on("apexwindowresized", function() {
            graph.render();
        });
        apex.jQuery("#t_Button_navControl").click(function() {
            setTimeout(function() {
                graph.render();
            }, 500);

share/public/javascripts/dataTables.bootstrap.js  view on Meta::CPAN

				}
			};

			$(nPaging).addClass('pagination').append(
				'<ul>'+
					'<li class="prev disabled"><a href="#">&larr; '+oLang.sPrevious+'</a></li>'+
					'<li class="next disabled"><a href="#">'+oLang.sNext+' &rarr; </a></li>'+
				'</ul>'
			);
			var els = $('a', nPaging);
			$(els[0]).bind( 'click.DT', { action: "previous" }, fnClickHandler );
			$(els[1]).bind( 'click.DT', { action: "next" }, fnClickHandler );
		},

		"fnUpdate": function ( oSettings, fnDraw ) {
			var iListLength = 5;
			var oPaging = oSettings.oInstance.fnPagingInfo();
			var an = oSettings.aanFeatures.p;
			var i, ien, j, sClass, iStart, iEnd, iHalf=Math.floor(iListLength/2);

			if ( oPaging.iTotalPages < iListLength) {
				iStart = 1;

share/public/javascripts/dataTables.bootstrap.js  view on Meta::CPAN


			for ( i=0, ien=an.length ; i<ien ; i++ ) {
				// Remove the middle elements
				$('li:gt(0)', an[i]).filter(':not(:last)').remove();

				// Add the new list items and their event handlers
				for ( j=iStart ; j<=iEnd ; j++ ) {
					sClass = (j==oPaging.iPage+1) ? 'class="active"' : '';
					$('<li '+sClass+'><a href="#">'+j+'</a></li>')
						.insertBefore( $('li:last', an[i])[0] )
						.bind('click', function (e) {
							e.preventDefault();
							oSettings._iDisplayStart = (parseInt($('a', this).text(),10)-1) * oPaging.iLength;
							fnDraw( oSettings );
						} );
				}

				// Add / remove disabled classes from the static elements
				if ( oPaging.iPage === 0 ) {
					$('li:first', an[i]).addClass('disabled');
				} else {

share/public/javascripts/jquery-history.js  view on Meta::CPAN

(function(a,b){"use strict";var c=a.History=a.History||{},d=a.jQuery;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){d(a).bind(b,c)},trigger:function(a,b,c){d(a).trigge...



( run in 1.545 second using v1.01-cache-2.11-cpan-2398b32b56e )