From 4438756017eb637da93008854b2cae71a471668b Mon Sep 17 00:00:00 2001 From: Rene Krenn Date: Mon, 7 Mar 2016 11:46:30 +0100 Subject: [PATCH] MT#18395 lazy load dashboard values csc dashboard recent calls cover last 24h Change-Id: I19704643203795949c3e46b44fd136a59f83ac2b --- lib/NGCP/Panel.pm | 2 +- lib/NGCP/Panel/Controller/Dashboard.pm | 26 +- lib/NGCP/Panel/Controller/Root.pm | 18 +- lib/NGCP/Panel/Widget.pm | 4 +- .../Widget/Plugin/AdminBillingOverview.pm | 101 +++++- .../Widget/Plugin/AdminPeeringOverview.pm | 52 ++- .../Widget/Plugin/AdminResellerOverview.pm | 68 +++- .../Widget/Plugin/ResellerBillingOverview.pm | 91 ++++-- .../Widget/Plugin/ResellerCustomerOverview.pm | 59 +++- .../Widget/Plugin/ResellerDomainOverview.pm | 54 +++- .../Widget/Plugin/SubscriberCFOverview.pm | 76 +++-- .../Widget/Plugin/SubscriberCallsOverview.pm | 88 +++-- .../Plugin/SubscriberRegisterOverview.pm | 52 ++- .../Widget/Plugin/SubscriberVmOverview.pm | 68 +++- share/static/css/application.css | 49 +-- share/static/img/loader_transparent.gif | Bin 0 -> 771 bytes share/static/js/jquery.ajaxq.js | 301 ++++++++++++++++++ share/static/js/sprintf.js | 134 ++++++++ share/templates/dashboard.tt | 26 +- .../widgets/admin_billing_overview.tt | 44 ++- .../widgets/admin_peering_overview.tt | 37 ++- .../widgets/admin_reseller_overview.tt | 44 ++- .../widgets/reseller_billing_overview.tt | 39 ++- .../widgets/reseller_customer_overview.tt | 37 ++- .../widgets/reseller_domain_overview.tt | 37 ++- .../widgets/subscriber_calls_overview.tt | 95 +++--- .../widgets/subscriber_cf_overview.tt | 56 ++-- .../widgets/subscriber_reg_overview.tt | 63 ++-- .../widgets/subscriber_vm_overview.tt | 70 ++-- 29 files changed, 1451 insertions(+), 340 deletions(-) create mode 100644 share/static/img/loader_transparent.gif create mode 100644 share/static/js/jquery.ajaxq.js create mode 100644 share/static/js/sprintf.js diff --git a/lib/NGCP/Panel.pm b/lib/NGCP/Panel.pm index 551a07f50a..2627f02867 100644 --- a/lib/NGCP/Panel.pm +++ b/lib/NGCP/Panel.pm @@ -73,7 +73,7 @@ __PACKAGE__->config( 'View::JSON' => { #Set the stash keys to be exposed to a JSON response #(sEcho iTotalRecords iTotalDisplayRecords aaData) for datatables - expose_stash => [ qw(sEcho iTotalRecords iTotalDisplayRecords aaData dt_custom_footer) ], + expose_stash => [ qw(sEcho iTotalRecords iTotalDisplayRecords aaData dt_custom_footer widget_data) ], }, 'View::TT' => { INCLUDE_PATH => [ diff --git a/lib/NGCP/Panel/Controller/Dashboard.pm b/lib/NGCP/Panel/Controller/Dashboard.pm index 48050d1dc7..29cc7c8ecf 100644 --- a/lib/NGCP/Panel/Controller/Dashboard.pm +++ b/lib/NGCP/Panel/Controller/Dashboard.pm @@ -29,8 +29,8 @@ sub index :Path :Args(0) { my $widget_templates = []; foreach($plugin_finder->instantiate_plugins($c, 'dashboard_widgets')) { - $_->handle($c); - push @{ $widget_templates }, $_->template; + $_->{instance}->handle($c); #prepare stash for values rendered by tt + push @{ $widget_templates }, $_->{instance}->template; } $c->stash(widgets => $widget_templates); @@ -38,6 +38,28 @@ sub index :Path :Args(0) { delete $c->session->{redirect_targets}; } +sub ajax :Path('ajax') :Args(1) { + my ($self, $c, $exec) = @_; + + my $combined_plugin = NGCP::Panel::Widget->new; + + foreach($combined_plugin->instantiate_plugins($c, 'dashboard_widgets')) { + #$_->{instance}->handle($c); + $combined_plugin->load_plugin($_->{name}); + } + + my $value = undef; + eval { + $value = $combined_plugin->$exec($c); + }; + if ($@) { + $c->log->debug("error processing widget ajax request '$exec': " . $@); + } + $c->stash(widget_data => $value); + + $c->detach( $c->view("JSON") ); +} + =head1 AUTHOR Andreas Granig,,, diff --git a/lib/NGCP/Panel/Controller/Root.pm b/lib/NGCP/Panel/Controller/Root.pm index ae1a43157f..e786e46206 100644 --- a/lib/NGCP/Panel/Controller/Root.pm +++ b/lib/NGCP/Panel/Controller/Root.pm @@ -64,10 +64,10 @@ sub auto :Private { } unless($c->user_exists) { - + if(index($c->controller->catalyst_component_name, 'NGCP::Panel::Controller::API') == 0) { $c->log->debug("++++++ Root::auto unauthenticated API request"); - my $ssl_dn = $c->request->env->{SSL_CLIENT_M_DN} // ""; + my $ssl_dn = $c->request->env->{SSL_CLIENT_M_DN} // ""; my $ssl_sn = hex ($c->request->env->{SSL_CLIENT_M_SERIAL} // 0); if($ssl_sn) { $c->log->debug("++++++ Root::auto API request with client auth sn '$ssl_sn'"); @@ -81,7 +81,7 @@ sub auto :Private { return; } - my $res = $c->authenticate({ + my $res = $c->authenticate({ ssl_client_m_serial => $ssl_sn, is_active => 1, # TODO: abused as password until NoPassword handler is available }, 'api_admin_cert'); @@ -152,7 +152,7 @@ sub auto :Private { $c->response->status(403); return; } - + # store uri for redirect after login my $target = undef; if($c->request->method eq 'GET') { @@ -186,8 +186,8 @@ sub auto :Private { my $plugin_finder = NGCP::Panel::Widget->new; my $topmenu_templates = []; foreach($plugin_finder->instantiate_plugins($c, 'topmenu_widgets')) { - $_->handle($c); - push @{ $topmenu_templates }, $_->template; + $_->{instance}->handle($c); + push @{ $topmenu_templates }, $_->{instance}->template; } $c->stash(topmenu => $topmenu_templates); @@ -263,7 +263,7 @@ sub _prune_row { sub error_page :Private { my ($self,$c) = @_; $c->log->error( 'Failed to find path ' . $c->request->path ); - + if($c->request->path =~ /^api\/.+/) { $c->response->content_type('application/json'); $c->response->body(JSON::to_json({ @@ -278,7 +278,7 @@ sub error_page :Private { sub denied_page :Private { my ($self,$c) = @_; - + $c->log->error('Access denied to path ' . $c->request->path ); if($c->request->path =~ /^api\/.+/) { $c->response->content_type('application/json'); @@ -321,7 +321,7 @@ sub api_apply_fake_time :Private { } NGCP::Panel::Utils::DateTime::set_fake_time(); $c->stash->{is_fake_time} = 0; - #$c->log->debug('resetting faked system time: ' . NGCP::Panel::Utils::DateTime::to_string(NGCP::Panel::Utils::DateTime::current_local)); + #$c->log->debug('resetting faked system time: ' . NGCP::Panel::Utils::DateTime::to_string(NGCP::Panel::Utils::DateTime::current_local)); } } diff --git a/lib/NGCP/Panel/Widget.pm b/lib/NGCP/Panel/Widget.pm index 2a98be47c3..858e30f167 100644 --- a/lib/NGCP/Panel/Widget.pm +++ b/lib/NGCP/Panel/Widget.pm @@ -22,10 +22,10 @@ sub instantiate_plugins { my $inst = NGCP::Panel::Widget->new; $inst->load_plugin($_); if($inst->filter($c, $type_filter)) { - push @instances, $inst; + push @instances, { instance => $inst, name => $_ }; } } - return sort {$a->priority > $b->priority} @instances; + return sort {$a->{instance}->priority > $b->{instance}->priority} @instances; } no Moose; diff --git a/lib/NGCP/Panel/Widget/Plugin/AdminBillingOverview.pm b/lib/NGCP/Panel/Widget/Plugin/AdminBillingOverview.pm index b25598eb16..e2aee0fad6 100644 --- a/lib/NGCP/Panel/Widget/Plugin/AdminBillingOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/AdminBillingOverview.pm @@ -22,14 +22,49 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; + + # add queries used in tt here ... + + return; +}; + +sub filter { + my ($self, $c, $type) = @_; + + return $self if( + $type eq $self->type && + $c->user->roles eq 'admin' && + ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' + ); + return; +} + +sub _get_interval { + my $self = shift; my $stime = NGCP::Panel::Utils::DateTime::current_local->truncate(to => 'month'); my $etime = $stime->clone->add(months => 1); + return ($stime,$etime); +} + +sub _prepare_profiles_count { + my ($self, $c) = @_; - #how to catchup contract balances of all contracts here? $c->stash( profiles => $c->model('DB')->resultset('billing_profiles')->search_rs({ status => { '!=' => 'terminated'}, }), + ); + +} + +sub _prepare_peering_sum { + my ($self, $c) = @_; + + # how to catchup contract balances of all contracts here? + # well, we don't care for a stats view ... + + my ($stime,$etime) = $self->_get_interval(); + $c->stash( peering_sum => $c->model('DB')->resultset('contract_balances')->search_rs({ 'start' => { '>=' => $stime }, 'end' => { '<' => $etime}, @@ -40,7 +75,18 @@ around handle => sub { alias => 'myinner', join => 'product', })->as_query, - })->get_column('cash_balance_interval')->sum, + })->get_column('cash_balance_interval'), + ); +} + +sub _prepare_reseller_sum { + my ($self, $c) = @_; + + # how to catchup contract balances of all contracts here? + # well, we don't care for a stats view ... + + my ($stime,$etime) = $self->_get_interval(); + $c->stash( reseller_sum => $c->model('DB')->resultset('contract_balances')->search_rs({ 'start' => { '>=' => $stime }, 'end' => { '<' => $etime}, @@ -51,7 +97,18 @@ around handle => sub { alias => 'myinner', join => 'product', })->as_query, - })->get_column('cash_balance_interval')->sum, + })->get_column('cash_balance_interval'), + ); +} + +sub _prepare_customer_sum { + my ($self, $c) = @_; + + # how to catchup contract balances of all contracts here? + # well, we don't care for a stats view ... + + my ($stime,$etime) = $self->_get_interval(); + $c->stash( customer_sum => $c->model('DB')->resultset('contract_balances')->search_rs({ 'start' => { '>=' => $stime }, 'end' => { '<' => $etime}, @@ -62,20 +119,36 @@ around handle => sub { alias => 'myinner', join => 'product', })->as_query, - })->get_column('cash_balance_interval')->sum, + })->get_column('cash_balance_interval'), ); - return; -}; +} -sub filter { - my ($self, $c, $type) = @_; +sub profiles_count { + my ($self, $c) = @_; - return $self if( - $type eq $self->type && - $c->user->roles eq 'admin' && - ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' - ); - return; + $self->_prepare_profiles_count($c); + return $c->stash->{profiles}->count; +} + +sub peering_sum { + my ($self, $c) = @_; + + $self->_prepare_peering_sum($c); + return $c->stash->{peering_sum}->sum; +} + +sub reseller_sum { + my ($self, $c) = @_; + + $self->_prepare_reseller_sum($c); + return $c->stash->{reseller_sum}->sum; +} + +sub customer_sum { + my ($self, $c) = @_; + + $self->_prepare_customer_sum($c); + return $c->stash->{customer_sum}->sum; } 1; diff --git a/lib/NGCP/Panel/Widget/Plugin/AdminPeeringOverview.pm b/lib/NGCP/Panel/Widget/Plugin/AdminPeeringOverview.pm index c9379ff46f..e88c58e1d1 100644 --- a/lib/NGCP/Panel/Widget/Plugin/AdminPeeringOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/AdminPeeringOverview.pm @@ -22,15 +22,7 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; - my $peer_groups = $c->model('DB')->resultset('voip_peer_groups')->search_rs({}); - my $peer_hosts = $peer_groups->search_related_rs('voip_peer_hosts'); - my $peer_rules = $peer_groups->search_related_rs('voip_peer_rules'); - - $c->stash( - groups => $peer_groups, - hosts => $peer_hosts, - rules => $peer_rules, - ); + # add queries used in tt here ... return; }; @@ -46,5 +38,47 @@ sub filter { return; } +sub _prepare_peer_groups_count { + my ($self, $c) = @_; + my $peer_groups = $c->model('DB')->resultset('voip_peer_groups')->search_rs({}); + $c->stash( + groups => $peer_groups, + ); +} + +sub _prepare_hosts_count { + my ($self, $c) = @_; + $self->_prepare_peer_groups_count($c); + $c->stash( + hosts => $c->stash->{groups}->search_related_rs('voip_peer_hosts'), + ); +} + +sub _prepare_rules_count { + my ($self, $c) = @_; + $self->_prepare_peer_groups_count($c); + $c->stash( + rules => $c->stash->{groups}->search_related_rs('voip_peer_rules'), + ); +} + +sub groups_count { + my ($self, $c) = @_; + $self->_prepare_peer_groups_count($c); + return $c->stash->{groups}->count; +} + +sub hosts_count { + my ($self, $c) = @_; + $self->_prepare_hosts_count($c); + return $c->stash->{hosts}->count; +} + +sub rules_count { + my ($self, $c) = @_; + $self->_prepare_rules_count($c); + return $c->stash->{rules}->count; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Widget/Plugin/AdminResellerOverview.pm b/lib/NGCP/Panel/Widget/Plugin/AdminResellerOverview.pm index 2bdaf6fc45..9afbf07209 100644 --- a/lib/NGCP/Panel/Widget/Plugin/AdminResellerOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/AdminResellerOverview.pm @@ -22,33 +22,81 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; + # add queries used in tt here ... + + return; +}; + +sub filter { + my ($self, $c, $type) = @_; + + return $self if( + $type eq $self->type && + $c->user->roles eq 'admin' && + ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' + ); + return; +} + +sub _prepare_resellers_count { + my ($self, $c) = @_; $c->stash( resellers => $c->model('DB')->resultset('resellers')->search_rs({ status => { '!=' => 'terminated' }, }), + ); +} + +sub _prepare_domains_count { + my ($self, $c) = @_; + $c->stash( domains => $c->model('DB')->resultset('domain_resellers')->search_rs({}), + ); +} + +sub _prepare_customers_count { + my ($self, $c) = @_; + $c->stash( customers => $c->model('DB')->resultset('contracts')->search_rs({ status => { '!=' => 'terminated' }, 'product.class' => { 'not in' => [ 'reseller', 'sippeering', 'pstnpeering' ] }, }, { join => { 'billing_mappings' => 'product' }, }), + ); +} + +sub _prepare_subscribers_count { + my ($self, $c) = @_; + $c->stash( subscribers => $c->model('DB')->resultset('voip_subscribers')->search_rs({ status => { '!=' => 'terminated' }, }), ); - return; -}; +} -sub filter { - my ($self, $c, $type) = @_; +sub resellers_count { + my ($self, $c) = @_; + $self->_prepare_resellers_count($c); + return $c->stash->{resellers}->count; +} - return $self if( - $type eq $self->type && - $c->user->roles eq 'admin' && - ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' - ); - return; +sub domains_count { + my ($self, $c) = @_; + $self->_prepare_domains_count($c); + return $c->stash->{domains}->count; +} + +sub customers_count { + my ($self, $c) = @_; + $self->_prepare_customers_count($c); + return $c->stash->{customers}->count; +} + +sub subscribers_count { + my ($self, $c) = @_; + $self->_prepare_subscribers_count($c); + return $c->stash->{subscribers}->count; } 1; diff --git a/lib/NGCP/Panel/Widget/Plugin/ResellerBillingOverview.pm b/lib/NGCP/Panel/Widget/Plugin/ResellerBillingOverview.pm index 70433e6948..181e18c686 100644 --- a/lib/NGCP/Panel/Widget/Plugin/ResellerBillingOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/ResellerBillingOverview.pm @@ -22,22 +22,81 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; + + # add queries used in tt here ... + + return; +}; + +sub _get_interval { + my $self = shift; my $stime = NGCP::Panel::Utils::DateTime::current_local->truncate(to => 'month'); my $etime = $stime->clone->add(months => 1); + return ($stime,$etime); +} + +sub filter { + my ($self, $c, $type) = @_; + + return $self if( + $type eq $self->type && + $c->user->roles eq 'reseller' && + ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' + ); + return; +} + +sub _get_reseller { + my ($self, $c) = @_; + return $c->model('DB')->resultset('resellers')->find($c->user->reseller_id); +} + +sub _prepare_reseller_profiles_count { + my ($self, $c) = @_; + my $reseller = $self->_get_reseller($c); + $c->stash( + profiles => $reseller->billing_profiles, + ); +} - my $reseller = $c->model('DB')->resultset('resellers')->find($c->user->reseller_id); - my $reseller_balance = $reseller->contract->contract_balances->search({ - 'start' => { '>=' => $stime }, - 'end' => { '<' => $etime}, - })->first; +sub _prepare_reseller_balance { + my ($self, $c) = @_; + my $reseller = $self->_get_reseller($c); + my ($stime,$etime) = $self->_get_interval(); + $c->stash( + balance => $reseller->contract->contract_balances->search({ + 'start' => { '>=' => $stime }, + 'end' => { '<' => $etime}, + }), + ); +} + +sub profiles_count { + my ($self, $c) = @_; + + $self->_prepare_reseller_profiles_count($c); + return $c->stash->{profiles}->count; +} + +sub reseller_sum { + my ($self, $c) = @_; + $self->_prepare_reseller_balance($c); + my $reseller_balance = $c->stash->{balance}->first; my $reseller_sum = 0; if($reseller_balance) { $reseller_sum = $reseller_balance->cash_balance_interval; } + return $reseller_sum; +} + +sub _prepare_customer_sum { + my ($self, $c) = @_; + my ($stime,$etime) = $self->_get_interval(); + # how to catchup contract balances of all contracts here? + # well, we don't care for a stats view ... + $c->stash( - profiles => $reseller->billing_profiles, - reseller_sum => $reseller_sum, customer_sum => $c->model('DB')->resultset('contract_balances')->search_rs({ 'start' => { '>=' => $stime }, 'end' => { '<' => $etime}, @@ -46,20 +105,14 @@ around handle => sub { join => { 'contract' => 'contact', }, - })->get_column('cash_balance_interval')->sum, + })->get_column('cash_balance_interval'), ); - return; -}; - -sub filter { - my ($self, $c, $type) = @_; +} - return $self if( - $type eq $self->type && - $c->user->roles eq 'reseller' && - ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' - ); - return; +sub customer_sum { + my ($self, $c) = @_; + $self->_prepare_customer_sum($c); + return $c->stash->{customer_sum}->sum; } 1; diff --git a/lib/NGCP/Panel/Widget/Plugin/ResellerCustomerOverview.pm b/lib/NGCP/Panel/Widget/Plugin/ResellerCustomerOverview.pm index 4c1b8846ae..fadc50ed39 100644 --- a/lib/NGCP/Panel/Widget/Plugin/ResellerCustomerOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/ResellerCustomerOverview.pm @@ -22,7 +22,24 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; - my $reseller = $c->model('DB')->resultset('resellers')->find($c->user->reseller_id); + # add queries used in tt here ... + + return; +}; + +sub filter { + my ($self, $c, $type) = @_; + + return $self if( + $type eq $self->type && + $c->user->roles eq 'reseller' && + ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' + ); + return; +} + +sub _prepare_customers_count { + my ($self, $c) = @_; $c->stash( customers => $c->model('DB')->resultset('contracts')->search({ @@ -32,28 +49,48 @@ around handle => sub { },{ join => [ 'contact', { 'billing_mappings' => 'product' } ], }), + ); +} + +sub _prepare_subscribers_count { + my ($self, $c) = @_; + + $c->stash( subscribers => $c->model('DB')->resultset('voip_subscribers')->search({ 'contact.reseller_id' => $c->user->reseller_id, 'me.status' => { '!=' => 'terminated' }, },{ join => { 'contract' => 'contact'}, }), + ); +} + +sub _prepare_contacts_count { + my ($self, $c) = @_; + + $c->stash( contacts => $c->model('DB')->resultset('contacts')->search({ reseller_id => $c->user->reseller_id, }), ); - return; -}; +} -sub filter { - my ($self, $c, $type) = @_; +sub customers_count { + my ($self, $c) = @_; + $self->_prepare_customers_count($c); + return $c->stash->{customers}->count; +} - return $self if( - $type eq $self->type && - $c->user->roles eq 'reseller' && - ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' - ); - return; +sub subscribers_count { + my ($self, $c) = @_; + $self->_prepare_subscribers_count($c); + return $c->stash->{subscribers}->count; +} + +sub contacts_count { + my ($self, $c) = @_; + $self->_prepare_contacts_count($c); + return $c->stash->{contacts}->count; } 1; diff --git a/lib/NGCP/Panel/Widget/Plugin/ResellerDomainOverview.pm b/lib/NGCP/Panel/Widget/Plugin/ResellerDomainOverview.pm index f772c932d3..ec4ea6e37a 100644 --- a/lib/NGCP/Panel/Widget/Plugin/ResellerDomainOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/ResellerDomainOverview.pm @@ -22,13 +22,8 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; - my $reseller = $c->model('DB')->resultset('resellers')->find($c->user->reseller_id); + # add queries used in tt here ... - $c->stash( - domains => $reseller->domain_resellers, - rwr_sets => $reseller->voip_rewrite_rule_sets, - sound_sets => $reseller->voip_sound_sets, - ); return; }; @@ -43,5 +38,52 @@ sub filter { return; } +sub _get_reseller { + my ($self, $c) = @_; + return $c->model('DB')->resultset('resellers')->find($c->user->reseller_id); +} + +sub _prepare_domains_count { + my ($self, $c) = @_; + my $reseller = $self->_get_reseller($c); + $c->stash( + domains => $reseller->domain_resellers, + ); +} + +sub _prepare_rwr_sets_count { + my ($self, $c) = @_; + my $reseller = $self->_get_reseller($c); + $c->stash( + rwr_sets => $reseller->voip_rewrite_rule_sets, + ); +} + +sub _prepare_sound_sets_count { + my ($self, $c) = @_; + my $reseller = $self->_get_reseller($c); + $c->stash( + sound_sets => $reseller->voip_sound_sets, + ); +} + +sub domains_count { + my ($self, $c) = @_; + $self->_prepare_domains_count($c); + return $c->stash->{domains}->count; +} + +sub rwr_sets_count { + my ($self, $c) = @_; + $self->_prepare_rwr_sets_count($c); + return $c->stash->{rwr_sets}->count; +} + +sub sound_sets_count { + my ($self, $c) = @_; + $self->_prepare_sound_sets_count($c); + return $c->stash->{sound_sets}->count; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Widget/Plugin/SubscriberCFOverview.pm b/lib/NGCP/Panel/Widget/Plugin/SubscriberCFOverview.pm index 896945173b..e70501ea2d 100644 --- a/lib/NGCP/Panel/Widget/Plugin/SubscriberCFOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/SubscriberCFOverview.pm @@ -22,44 +22,76 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; + unless ($c->stash->{subscriber}) { + $c->stash( + subscriber => $c->model('DB')->resultset('voip_subscribers')->find({ + uuid => $c->user->uuid, + }), + ); + } + + return; +}; + +sub filter { + my ($self, $c, $type) = @_; + + return $self if( + $type eq $self->type && + ($c->user->roles eq 'subscriber' || $c->user->roles eq 'subscriberadmin') && + ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' + ); + return; +} + +sub _get_cf_type_descriptions { + my ($self, $c) = @_; + return { cfu => $c->loc("Call Forward Unconditional"), + cfb => $c->loc("Call Forward Busy"), + cft => $c->loc("Call Forward Timeout"), + cfna => $c->loc("Call Forward Unavailable") }; +} + +sub cfs { + my ($self, $c) = @_; my $prov_subscriber = $c->user; my $cfs = {}; + my $descriptions = $self->_get_cf_type_descriptions($c); - foreach my $type(qw/cfu cfna cft cfb/) { + foreach my $type (qw/cfu cfna cft cfb/) { my $maps = $prov_subscriber->voip_cf_mappings ->search({ type => $type }); - $cfs->{$type} = []; - foreach my $map($maps->all) { + my @mappings = (); + foreach my $map ($maps->all) { my @dset = map { { $_->get_columns } } $map->destination_set->voip_cf_destinations->search({}, { order_by => { -asc => 'priority' }})->all; - foreach my $d(@dset) { - $d->{as_string} = NGCP::Panel::Utils::Subscriber::destination_as_string($c, $d); + foreach my $d (@dset) { + my $as_string = NGCP::Panel::Utils::Subscriber::destination_as_string($c, $d); + delete @$d{keys %$d}; + $d->{as_string} = $as_string; } my @tset = (); - if($map->time_set) { + if ($map->time_set) { @tset = map { { $_->get_columns } } $map->time_set->voip_cf_periods->all; - foreach my $t(@tset) { - $t->{as_string} = NGCP::Panel::Utils::Subscriber::period_as_string($t); + foreach my $t (@tset) { + my $as_string = NGCP::Panel::Utils::Subscriber::period_as_string($t); + delete @$t{keys %$t}; + $t->{as_string} = $as_string; } } - push @{ $cfs->{$type} }, { destinations => \@dset, periods => \@tset }; + push @mappings, { + destinations => \@dset, + periods => \@tset, + }; } + $cfs->{$type} = { + mappings => \@mappings, + desc => $descriptions->{$type}, + }; } - $c->stash(cf_destinations => $cfs); - - return; -}; -sub filter { - my ($self, $c, $type) = @_; - - return $self if( - $type eq $self->type && - ($c->user->roles eq 'subscriber' || $c->user->roles eq 'subscriberadmin') && - ref $c->controller eq 'NGCP::Panel::Controller::Dashboard' - ); - return; + return $cfs; } 1; diff --git a/lib/NGCP/Panel/Widget/Plugin/SubscriberCallsOverview.pm b/lib/NGCP/Panel/Widget/Plugin/SubscriberCallsOverview.pm index ef5aa5b704..216a45e2d6 100644 --- a/lib/NGCP/Panel/Widget/Plugin/SubscriberCallsOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/SubscriberCallsOverview.pm @@ -1,6 +1,10 @@ package NGCP::Panel::Widget::Plugin::SubscriberCallsOverview; use Moose::Role; +use NGCP::Panel::Utils::DateTime; +use DateTime::Format::Strptime; +use URI::Escape; + has 'template' => ( is => 'ro', isa => 'Str', @@ -22,28 +26,14 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; - my $out_rs = $c->model('DB')->resultset('cdr')->search({ - source_user_id => $c->user->uuid, - }); - my $in_rs = $c->model('DB')->resultset('cdr')->search({ - destination_user_id => $c->user->uuid, - }); - my $calls_rs = $out_rs->union_all($in_rs)->search(undef, { - order_by => { -desc => 'me.start_time' }, - })->slice(0, 4); + unless ($c->stash->{subscriber}) { + $c->stash( + subscriber => $c->model('DB')->resultset('voip_subscribers')->find({ + uuid => $c->user->uuid, + }), + ); + } - my $sub = $c->user->voip_subscriber; - my $calls = [ map { - my $call = { $_->get_inflated_columns }; - $call->{destination_user_in} = NGCP::Panel::Utils::Subscriber::apply_rewrite( - c => $c, subscriber => $sub, number => $call->{destination_user_in}, direction => 'caller_out' - ); - $call->{source_cli} = NGCP::Panel::Utils::Subscriber::apply_rewrite( - c => $c, subscriber => $sub, number => $call->{source_cli}, direction => 'caller_out' - ); - $call; - } $calls_rs->all ]; - $c->stash(calls => $calls); return; }; @@ -58,5 +48,61 @@ sub filter { return; } +sub _prepare_calls { + my ($self, $c) = @_; + + my $out_rs = $c->model('DB')->resultset('cdr')->search({ + source_user_id => $c->user->uuid, + }); + my $in_rs = $c->model('DB')->resultset('cdr')->search({ + destination_user_id => $c->user->uuid, + }); + my $calls_rs = $out_rs->union_all($in_rs); + + $c->stash(calls_rs => $calls_rs); + +} + +sub calls_count { + my ($self, $c) = @_; + $self->_prepare_calls($c); + + my $stime = NGCP::Panel::Utils::DateTime::current_local->subtract(hours => 24); + + $c->stash->{calls_rs}->search({ + start_time => { '>=' => $stime->epoch } + })->count; +} + +sub calls_slice { + my ($self, $c) = @_; + $self->_prepare_calls($c); + my $sub = $c->user->voip_subscriber; + my $datetime_fmt = DateTime::Format::Strptime->new( + pattern => '%F %T', + ); + return [ map { + my $call = { $_->get_inflated_columns }; + my %resource = (); + $resource{destination_user_in} = URI::Escape::uri_unescape( + NGCP::Panel::Utils::Subscriber::apply_rewrite( + c => $c, subscriber => $sub, number => $call->{destination_user_in}, direction => 'caller_out' + ) + ); + $resource{source_cli} = ($call->{clir} ? $c->loc('anonymous') : URI::Escape::uri_unescape( + NGCP::Panel::Utils::Subscriber::apply_rewrite( + c => $c, subscriber => $sub, number => $call->{source_cli}, direction => 'caller_out' + ) + )); + $resource{call_status} = $call->{call_status}; + $resource{source_user_id} = $call->{source_user_id}; + $resource{start_time} = $datetime_fmt->format_datetime($call->{start_time}); + $resource{duration} = $call->{duration}; + \%resource; + } $c->stash->{calls_rs}->search(undef, { + order_by => { -desc => 'me.start_time' }, + })->slice(0, 4)->all ]; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Widget/Plugin/SubscriberRegisterOverview.pm b/lib/NGCP/Panel/Widget/Plugin/SubscriberRegisterOverview.pm index b4b7eae26f..eeb4cbf2fc 100644 --- a/lib/NGCP/Panel/Widget/Plugin/SubscriberRegisterOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/SubscriberRegisterOverview.pm @@ -22,21 +22,14 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; - my $rs = $c->model('DB')->resultset('location')->search({ - username => $c->user->username, - }); - if($c->config->{features}->{multidomain}) { - $rs = $rs->search({ - domain => $c->user->domain->domain, - }); + unless ($c->stash->{subscriber}) { + $c->stash( + subscriber => $c->model('DB')->resultset('voip_subscribers')->find({ + uuid => $c->user->uuid, + }), + ); } - my $reg_count = $rs->count; - $rs = $rs->slice(0,4); - $c->stash( - regs => $rs, - reg_count => $reg_count, - ); return; }; @@ -51,5 +44,38 @@ sub filter { return; } +sub _prepare_registrations { + my ($self, $c) = @_; + + my $rs = $c->model('DB')->resultset('location')->search({ + username => $c->user->username, + }); + if($c->config->{features}->{multidomain}) { + $rs = $rs->search({ + domain => $c->user->domain->domain, + }); + } + + $c->stash(registrations => $rs); + +} + +sub registrations_slice { + my ($self, $c) = @_; + $self->_prepare_registrations($c); + return [ map { + my $registration = { $_->get_inflated_columns }; + my %resource = (); + $resource{user_agent} = $registration->{user_agent}; + \%resource; + } $c->stash->{registrations}->slice(0,4)->all ]; +} + +sub registrations_count { + my ($self, $c) = @_; + $self->_prepare_registrations($c); + return $c->stash->{registrations}->count; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Widget/Plugin/SubscriberVmOverview.pm b/lib/NGCP/Panel/Widget/Plugin/SubscriberVmOverview.pm index c0ec641168..9d03ce1234 100644 --- a/lib/NGCP/Panel/Widget/Plugin/SubscriberVmOverview.pm +++ b/lib/NGCP/Panel/Widget/Plugin/SubscriberVmOverview.pm @@ -1,6 +1,8 @@ package NGCP::Panel::Widget::Plugin::SubscriberVmOverview; use Moose::Role; +use DateTime::Format::Strptime; + has 'template' => ( is => 'ro', isa => 'Str', @@ -22,21 +24,14 @@ has 'priority' => ( around handle => sub { my ($foo, $self, $c) = @_; - my $sub = $c->model('DB')->resultset('voip_subscribers')->find({ - uuid => $c->user->uuid, - }); - my $rs = $c->model('DB')->resultset('voicemail_spool')->search({ - mailboxuser => $c->user->uuid, - msgnum => { '>=' => 0 }, - dir => { -like => '%/INBOX' }, - }, { - order_by => { -desc => 'me.origtime' }, - })->slice(0, 4); + unless ($c->stash->{subscriber}) { + $c->stash( + subscriber => $c->model('DB')->resultset('voip_subscribers')->find({ + uuid => $c->user->uuid, + }), + ); + } - $c->stash( - subscriber => $sub, - vmails => $rs, - ); return; }; @@ -51,5 +46,50 @@ sub filter { return; } +sub _prepare_voicemails { + my ($self, $c) = @_; + + # limited by asterisk.voicemail.maxmsg in config.yml + my $rs = $c->model('DB')->resultset('voicemail_spool')->search({ + mailboxuser => $c->user->uuid, + msgnum => { '>=' => 0 }, + dir => { -like => '%/INBOX' }, + }); + + $c->stash( + voicemails => $rs, + ); + +} + +sub voicemails_count { + my ($self, $c) = @_; + $self->_prepare_voicemails($c); + return $c->stash->{voicemails}->count; +} + +sub voicemails_slice { + my ($self, $c) = @_; + $self->_prepare_voicemails($c); + my $sub = $c->model('DB')->resultset('voip_subscribers')->find({ + uuid => $c->user->uuid, + }); + my $datetime_fmt = DateTime::Format::Strptime->new( + pattern => '%F %T', + ); + return [ map { + #my $voicemail= { $_->get_inflated_columns }; + #avoid loading the blob here! + my %resource = (); + $resource{play_uri} = $c->uri_for_action('/subscriber/play_voicemail', [$sub->id, $_->id])->as_string; + $resource{callerid} = $_->callerid; + $resource{origtime} = $datetime_fmt->format_datetime($_->origtime); + $resource{duration} = $_->duration; + \%resource; + } $c->stash->{voicemails}->search(undef,{ + order_by => { -desc => 'me.origtime' }, + })->slice(0, 4)->all ]; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/share/static/css/application.css b/share/static/css/application.css index 33d5a54ffe..41c8065d5a 100644 --- a/share/static/css/application.css +++ b/share/static/css/application.css @@ -4,7 +4,7 @@ Project: Dashboard Version: 1.0 - Last change: 10/10/2012 + Last change: 10/10/2012 -------------------------------------------------------------------*/ @@ -127,7 +127,7 @@ ul.list li { background-image: none; } /*------------------------------------------------------------------ - + Bootstrap Styles ------------------------------------------------------------------- */ @@ -818,7 +818,7 @@ body:not(:-moz-handler-blocked) .dropdown-submenu > a:after { background-color: #999999; } /*------------------------------------------------------------------ - + jQuery UI Styles ------------------------------------------------------------------- */ @@ -1040,7 +1040,7 @@ jQuery UI Styles opacity: .5; } /*------------------------------------------------------------------ - + Signin ------------------------------------------------------------------- */ @@ -1227,7 +1227,7 @@ span.login-checkbox > input[type='checkbox']:checked + label { } } /*------------------------------------------------------------------ - + Validation Styles ------------------------------------------------------------------- */ @@ -1283,7 +1283,7 @@ Validation Styles left: 8px; } /*------------------------------------------------------------------ - + Shortcuts ------------------------------------------------------------------- */ @@ -1341,7 +1341,7 @@ Shortcuts } } /*------------------------------------------------------------------ - + Pricing Styles ------------------------------------------------------------------- */ @@ -1464,6 +1464,15 @@ Pricing Styles .pricing-plans.plans-4 .plan-container { width: 25%; } + +.widget-loading { + color:transparent; + text-shadow: none; + background-image: url("/img/loader_transparent.gif"); + background-repeat: no-repeat; + background-position: center center; +} + /*------------------------------------------------------------------ [2. Min Width: 767px / Max Width: 979px] */ @@ -1480,7 +1489,7 @@ Pricing Styles } } /*------------------------------------------------------------------ - + Faq Styles ------------------------------------------------------------------- */ @@ -1550,7 +1559,7 @@ Faq Styles text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); } /*------------------------------------------------------------------ - + Error ------------------------------------------------------------------- */ @@ -1581,7 +1590,7 @@ Error } } /*------------------------------------------------------------------ - + Item Layout ------------------------------------------------------------------- */ @@ -1627,7 +1636,7 @@ Item Layout } } /*------------------------------------------------------------------ - + Gallery ------------------------------------------------------------------- */ @@ -1708,7 +1717,7 @@ Gallery border-radius: 3px; } /*------------------------------------------------------------------ - + Layout: Layout ------------------------------------------------------------------- */ @@ -1717,7 +1726,7 @@ body { background: #FFF; } /*------------------------------------------------------------------ - + Layout: Wrapper ------------------------------------------------------------------- */ @@ -1735,7 +1744,7 @@ body { height: 50px; } /*------------------------------------------------------------------ - + Layout: Topbar ------------------------------------------------------------------- */ @@ -1831,7 +1840,7 @@ Layout: Topbar border-top-color: #c9e3bd; } /*------------------------------------------------------------------ - + Layout: Header ------------------------------------------------------------------- */ @@ -1880,7 +1889,7 @@ Layout: Header text-decoration: none; } /*------------------------------------------------------------------ - + Layout: Main Nav ------------------------------------------------------------------- */ @@ -2013,7 +2022,7 @@ Layout: Main Nav border-top-color: #54893b; } /*------------------------------------------------------------------ - + Layout: Masthead ------------------------------------------------------------------- */ @@ -2070,7 +2079,7 @@ Layout: Masthead vertical-align: top; } /*------------------------------------------------------------------ - + Layout: Content ------------------------------------------------------------------- */ @@ -2096,7 +2105,7 @@ Layout: Content background: none; } /*------------------------------------------------------------------ - + Layout: Footer ------------------------------------------------------------------- */ @@ -2120,7 +2129,7 @@ Layout: Footer text-align: right; } /*------------------------------------------------------------------ - + Layout: Responsive ------------------------------------------------------------------- */ diff --git a/share/static/img/loader_transparent.gif b/share/static/img/loader_transparent.gif new file mode 100644 index 0000000000000000000000000000000000000000..e846e1d6c58796558015ffee1fdec546bc207ee8 GIT binary patch literal 771 zcmZ?wbhEHb6krfw*v!MQYQ=(yeQk4RPu{+D?cCXuwr^cCp}%d_ius2R?!0jBXnAQ) zOH<|l|Nj|aK=D7fpKD04vtxj(k)8oFBT!uNCkrbB0}q1^NDatX1{VJbCr|b)oWWMT zS%hVC ~NwO_yO%;SvZ5MdNYf|QNy-I*%yJaj+uTdt+qbZ z4E`Fzb8m}I&!N8OKmWEcCmrLs^Hs&3i)mt@hQVdcqghkaBs*D}tG_lKew4?rTjzIZ z9tSone1TS+TR7tu^CunG)Y7Jg#sw#)sG9C!c0I%LEzP)9;hqRf&)s$D8d5Db{TBs% zgl0~5QQ91luq4Q9tJgt4QLbaxZvAaKeCM9!oy85dg4k>TdBSVqjHub_PG=PO&J-rx z7oYTuF+kH|tG-UK+EkUhDjYx?zW?T|lx>+aOQm zzL$v$zBLo4Cj=G&tw{H}dW?tlTkS)SY4<#NS92z*EY-MMB6Ftp`R=*=*Ev7cS+X%W zMCur^FdlokL}1Y+&aasU2J4#EOuNlnb9CmqgLCGTSY!1BD42pkHY^XidQ5=>YQx%` z*%Pm9D!CkBu&tMWm(%-ejACVWGS2RX5=QOJ$1*tr7F}F+*-OA+Ly&Isg|AEuUYicA z#%IG6kPXkHt{zk2M6zK@Vu^4Q(1zE$?yY6M!^&jQ+2^E?!p7{g*|X6}vuRC3p@jk0 W117c83?+LXEZI4G$p&LV25SKE>nb+@ literal 0 HcmV?d00001 diff --git a/share/static/js/jquery.ajaxq.js b/share/static/js/jquery.ajaxq.js new file mode 100644 index 0000000000..c908d7ff33 --- /dev/null +++ b/share/static/js/jquery.ajaxq.js @@ -0,0 +1,301 @@ +;(function(root) { + 'use strict'; + + var $ = root.jQuery || root.Zepto || root.$; + + if (typeof $ === 'undefined') throw 'jquery.ajaxq requires jQuery or jQuery-compatible library (e.g. Zepto.js)'; + + /** + * @type {Function} + */ + var slice = Array.prototype.slice; + + /** + * @type {Function} + */ + var noop = function() {}; + + /** + * Copy of jQuery function + * @type {Function} + */ + var isNumeric = function(obj) { + return !$.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; + } + + /** + * @type {Function} + */ + var isObject = function(obj) { + return "[object Object]" === Object.prototype.toString.call(obj); + } + + + var Request = (function (argument) { + + function Request(url, settings) { + this._aborted = false; + this._jqXHR = null; + this._calls = {}; + this._args = [url, settings]; + this._deferred = $.Deferred(); + + this._deferred.pipe = this._deferred.then; + + this.readyState = 1; + } + + var proto = Request.prototype; + + $.extend(proto, { + + // start jqXHR by calling $.ajax + run: function() { + var + deferred = this._deferred, + methodName, argsStack, i; + + if (this._jqXHR !== null) { + return this._jqXHR; + } + // clreate new jqXHR object + var + url = this._args[0], + settings = this._args[1]; + + if (isObject(url)) { + settings = url; + } else { + settings = $.extend(true, settings || {}, { + url: url + }); + } + + this._jqXHR = $.ajax.call($, settings); + + this._jqXHR.done(function() { + deferred.resolve.apply(deferred, arguments); + }); + + this._jqXHR.fail(function() { + deferred.reject.apply(deferred, arguments); + }); + + if (this._aborted) { + this._jqXHR.abort(this.statusText); + } + + // apply buffered calls + for (methodName in this._calls) { + argsStack = this._calls[methodName]; + for (var i in argsStack) { + this._jqXHR[methodName].apply(this._jqXHR, argsStack[i]); + } + } + + return this._jqXHR; + }, + + // returns original jqXHR object if it exists + // or writes to callected method to _calls and returns itself + _call: function(methodName, args) { + if (this._jqXHR !== null) { + if (typeof this._jqXHR[methodName] === 'undefined') { + return this._jqXHR; + } + return this._jqXHR[methodName].apply(this._jqXHR, args); + } + + this._calls[methodName] = this._calls[methodName] || []; + this._calls[methodName].push(args); + + return this; + }, + + // returns original jqXHR object if it exists + // or writes to callected method to _calls and returns itself + abort: function(statusText) { + if (this._jqXHR !== null) { + var + self = this, + _copyProperties = ['readyState', 'status', 'statusText'], + _return = this._jqXHR.abort.apply(this._jqXHR, arguments) || this._jqXHR; + + if (_return) { + $.each(_copyProperties, function(i, prop) { + self[prop] = _return[prop]; + }); + } + + return _return; + } + + this.statusText = statusText || 'abort'; + this.status = 0; + this.readyState = 0; + this._aborted = true; + + return this; + }, + state: function() { + if (this._jqXHR !== null) { + return this._jqXHR.state.apply(this._jqXHR, arguments); + } + return 'pending'; + } + }); + + // each method returns self object + var _chainMethods = ['setRequestHeader', 'overrideMimeType', 'statusCode', + 'done', 'fail', 'progress', 'complete', 'success', 'error', 'always' ]; + + $.each(_chainMethods, function(i, methodName) { + proto[methodName] = function() { + return this._call(methodName, slice.call(arguments)) || this._jqXHR; + } + }); + + var _nullMethods = ['getResponseHeader', 'getAllResponseHeaders']; + + $.each(_nullMethods, function(i, methodName) { + proto[methodName] = function() { + // apply original method if _jqXHR exists + if (this._jqXHR !== null) { + return this._jqXHR[methodName].apply(this, arguments); + } + + // return null if origina method does not exists + return null; + }; + }); + + var _promiseMethods = ['pipe', 'then', 'promise']; + + $.each(_promiseMethods, function(i, methodName) { + proto[methodName] = function() { + return this._deferred[methodName].apply(this._deferred, arguments); + }; + }); + + return Request; + })() + var Queue = (function() { + + var _params = {}, _queueCounter = 0; + + function _runNext(queue, request) { + var + removeIndex = _getStarted(queue).indexOf(request), + nextRequest = _getPending(queue).shift(); + + if (removeIndex !== -1) { + _getStarted(queue).splice(removeIndex, 1); + } + + if (typeof nextRequest !== 'undefined') { + nextRequest + .always($.proxy(_runNext, null, queue, nextRequest)) + .run(); + } + } + + function _ajax(queue, request) { + if (_getStarted(queue).length < _getBandwidth(queue)) { + _getStarted(queue).push(request); + request.always($.proxy(_runNext, null, queue, request)); + request.run(); + } else { + _getPending(queue).push(request) + } + } + + function _getParams(queue) { + return _params[queue.id] || (_params[queue.id] = {}); + } + + function _getParam(queue, name) { + return _getParams(queue)[name]; + } + + function _getStarted(queue) { + return _getParams(queue).started || (_getParams(queue).started = []); + } + + function _getPending(queue) { + return _getParams(queue).pending || (_getParams(queue).pending = []); + } + + function _setBandwidth(queue, bandwidth) { + if ((bandwidth = parseInt(bandwidth || 1, 10)) < 1) throw "Bandwidth can\'t be less then 1"; + _getParams(queue).bandwidth = bandwidth; + } + + function _getBandwidth(queue, bandwidth) { + return _getParams(queue).bandwidth; + } + + function Queue(bandwidth) { + if (typeof bandwidth !== 'undefined' && !isNumeric(bandwidth)) throw "number expected"; + this.id = ++_queueCounter; + _setBandwidth(this, bandwidth); + }; + + $.extend(Queue.prototype, { + ajax: function(url, settings) { + var request = new Request(url, settings); + _ajax(this, request); + return request; + }, + getJSON: function ( url, data, callback ) { + return this.get( url, data, callback, "json" ); + }, + getBandwidth: function() { + return _getBandwidth(this); + } + }); + + $.each(['get', 'post'], function(i, method) { + Queue.prototype[method] = function( url, data, callback, type ) { + // shift arguments if data argument was omitted + if ( $.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + return this.ajax({ + url: url, + type: method, + dataType: type, + data: data, + success: callback + }); + } + }); + + return Queue; + })(); + + if (typeof $.ajaxq !== 'undefined') throw "Namespace $.ajaxq is Alread y busy."; + + var _queue = new Queue(); + + $.ajaxq = function(url, settions) { + return _queue.ajax.apply(_queue, arguments); + }; + + $.each(['get', 'post', 'getJSON'], function(i, methodName) { + $.ajaxq[methodName] = function() { + return _queue[methodName].apply(_queue, arguments); + } + }); + + $.ajaxq.Queue = function(bandwidth) { + return new Queue(bandwidth); + }; + + $.ajaxq.Request = function(url, settings) { + return new Request(url, settings); + } + +})(this); diff --git a/share/static/js/sprintf.js b/share/static/js/sprintf.js new file mode 100644 index 0000000000..b7b1f03f34 --- /dev/null +++ b/share/static/js/sprintf.js @@ -0,0 +1,134 @@ +/*! sprintf.js | Copyright (c) 2007-2013 Alexandru Marasteanu | 3 clause BSD license */ + +(function(ctx) { + var sprintf = function() { + if (!sprintf.cache.hasOwnProperty(arguments[0])) { + sprintf.cache[arguments[0]] = sprintf.parse(arguments[0]); + } + return sprintf.format.call(null, sprintf.cache[arguments[0]], arguments); + }; + + sprintf.format = function(parse_tree, argv) { + var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; + for (i = 0; i < tree_length; i++) { + node_type = get_type(parse_tree[i]); + if (node_type === 'string') { + output.push(parse_tree[i]); + } + else if (node_type === 'array') { + match = parse_tree[i]; // convenience purposes only + if (match[2]) { // keyword argument + arg = argv[cursor]; + for (k = 0; k < match[2].length; k++) { + if (!arg.hasOwnProperty(match[2][k])) { + throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); + } + arg = arg[match[2][k]]; + } + } + else if (match[1]) { // positional argument (explicit) + arg = argv[match[1]]; + } + else { // positional argument (implicit) + arg = argv[cursor++]; + } + + if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { + throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); + } + switch (match[8]) { + case 'b': arg = arg.toString(2); break; + case 'c': arg = String.fromCharCode(arg); break; + case 'd': arg = parseInt(arg, 10); break; + case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; + case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; + case 'o': arg = arg.toString(8); break; + case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; + case 'u': arg = arg >>> 0; break; + case 'x': arg = arg.toString(16); break; + case 'X': arg = arg.toString(16).toUpperCase(); break; + } + arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); + pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; + pad_length = match[6] - String(arg).length; + pad = match[6] ? str_repeat(pad_character, pad_length) : ''; + output.push(match[5] ? arg + pad : pad + arg); + } + } + return output.join(''); + }; + + sprintf.cache = {}; + + sprintf.parse = function(fmt) { + var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; + while (_fmt) { + if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { + parse_tree.push(match[0]); + } + else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { + parse_tree.push('%'); + } + else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { + if (match[2]) { + arg_names |= 1; + var field_list = [], replacement_field = match[2], field_match = []; + if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { + if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { + field_list.push(field_match[1]); + } + else { + throw('[sprintf] huh?'); + } + } + } + else { + throw('[sprintf] huh?'); + } + match[2] = field_list; + } + else { + arg_names |= 2; + } + if (arg_names === 3) { + throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); + } + parse_tree.push(match); + } + else { + throw('[sprintf] huh?'); + } + _fmt = _fmt.substring(match[0].length); + } + return parse_tree; + }; + + var vsprintf = function(fmt, argv, _argv) { + _argv = argv.slice(0); + _argv.splice(0, 0, fmt); + return sprintf.apply(null, _argv); + }; + + /** + * helpers + */ + function get_type(variable) { + return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); + } + + function str_repeat(input, multiplier) { + for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} + return output.join(''); + } + + /** + * export to either browser or node.js + */ + ctx.sprintf = sprintf; + ctx.vsprintf = vsprintf; +})(typeof exports != "undefined" ? exports : window); diff --git a/share/templates/dashboard.tt b/share/templates/dashboard.tt index ae0b45b672..60fbe59111 100644 --- a/share/templates/dashboard.tt +++ b/share/templates/dashboard.tt @@ -1,5 +1,29 @@ -[% site_config.title = c.loc('Dashboard') -%] [% wcount = widgets.size() > 4 ? 4 : widgets.size() -%] + + + + +[% site_config.title = c.loc('Dashboard') -%] +
diff --git a/share/templates/widgets/admin_billing_overview.tt b/share/templates/widgets/admin_billing_overview.tt index b84a3fd138..999571a48e 100644 --- a/share/templates/widgets/admin_billing_overview.tt +++ b/share/templates/widgets/admin_billing_overview.tt @@ -1,17 +1,43 @@ +
-
+
[% c.loc('Billing') %]
-
- [% profiles.count %] - [% profiles.count == 1 ? c.loc('Billing Profile') : c.loc('Billing Profiles') %] -
+
0processing ...
-
    -
  • [% FILTER format("%.02f") %][% (peering_sum || 0) / 100.0 %][% END %] [% c.loc('Peering Costs') %]
  • -
  • [% FILTER format("%.02f") %][% (reseller_sum || 0) / 100.0 %][% END %] [% c.loc('Reseller Revenue') %]
  • -
  • [% FILTER format("%.02f") %][% (customer_sum || 0) / 100.0 %][% END %] [% c.loc('Customer Revenue') %]
  • +
diff --git a/share/templates/widgets/admin_peering_overview.tt b/share/templates/widgets/admin_peering_overview.tt index a32e1dc919..9d4bceae91 100644 --- a/share/templates/widgets/admin_peering_overview.tt +++ b/share/templates/widgets/admin_peering_overview.tt @@ -1,16 +1,37 @@ +
-
+
[% c.loc('Peerings') %]
-
- [% groups.count %] - [% groups.count == 1 ? c.loc('Peering Group') : c.loc('Peering Groups') %] -
+
0processing ...
-
    -
  • [% hosts.count %] [% hosts.count == 1 ? c.loc('Peering Server') : c.loc('Peering Servers') %]
  • -
  • [% rules.count %] [% rules.count == 1 ? c.loc('Peering Rule') : c.loc('Peering Rules') %]
  • +
diff --git a/share/templates/widgets/admin_reseller_overview.tt b/share/templates/widgets/admin_reseller_overview.tt index e2679796b0..3f06e815ef 100644 --- a/share/templates/widgets/admin_reseller_overview.tt +++ b/share/templates/widgets/admin_reseller_overview.tt @@ -1,17 +1,43 @@ +
-
+
[% c.loc('Resellers') %]
-
- [% resellers.count %] - [% resellers.count == 1 ? c.loc('Reseller') : c.loc('Resellers') %] -
+
0processing ...
-
    -
  • [% domains.count %] [% domains.count == 1 ? c.loc('Domain') : c.loc('Domains') %]
  • -
  • [% customers.count %] [% customers.count == 1 ? c.loc('Customer') : c.loc('Customers') %]
  • -
  • [% subscribers.count %] [% subscribers.count == 1 ? c.loc('Subscriber') : c.loc('Subscribers') %]
  • +
diff --git a/share/templates/widgets/reseller_billing_overview.tt b/share/templates/widgets/reseller_billing_overview.tt index cf318724b8..adef1ea361 100644 --- a/share/templates/widgets/reseller_billing_overview.tt +++ b/share/templates/widgets/reseller_billing_overview.tt @@ -1,16 +1,37 @@ +
-
-
Billing
-
- [% profiles.count %] - [% profiles.count == 1 ? c.loc('Billing Profile') : c.loc('Billing Profiles') %] -
+
+
[% c.loc('Billing') %]
+
0processing ...
-
    -
  • [% FILTER format("%.02f") %][% (reseller_sum || 0) / 100.0 %][% END %] [% c.loc('Reseller Cost') %]
  • -
  • [% FILTER format("%.02f") %][% (customer_sum || 0) / 100.0 %][% END %] [% c.loc('Customer Revenue') %]
  • +
diff --git a/share/templates/widgets/reseller_customer_overview.tt b/share/templates/widgets/reseller_customer_overview.tt index 238d335408..ee2774be62 100644 --- a/share/templates/widgets/reseller_customer_overview.tt +++ b/share/templates/widgets/reseller_customer_overview.tt @@ -1,16 +1,37 @@ +
-
+
[% c.loc('Customers') %]
-
- [% customers.count %] - [% customers.count == 1 ? c.loc('Customer') : c.loc('Customers') %] -
+
0processing ...
-
    -
  • [% contacts.count %] [% contacts.count == 1 ? c.loc('Contact') : c.loc('Contacts') %]
  • -
  • [% subscribers.count %] [% subscribers.count == 1 ? c.loc('Subscriber') : c.loc('Subscribers') %]
  • +
diff --git a/share/templates/widgets/reseller_domain_overview.tt b/share/templates/widgets/reseller_domain_overview.tt index ae167c09fd..e9a36a3df9 100644 --- a/share/templates/widgets/reseller_domain_overview.tt +++ b/share/templates/widgets/reseller_domain_overview.tt @@ -1,16 +1,37 @@ +
-
+
[% c.loc('Domains') %]
-
- [% domains.count %] - [% domains.count == 1 ? c.loc('Domain') : c.loc('Domains') %] -
+
0processing ...
-
    -
  • [% rwr_sets.count %] [% rwr_sets.count == 1 ? c.loc('Rewrite Rule Set') : c.loc('Rewrite Rule Sets') %]
  • -
  • [% sound_sets.count %] [% sound_sets.count == 1 ? c.loc('Sound Set') : c.loc('Sound Set') %]
  • +
diff --git a/share/templates/widgets/subscriber_calls_overview.tt b/share/templates/widgets/subscriber_calls_overview.tt index 8221c3860d..aa68e2c948 100644 --- a/share/templates/widgets/subscriber_calls_overview.tt +++ b/share/templates/widgets/subscriber_calls_overview.tt @@ -1,48 +1,63 @@ -[% USE Math -%] +
-
+
[% c.loc('Call List') %]
-
- [% calls.size %] - [% calls.size != 1 ? c.loc('Recent Calls') : c.loc('Recent Call') %] -
+
0processing ...
-
    - [% IF calls.size == 0 -%] -
  • [% c.loc('No calls yet') %]
  • - [% ELSE -%] - [% FOR call IN calls -%] -
  • -
    - [% IF call.source_user_id == c.user.uuid -%] -
    - [% IF call.call_status == "ok" -%] - - [% ELSE -%] - - [% END -%] -
    -
    [% call.destination_user_in | uri_unescape %]
    - [% ELSE -%] -
    - [% IF call.call_status == "ok" -%] - - [% ELSE -%] - - [% END -%] -
    -
    [% call.clir ? "anonymous" : call.source_cli | uri_unescape %]
    - [% END -%] -
    -
    -
    [% call.start_time -%]
    -
    [% Math.int(call.duration) -%]s
    -
    -
  • - [% END -%] - [% END -%] +
diff --git a/share/templates/widgets/subscriber_cf_overview.tt b/share/templates/widgets/subscriber_cf_overview.tt index 546dc5c07d..d60f8af806 100644 --- a/share/templates/widgets/subscriber_cf_overview.tt +++ b/share/templates/widgets/subscriber_cf_overview.tt @@ -1,33 +1,39 @@ -[% - cfs = [ { type = "cfu", desc = c.loc("Call Forward Unconditional") }, - { type = "cfb", desc = c.loc("Call Forward Busy") }, - { type = "cft", desc = c.loc("Call Forward Timeout") }, - { type = "cfna", desc = c.loc("Call Forward Unavailable") } ]; - cfcount = 0; - FOR cf IN cfs; - IF cf_destinations.${cf.type}.size; - cfcount = cfcount + 1; - END; - END; --%] - +
-
+
[% c.loc('Call Forwards') %]
-
- [% cfcount %] - [% cfcount == 1 ? c.loc('Call Forward Configured') : c.loc('Call Forwards Configured') %] -
+
0processing ...
-
    - [% FOR cf IN cfs -%] -
  • - [% cf.desc %] [% cf_destinations.${cf.type}.size ? '' _ c.loc('active') : '' _ c.loc('inactive') %] -
  • - - [% END -%] +
diff --git a/share/templates/widgets/subscriber_reg_overview.tt b/share/templates/widgets/subscriber_reg_overview.tt index 209a46b1f1..de89beda5b 100644 --- a/share/templates/widgets/subscriber_reg_overview.tt +++ b/share/templates/widgets/subscriber_reg_overview.tt @@ -1,32 +1,47 @@ -[% USE Math -%] +
-
+
[% c.loc('Registered Devices') %]
-
- [% reg_count %] - [% reg_count == 1 ? c.loc('Registered Device') : c.loc('Registered Devices') %] -
+
0processing ...
-
    - [% IF reg_count == 0 -%] -
  • [% c.loc('No devices registered') %]
  • - [% ELSE -%] - [% FOR reg IN regs.all -%] -
  • -
    -
    - [% IF reg.user_agent.length > 48 -%] - [% ua = reg.user_agent.substr(45, reg.user_agent.length - 45, '...'); ua %] - [% ELSE -%] - [% reg.user_agent %] - [% END -%] -
    -
    -
  • - [% END -%] - [% END -%] +
diff --git a/share/templates/widgets/subscriber_vm_overview.tt b/share/templates/widgets/subscriber_vm_overview.tt index e94609d248..f4dbe15e84 100644 --- a/share/templates/widgets/subscriber_vm_overview.tt +++ b/share/templates/widgets/subscriber_vm_overview.tt @@ -1,34 +1,52 @@ +
-
+
[% c.loc('Voicebox Messages') %]
-
- [% vmails.count %] - [% vmails.count == 1 ? c.loc('New Message') : c.loc('New Messages') %] -
+
0processing ...
-
    - [% IF vmails.count == 0 -%] -
  • [% c.loc('No new messages') %]
  • - [% ELSE -%] - [% FOR vmail IN vmails.all -%] -
  • -
    -
    - - - -
    -
    [% vmail.callerid -%]
    -
    -
    -
    [% vmail.origtime -%]
    -
    [% vmail.duration -%]s
    -
    -
  • - [% END -%] - [% END -%] +