diff --git a/lib/NGCP/Panel/Controller/Subscriber.pm b/lib/NGCP/Panel/Controller/Subscriber.pm index 1769292479..49ebad6bfa 100644 --- a/lib/NGCP/Panel/Controller/Subscriber.pm +++ b/lib/NGCP/Panel/Controller/Subscriber.pm @@ -6,6 +6,7 @@ use NGCP::Panel::Utils; use NGCP::Panel::Utils::Navigation; use NGCP::Panel::Utils::Contract; use NGCP::Panel::Utils::Subscriber; +use NGCP::Panel::Utils::Datatables; use NGCP::Panel::Form::Subscriber; use NGCP::Panel::Form::SubscriberCFSimple; use NGCP::Panel::Form::SubscriberCFTSimple; @@ -41,6 +42,14 @@ sub sub_list :Chained('/') :PathPart('subscriber') :CaptureArgs(0) { $c->stash( template => 'subscriber/list.tt', ); + + NGCP::Panel::Utils::Datatables::set_columns($c, [ + { name => "id", title => "#" }, + { name => "username", "search" => 1, title => 'Username' }, + { name => "domain.domain", "search" => 1, title => 'Domain' }, + { name => "contract.status", "search" => 1, title => 'Contract Status' }, + { name => "status", "search" => 1, title => 'Status' }, + ]); #NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c); } @@ -190,24 +199,16 @@ sub ajax :Chained('sub_list') :PathPart('ajax') :Args(0) { my ($self, $c) = @_; my $dispatch_to = '_ajax_resultset_' . $c->user->auth_realm; my $resultset = $self->$dispatch_to($c); - $c->forward( "/ajax_process_resultset", [$resultset, - ["id", "username", "domain_name", "contract_id", "status",], - ["username", "domain.domain", "contract_id", "status",]]); + + + NGCP::Panel::Utils::Datatables::process($c, $resultset); + $c->detach( $c->view("JSON") ); } sub _ajax_resultset_admin { my ($self, $c) = @_; - return $c->model('DB')->resultset('voip_subscribers')->search_rs({}, - { - 'join' => 'domain', - '+select' => [ - 'domain.domain', - ], - '+as' => [ - 'domain_name', - ] - }); + return $c->model('DB')->resultset('voip_subscribers')->search; } sub _ajax_resultset_reseller { diff --git a/lib/NGCP/Panel/Utils/Datatables.pm b/lib/NGCP/Panel/Utils/Datatables.pm new file mode 100644 index 0000000000..7998d8a1f5 --- /dev/null +++ b/lib/NGCP/Panel/Utils/Datatables.pm @@ -0,0 +1,135 @@ +package NGCP::Panel::Utils::Datatables; +use strict; +use warnings; + +use Sipwise::Base; +use List::Util qw/first/; +use Scalar::Util qw/blessed/; + +sub process { + my ($c, $rs) = @_; + + my $cols = $c->stash->{dt_columns}; + + my $aaData = []; + my $totalRecords = $rs->count; + my $displayRecords = 0; + + # check if we need to join more tables + # TODO: can we nest it deeper than once level? + for my $c(@{ $cols }) { + my @parts = split /\./, $c->{name}; + if(@parts == 2) { + $rs = $rs->search_rs(undef, { + join => $parts[0], + '+select' => [ $c->{name} ], + '+as' => [ $c->{accessor} ], + }); + } elsif(@parts > 2) { + # TODO throw an error for now as we only support one level + } + } + + # searching + my @searchColumns = (); + foreach my $c(@{ $cols }) { + # avoid amigious column names if we have the same column in different joined tables + my $name = $c->{name} =~ /\./ ? $c->{name} : 'me.'.$c->{name}; + push @searchColumns, $name if $c->{search}; + } + my $searchString = $c->request->params->{sSearch} // ""; + if($searchString) { + $rs = $rs->search([ map{ +{ $_ => { like => '%'.$searchString.'%' } } } @searchColumns ]); + } + + $displayRecords = $rs->count; + + # show specific row on top (e.g. if we come back from a newly created entry) + my $topId = $c->request->params->{iIdOnTop}; + if(defined $topId) { + if(defined(my $row = $rs->find($topId))) { + push @{ $aaData }, _prune_row($cols, $row->get_inflated_columns); + $rs = $rs->search({ 'me.id' => { '!=', $topId} }); + } else { + $c->log->error("iIdOnTop $topId not found in resultset " . ref $rs); + } + } + + + # sorting + my $sortColumn = $c->request->params->{iSortCol_0}; + my $sortDirection = $c->request->params->{sSortDir_0} || 'asc'; + if(defined $sortColumn && defined $sortDirection) { + if('desc' eq lc $sortDirection) { + $sortDirection = 'desc'; + } else { + $sortDirection = 'asc'; + } + + # first, get the fields we're actually showing + my @displayedFields = (); + for my $c(@{ $cols }) { + next unless $c->{title}; + # again, add 'me' to avoid ambiguous column names + my $name = $c->{name} =~ /\./ ? $c->{name} : 'me.'.$c->{name}; + push @displayedFields, $name; + } + # ... and pick the name defined by the dt index + my $sortName = $displayedFields[$sortColumn]; + + $rs = $rs->search(undef, { + order_by => { + "-$sortDirection" => $sortName, + } + }); + } + + # pagination + my $pageStart = $c->request->params->{iDisplayStart}; + my $pageSize = $c->request->params->{iDisplayLength}; + if(defined $pageStart && defined $pageSize && $pageSize > 0) { + $rs = $rs->search(undef, { + offset => $pageStart, + rows => $pageSize, + }); + } + + for my $row ($rs->all) { + push @{ $aaData }, _prune_row($cols, $row->get_inflated_columns); + } + + $c->stash( + aaData => $aaData, + iTotalRecords => $totalRecords, + iTotalDisplayRecords => $displayRecords, + sEcho => int($c->request->params->{sEcho} // 1), + ); + +} + +sub set_columns { + my ($c, $cols) = @_; + + for my $c(@{ $cols }) { + $c->{accessor} = $c->{name}; + $c->{accessor} =~ s/\./_/g; + } + $c->stash->{dt_columns} = $cols; +} + +sub _prune_row { + my ($columns, %row) = @_; + while (my ($k,$v) = each %row) { + unless (first { $_->{accessor} eq $k && $_->{title} } @{ $columns }) { + delete $row{$k}; + next; + } + $row{$k} = $v->datetime if blessed($v) && $v->isa('DateTime'); + } + return { %row }; +} + + +1; + +# vim: set tabstop=4 expandtab: diff --git a/share/templates/subscriber/list.tt b/share/templates/subscriber/list.tt index 44d8222a70..db1dec605c 100644 --- a/share/templates/subscriber/list.tt +++ b/share/templates/subscriber/list.tt @@ -3,15 +3,23 @@ helper.name = 'Subscriber'; helper.data = subscribers; helper.messages = messages; - helper.column_titles = [ '#', 'Username', 'Domain', 'Contract #', 'Status' ]; - helper.column_fields = [ 'id', 'username', 'domain_name', 'contract_id', 'status' ]; - helper.column_sort = 'status'; + #helper.column_titles = [ '#', 'Username', 'Domain', 'Contract #', 'Status' ]; + #helper.column_fields = [ 'id', 'username', 'domain_name', 'contract_id', 'status' ]; + #helper.column_sort = 'status'; + helper.column_titles = []; + helper.column_fields = []; + + FOR col IN dt_columns; + NEXT UNLESS col.title; + helper.column_titles.push(col.title); + helper.column_fields.push(col.accessor); + END; helper.close_target = close_target; helper.create_flag = create_flag; helper.edit_flag = edit_flag; helper.form_object = form; - helper.ajax_uri = c.uri_for( c.controller.action_for('ajax') ); + helper.ajax_uri = c.uri_for_action('/subscriber/ajax'); helper.dt_buttons = [ { name = 'Terminate', uri = "/subscriber/'+full.id+'/terminate", class = 'btn-small btn-secondary', icon = 'icon-trash', condition = 'full.status != "terminated"' },