commit 41e9261d94e47c97d95a82ad6492f6a6a23519b5 Author: Daniel Tiefnig <dtiefnig@sipwise.com> Date: Thu Feb 7 18:15:42 2008 +0000 renamed www/admin to www_admin created new directory layout for branching and tagging diff --git a/admin.yml b/admin.yml new file mode 100644 index 0000000..8effd26 --- /dev/null +++ b/admin.yml @@ -0,0 +1,3 @@ +--- +name: admin +view: Sipwise diff --git a/lib/admin.pm b/lib/admin.pm new file mode 100644 index 0000000..d7c4817 --- /dev/null +++ b/lib/admin.pm @@ -0,0 +1,81 @@ +package admin; + +use strict; +use warnings; + +use Catalyst::Runtime '5.70'; +use XML::Simple; + +# Set flags and add plugins for the application +# +# -Debug: activates the debug mode for very useful log messages +# ConfigLoader: will load the configuration from a YAML file in the +# application's home directory +# Static::Simple: will serve static files from the application's root +# directory + +use Catalyst::Log::Log4perl; + +use Catalyst qw/-Debug ConfigLoader Static::Simple + Authentication Authentication::Store::Minimal Authentication::Credential::Password + Session Session::Store::FastMmap Session::State::Cookie + /; + +our $VERSION = '0.01'; + +# Configure the application. +# +# Note that settings in admin.yml (or other external +# configuration file that you set up manually) take precedence +# over this when using ConfigLoader. Thus configuration +# details given here can function as a default configuration, +# with a external configuration file acting as an override for +# local deployment. + +# load configuration from admin.conf XML +my $xs = new XML::Simple; +my $xc = $xs->XMLin( '/usr/local/etc/admin.conf', ForceArray => 0); + +__PACKAGE__->config( authentication => {}, %$xc ); + +if(__PACKAGE__->config->{log4perlconf}) { + __PACKAGE__->log( Catalyst::Log::Log4perl->new( + __PACKAGE__->config->{log4perlconf} + )); +} + +# Start the application +__PACKAGE__->setup; + +=head1 NAME + +admin - Catalyst based application + +=head1 DESCRIPTION + +The core module of the administration framework. + +=head1 BUGS AND LIMITATIONS + +=over + +=item none so far + +=back + +=head1 SEE ALSO + +L<admin::Controller::Root>, L<Catalyst> + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The admin module is Copyright (c) 2007 Sipwise GmbH, Austria. All rights +reserved. + +=cut + +1; diff --git a/lib/admin/Controller/Root.pm b/lib/admin/Controller/Root.pm new file mode 100644 index 0000000..bc79fa0 --- /dev/null +++ b/lib/admin/Controller/Root.pm @@ -0,0 +1,121 @@ +package admin::Controller::Root; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +# +# Sets the actions in this controller to be registered with no prefix +# so they function identically to actions created in MyApp.pm +# +__PACKAGE__->config->{namespace} = ''; + +=head1 NAME + +admin::Controller::Root - Root Controller for admin + +=head1 DESCRIPTION + +This provides basic functionality for the admin web interface. + +=head1 METHODS + +=head2 auto + +Verify user is logged in. + +=cut + +# Note that 'auto' runs after 'begin' but before your actions and that +# 'auto' "chain" (all from application path to most specific class are run) +sub auto : Private { + my ($self, $c) = @_; + + if ($c->controller =~ /^admin::Controller::Root\b/ + or $c->controller =~ /^admin::Controller::login\b/) + { + $c->log->debug('***Root::auto front page or login access granted.'); + return 1; + } + + if (!$c->user_exists) { + $c->log->debug('***Root::auto User not found, forwarding to /'); + $c->response->redirect($c->uri_for('/')); + return; + } + + return 1; +} + +=head2 default + +Display default page. + +=cut + +sub default : Private { + my ( $self, $c ) = @_; + + if ($c->user_exists) { + $c->stash->{template} = 'tt/default.tt'; + } else { + $c->stash->{template} = 'tt/login.tt'; + } +} + +=head2 end + +Attempt to render a view, if needed. + +=cut + +sub end : ActionClass('RenderView') { + my ( $self, $c ) = @_; + + $c->stash->{current_view} = $c->config->{view}; + + unless($c->response->{status} =~ /^3/) { # only if not a redirect + if(exists $c->session->{prov_error}) { + $c->stash->{prov_error} = + $c->model('Provisioning')->localize($c->view($c->config->{view})-> + config->{VARIABLES}{site_config}{language}, + $c->session->{prov_error}); + delete $c->session->{prov_error}; + } + + if(exists $c->session->{messages}) { + $c->stash->{messages} = $c->model('Provisioning')->localize($c->view($c->config->{view})-> + config->{VARIABLES}{site_config}{language}, + $c->session->{messages}); + delete $c->session->{messages}; + } + } + + return 1; +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The Root controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Controller/account.pm b/lib/admin/Controller/account.pm new file mode 100644 index 0000000..c645e63 --- /dev/null +++ b/lib/admin/Controller/account.pm @@ -0,0 +1,331 @@ +package admin::Controller::account; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +=head1 NAME + +admin::Controller::account - Catalyst Controller + +=head1 DESCRIPTION + +This provides functionality for VoIP account administration. + +=head1 METHODS + +=head2 index + +Display search form. + +=cut + +sub index : Private { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/account.tt'; + + return 1; +} + +=head2 getbyid + +Check entered account ID and redirect. + +=cut + +sub getbyid : Local { + my ( $self, $c ) = @_; + + my $account_id = $c->request->params->{account_id}; + + if(defined $account_id and $account_id =~ /^\d+$/) { + + if($c->model('Provisioning')->call_prov( $c, 'billing', 'get_voip_account_by_id', + { id => $account_id }, + \$c->session->{voip_account} + )) + { + $c->response->redirect("/account/detail?account_id=$account_id"); + return; + } + + delete $c->session->{prov_error} if $c->session->{prov_error} eq 'Client.Voip.NoSuchAccount'; + $c->session->{messages} = { accsearcherr => 'Client.Voip.NoSuchAccount' }; + } else { + $c->session->{messages} = { accsearcherr => 'Client.Syntax.AccountID' }; + } + + $c->response->redirect("/account"); + return; +} + +=head2 detail + +Show account details. + +=cut + +sub detail : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/account_detail.tt'; + + my $account_id = $c->request->params->{account_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_voip_account_by_id', + { id => $account_id }, + \$c->session->{voip_account} + ); + if($c->config->{billing_features}) { + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_voip_account_balance', + { id => $account_id }, + \$c->session->{voip_account}{balance} + ); + + $c->session->{voip_account}{balance}{cash_balance} = + sprintf "%.2f", $c->session->{voip_account}{balance}{cash_balance} / 100; + $c->session->{voip_account}{balance}{cash_balance_interval} = + sprintf "%.2f", $c->session->{voip_account}{balance}{cash_balance_interval} / 100; + + if(ref $c->session->{restore_account_input} eq 'HASH') { + $c->stash->{account}{product} = $c->session->{restore_account_input}{product}; + $c->stash->{account}{billing_profile} = $c->session->{restore_account_input}{billing_profile}; + delete $c->session->{restore_account_input}; + } + + if(ref $c->session->{restore_balance_input} eq 'HASH') { + $c->stash->{balanceadd} = $c->session->{restore_balance_input}; + delete $c->session->{restore_balance_input}; + } + + $c->stash->{edit_account} = $c->request->params->{edit_account}; + $c->stash->{edit_balance} = $c->request->params->{edit_balance}; + + # we only use this to fill the drop-down lists + if($c->request->params->{edit_account}) { + my $products; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_products', + undef, + \$products + ); + $c->stash->{products} = [ grep { $$_{class} eq 'voip' } @{$$products{result}} ]; + my $billing_profiles; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_billing_profiles', + undef, + \$billing_profiles + ); + + $c->stash->{billing_profiles} = $$billing_profiles{result}; + } + + $c->stash->{billing_features} = 1; + } + + delete $c->session->{voip_account}{subscribers} + if exists $c->session->{voip_account}{subscribers} + and !defined $c->session->{voip_account}{subscribers} + or ref $c->session->{voip_account}{subscribers} ne 'ARRAY' + or $#{$c->session->{voip_account}{subscribers}} == -1; + + $c->stash->{account} = $c->session->{voip_account}; +# $c->stash->{account}{is_locked} = 1 if $c->session->{voip_account}{status} eq 'locked'; + + return 1; +} + +=head2 create_account + +Creates a new VoIP account. + +=cut + +sub create_account : Local { + my ( $self, $c ) = @_; + + my %messages; + + my $acid; + if($c->model('Provisioning')->call_prov( $c, 'billing', 'create_voip_account', + { product => $c->config->{def_product}, + billing_group => $c->config->{def_billprof}, + }, + \$acid)) + { + $messages{accmsg} = 'Web.Account.Created'; + $c->session->{messages} = \%messages; + $c->response->redirect("/account/detail?account_id=$acid"); + return; + } + + $c->response->redirect("/account"); + return; +} + +=head2 update_account + +Update details of a VoIP account. + +=cut + +sub update_account : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $account_id = $c->request->params->{account_id}; + + my $product = $c->request->params->{product}; + $settings{product} = $product if defined $product; + + my $billing_profile = $c->request->params->{billing_profile}; + $settings{billing_profile} = $billing_profile if defined $billing_profile; + + if(keys %settings) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'update_voip_account', + { id => $account_id, + data => \%settings, + }, + undef)) + { + $messages{accmsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/account/detail?account_id=$account_id"); + return; + } + } + + $c->session->{messages} = \%messages; + $c->session->{restore_account_input} = \%settings; + $c->response->redirect("/account/detail?account_id=$account_id&edit_account=1"); + return; +} + +=head2 lock + +Locks and unlocks an account. + +=cut + +sub lock : Local { + my ( $self, $c ) = @_; + + my $account_id = $c->request->params->{account_id}; + my $lock = $c->request->params->{lock}; + + $c->model('Provisioning')->call_prov( $c, 'billing', 'lock_voip_account', + { id => $account_id, + lock => $lock, + }, + undef + ); + + $c->response->redirect("/account/detail?account_id=$account_id"); +} + +=head2 terminate + +Terminates an account. + +=cut + +sub terminate : Local { + my ( $self, $c ) = @_; + + my %messages; + + my $account_id = $c->request->params->{account_id}; + + if($c->model('Provisioning')->call_prov( $c, 'billing', 'terminate_voip_account', + { id => $account_id }, + undef)) + { + $messages{topmsg} = 'Server.Voip.SubscriberDeleted'; + $c->session->{messages} = \%messages; + $c->response->redirect("/account"); + return; + } + + $c->session->{messages} = \%messages; + $c->response->redirect("/account/detail?account_id=$account_id"); + return; +} + +=head2 update_account + +Update a VoIP account cash and free time balance. + +=cut + +sub update_balance : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $account_id = $c->request->params->{account_id}; + + my $add_cash = $c->request->params->{add_cash}; + if(defined $add_cash and length $add_cash) { + $settings{cash} = $add_cash; + if($settings{cash} =~ /^[+-]?\d+(?:[.,]\d+)?$/) { + $settings{cash} =~ s/,/./; + $settings{cash} *= 100; + } else { + $messages{addcash} = 'Client.Syntax.CashValue'; + } + } + my $add_time = $c->request->params->{add_time}; + if(defined $add_time and length $add_time) { + $messages{addtime} = 'Client.Syntax.TimeValue' + unless $add_time =~ /^[+-]?\d+$/; + $settings{free_time} = $add_time; + } + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'update_voip_account_balance', + { id => $account_id, + data => \%settings, + }, + undef)) + { + $messages{balmsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/account/detail?account_id=$account_id"); + return; + } + } else { + $messages{balerr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_balance_input} = \%settings; + $c->session->{restore_balance_input}{cash} = $add_cash + if defined $add_cash and length $add_cash; + $c->response->redirect("/account/detail?account_id=$account_id&edit_balance=1"); + return; +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Sipwise::Provisioning::Billing, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The account controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Controller/admin.pm b/lib/admin/Controller/admin.pm new file mode 100644 index 0000000..fe65255 --- /dev/null +++ b/lib/admin/Controller/admin.pm @@ -0,0 +1,200 @@ +package admin::Controller::admin; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +=head1 NAME + +admin::Controller::admin - Catalyst Controller + +=head1 DESCRIPTION + +Catalyst Controller. + +=head1 METHODS + +=head2 index + +Display admin list. + +=cut + +sub index : Private { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/admin.tt'; + + if($c->session->{admin}{is_master} or $c->session->{admin}{is_superuser}) { + my $admins; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_admins', + undef, + \$admins + ); + $c->stash->{admins} = $$admins{result}; + } else { # only own settings + my $admin; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_admin', + { login => $c->session->{admin}{login} }, + \$admin + ); + $c->stash->{admins} = [ $admin ]; + } + + $c->stash->{edit_admin} = $c->request->params->{edit_admin}; + + if(ref $c->session->{restore_admedit_input} eq 'HASH') { + $c->stash->{erefill} = $c->session->{restore_admedit_input}; + delete $c->session->{restore_admedit_input}; + } + if(ref $c->session->{restore_admadd_input} eq 'HASH') { + $c->stash->{arefill} = $c->session->{restore_admadd_input}; + delete $c->session->{restore_admadd_input}; + } + + return 1; +} + +=head2 do_edit_admin + +Change settings for an admin. + +=cut + +sub do_edit_admin : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $admin = $c->request->params->{admin}; + + $settings{password} = $c->request->params->{password}; + if(defined $settings{password} and length $settings{password}) { + $messages{epass} = 'Client.Voip.PassLength' + unless length $settings{password} >= 6; + } else { + delete $settings{password}; + } + + $settings{is_master} = $c->request->params->{is_master} ? 1 : 0; + $settings{is_active} = $c->request->params->{is_active} ? 1 : 0; + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'update_admin', + { login => $admin, + data => \%settings, + }, + undef + )) + { + $messages{eadmmsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/admin"); + return; + } + } else { + $messages{eadmerr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_admedit_input} = \%settings; + $c->response->redirect("/admin?edit_admin=$admin"); + return; +} + +=head2 do_create_admin + +Create a new admin. + +=cut + +sub do_create_admin : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $admin = $c->request->params->{admin}; + $messages{alogin} = 'Client.Syntax.MalformedLogin' + unless $admin =~ /^\w+$/; + + $settings{password} = $c->request->params->{password}; + $messages{apass} = 'Client.Voip.PassLength' + unless length $settings{password} >= 6; + + $settings{is_master} = $c->request->params->{is_master} ? 1 : 0; + $settings{is_active} = $c->request->params->{is_active} ? 1 : 0; + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'create_admin', + { login => $admin, + data => \%settings, + }, + undef + )) + { + $messages{cadmmsg} = 'Web.Admin.Created'; + $c->session->{messages} = \%messages; + $c->response->redirect("/admin"); + return; + } + } else { + $messages{cadmerr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_admadd_input} = \%settings; + $c->session->{restore_admadd_input}{admin} = $admin; + $c->response->redirect("/admin"); + return; +} + +=head2 do_delete_admin + +Delete a admin. + +=cut + +sub do_delete_admin : Local { + my ( $self, $c ) = @_; + + my $admin = $c->request->params->{admin}; + if($c->model('Provisioning')->call_prov( $c, 'billing', 'delete_admin', + { login => $admin }, + undef + )) + { + $c->session->{messages}{eadmmsg} = 'Web.Admin.Deleted'; + $c->response->redirect("/admin"); + return; + } + + $c->response->redirect("/admin"); + return; +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Sipwise::Provisioning::Billing, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The admin controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Controller/customer.pm b/lib/admin/Controller/customer.pm new file mode 100644 index 0000000..cf8e38e --- /dev/null +++ b/lib/admin/Controller/customer.pm @@ -0,0 +1,328 @@ +package admin::Controller::customer; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +=head1 NAME + +admin::Controller::customer - Catalyst Controller + +=head1 DESCRIPTION + +This provides functionality for customer administration. + +=head1 METHODS + +=head2 index + +Display search form. + +=cut + +sub index : Private { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/customer.tt'; + + return 1; +} + +=head2 search + +Search for customers and display results. + +=cut + +sub search : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/customer.tt'; + + my $search_string = $c->request->params->{search_string}; + + my $customer_list; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'search_customers', + { filter => { anything => '%'.$search_string.'%' } }, + \$customer_list + ); + + $c->stash->{customer_list} = $$customer_list{customers} + if ref $$customer_list{customers} eq 'ARRAY'; +} + +=head2 getbyid + +Check entered customer ID and redirect. + +=cut + +sub getbyid : Local { + my ( $self, $c ) = @_; + + my $customer_id = $c->request->params->{customer_id}; + + if(defined $customer_id and $customer_id =~ /^\d+$/) { + + if($c->model('Provisioning')->call_prov( $c, 'billing', 'get_customer', + { id => $customer_id }, + \$c->session->{customer} + )) + { + $c->response->redirect("/customer/detail?customer_id=$customer_id"); + return; + } + + if($c->session->{prov_error} eq 'Client.Billing.NoSuchCustomer') { + delete $c->session->{prov_error}; + $c->session->{messages} = { custgeterr => 'Client.Billing.NoSuchCustomer' }; + } + } else { + $c->session->{messages} = { custgeterr => 'Web.Syntax.Numeric' }; + } + + $c->response->redirect("/customer"); + return; +} + +=head2 detail + +Show customer details. + +=cut + +sub detail : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/customer_detail.tt'; + + my $customer_id = $c->request->params->{customer_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_customer', + { id => $customer_id }, + \$c->session->{customer} + ); + my $contracts; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_customer_contracts', + { id => $customer_id }, + \$contracts + ); + $c->session->{customer}{contracts} = $$contracts{result}; + my $orders; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_customer_orders', + { id => $customer_id }, + \$orders + ); + $c->session->{customer}{orders} = $$orders{result}; + + $c->stash->{customer} = $c->session->{customer}; + + if(ref $c->session->{restore_customer_input} eq 'HASH') { + $c->stash->{customer} = $c->session->{restore_customer_input}; + delete $c->session->{restore_customer_input}; + } + if(ref $c->session->{restore_contact_input} eq 'HASH') { + $c->stash->{customer}{contact} = $c->session->{restore_contact_input}; + delete $c->session->{restore_contact_input}; + } + if(ref $c->session->{restore_comm_contact_input} eq 'HASH') { + $c->stash->{customer}{comm_contact} = $c->session->{restore_comm_contact_input}; + delete $c->session->{restore_comm_contact_input}; + } + if(ref $c->session->{restore_tech_contact_input} eq 'HASH') { + $c->stash->{customer}{tech_contact} = $c->session->{restore_tech_contact_input}; + delete $c->session->{restore_tech_contact_input}; + } + + $c->stash->{show_pass} = $c->request->params->{show_pass}; + $c->stash->{edit_customer} = $c->request->params->{edit_customer}; + $c->stash->{edit_contact} = $c->request->params->{edit_contact}; + $c->stash->{edit_commercial} = $c->request->params->{edit_commercial}; + $c->stash->{edit_technical} = $c->request->params->{edit_technical}; + + return 1; +} + +=head2 create_customer + +Creates a new customer. Not yet implemented. + +=cut + +sub create_customer : Local { + my ( $self, $c ) = @_; + + my $customer_id; + if($c->model('Provisioning')->call_prov( $c, 'billing', 'create_customer', + { + }, + \$customer_id)) + { + $c->response->redirect("/customer/detail?customer_id=$customer_id"); + return; + } + + $c->response->redirect("/customer"); + return; +} + +=head2 update_customer + +Update details of a customer. + +=cut + +sub update_customer : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $customer_id = $c->request->params->{customer_id}; + + if(defined $c->request->params->{shopuser} and length $c->request->params->{shopuser}) { + $settings{shopuser} = $c->request->params->{shopuser}; + my $checkresult; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'check_username', + $settings{shopuser}, \$checkresult + ); + $messages{username} = 'Client.Syntax.MalformedUsername' unless $checkresult; + } + if(defined $c->request->params->{shoppass} and length $c->request->params->{shoppass}) { + $settings{shoppass} = $c->request->params->{shoppass}; + $messages{password} = 'Client.Voip.PassLength' unless length $settings{shoppass} >= 6; + } + + if(keys %settings and ! keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'update_customer', + { id => $customer_id, + data => \%settings, + }, + undef)) + { + $messages{accmsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/customer/detail?customer_id=$customer_id"); + return; + } + } + + $c->session->{messages} = \%messages; + $c->session->{restore_customer_input} = \%settings; + $c->response->redirect("/customer/detail?customer_id=$customer_id&edit_customer=1"); + return; +} + +=head2 update_contact + +Update details of a customer's contact. + +=cut + +sub update_contact : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $customer_id = $c->request->params->{customer_id}; + + my $ctype = $c->request->params->{ctype}; + + if(defined $c->request->params->{gender} and length $c->request->params->{gender}) { + $settings{gender} = $c->request->params->{gender}; + } else { + $settings{gender} = undef; + } + if(defined $c->request->params->{firstname} and length $c->request->params->{firstname}) { + $settings{firstname} = $c->request->params->{firstname}; + } else { + $settings{firstname} = undef; + } + if(defined $c->request->params->{lastname} and length $c->request->params->{lastname}) { + $settings{lastname} = $c->request->params->{lastname}; + } else { + $settings{lastname} = undef; + } + if(defined $c->request->params->{comregnum} and length $c->request->params->{comregnum}) { + $settings{comregnum} = $c->request->params->{comregnum}; + } else { + $settings{comregnum} = undef; + } + if(defined $c->request->params->{company} and length $c->request->params->{company}) { + $settings{company} = $c->request->params->{company}; + } else { + $settings{company} = undef; + } + if(defined $c->request->params->{street} and length $c->request->params->{street}) { + $settings{street} = $c->request->params->{street}; + } else { + $settings{street} = undef; + } + if(defined $c->request->params->{postcode} and length $c->request->params->{postcode}) { + $settings{postcode} = $c->request->params->{postcode}; + } else { + $settings{postcode} = undef; + } + if(defined $c->request->params->{phonenumber} and length $c->request->params->{phonenumber}) { + $settings{phonenumber} = $c->request->params->{phonenumber}; + } else { + $settings{phonenumber} = undef; + } + if(defined $c->request->params->{mobilenumber} and length $c->request->params->{mobilenumber}) { + $settings{mobilenumber} = $c->request->params->{mobilenumber}; + } else { + $settings{mobilenumber} = undef; + } + if(defined $c->request->params->{email} and length $c->request->params->{email}) { + $settings{email} = $c->request->params->{email}; + } else { + $settings{email} = undef; + } + if(defined $c->request->params->{newsletter} and $c->request->params->{newsletter}) { + $settings{newsletter} = 1; + } else { + $settings{newsletter} = 0; + } + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'update_customer', + { id => $customer_id, + data => { $ctype => \%settings }, + }, + undef)) + { + $messages{accmsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/customer/detail?customer_id=$customer_id"); + return; + } + } + + $c->session->{messages} = \%messages; + $c->session->{'restore_'.$ctype.'_input'} = \%settings; + $c->response->redirect("/customer/detail?customer_id=$customer_id&edit_customer=1"); + return; +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Sipwise::Provisioning::Billing, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The account controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Controller/domain.pm b/lib/admin/Controller/domain.pm new file mode 100644 index 0000000..7890a37 --- /dev/null +++ b/lib/admin/Controller/domain.pm @@ -0,0 +1,191 @@ +package admin::Controller::domain; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +=head1 NAME + +admin::Controller::domain - Catalyst Controller + +=head1 DESCRIPTION + +Catalyst Controller. + +=head1 METHODS + +=head2 index + +Display domain list. + +=cut + +sub index : Private { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/domain.tt'; + + my $domains; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_domains', + undef, + \$domains + ); + $c->stash->{domains} = $$domains{result}; + + $c->stash->{edit_domain} = $c->request->params->{edit_domain}; + + if(ref $c->session->{restore_domedit_input} eq 'HASH') { + foreach my $domain (@{$c->stash->{domains}}) { + next unless $$domain{domain} eq $c->stash->{edit_domain}; + $domain = { %$domain, %{$c->session->{restore_domedit_input}} }; + last; + } + delete $c->session->{restore_domedit_input}; + } + if(ref $c->session->{restore_domadd_input} eq 'HASH') { + $c->stash->{arefill} = $c->session->{restore_domadd_input}; + delete $c->session->{restore_domadd_input}; + } + + return 1; +} + +=head2 do_edit_domain + +Change settings for a domain. + +=cut + +sub do_edit_domain : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $domain = $c->request->params->{domain}; + + $settings{cc} = $c->request->params->{cc}; + $messages{ecc} = 'Client.Voip.MalformedCc' + unless $settings{cc} =~ /^\d+$/; + + $settings{timezone} = $c->request->params->{timezone}; + $messages{etimezone} = 'Client.Syntax.MalformedTimezone' + unless $settings{timezone} =~ m#^\w+/\w.+$#; + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'update_domain', + { domain => $domain, + data => \%settings, + }, + undef + )) + { + $messages{edommsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/domain"); + return; + } + } else { + $messages{edomerr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_domedit_input} = \%settings; + $c->response->redirect("/domain?edit_domain=$domain"); + return; +} + +=head2 do_create_domain + +Create a new domain. + +=cut + +sub do_create_domain : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $domain = $c->request->params->{domain}; + + $settings{cc} = $c->request->params->{cc}; + $messages{acc} = 'Client.Voip.MalformedCc' + unless $settings{cc} =~ /^\d+$/; + + $settings{timezone} = $c->request->params->{timezone}; + $messages{atimezone} = 'Client.Syntax.MalformedTimezone' + unless $settings{timezone} =~ m#^\w+/\w.+$#; + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'create_domain', + { domain => $domain, + data => \%settings, + }, + undef + )) + { + $messages{cdommsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/domain"); + return; + } + } else { + $messages{cdomerr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_domadd_input} = \%settings; + $c->session->{restore_domadd_input}{domain} = $domain; + $c->response->redirect("/domain"); + return; +} + +=head2 do_delete_domain + +Delete a domain. + +=cut + +sub do_delete_domain : Local { + my ( $self, $c ) = @_; + + my $domain = $c->request->params->{domain}; + if($c->model('Provisioning')->call_prov( $c, 'billing', 'delete_domain', + { domain => $domain }, + undef + )) + { + $c->session->{messages}{edommsg} = 'Web.Domain.Deleted'; + $c->response->redirect("/domain"); + return; + } + + $c->response->redirect("/domain"); + return; +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Sipwise::Provisioning::Billing, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The domain controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Controller/login.pm b/lib/admin/Controller/login.pm new file mode 100644 index 0000000..5b6754e --- /dev/null +++ b/lib/admin/Controller/login.pm @@ -0,0 +1,67 @@ +package admin::Controller::login; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +=head1 NAME + +admin::Controller::login - Catalyst Controller + +=head1 DESCRIPTION + +This allows a user to log in. + +=head1 METHODS + +=head2 index + +The authentication function. + +=cut + +sub index : Private { + my ( $self, $c ) = @_; + + $c->log->debug('***login::index called'); + + my $username = $c->request->params->{username} || ""; + my $password = $c->request->params->{password} || ""; + + if ($username && $password) { + if($c->model('Provisioning')->login($c, $username, $password)) { + $c->log->debug('***Login::index login successfull'); + } + } else { + $c->session->{prov_error} = 'Client.Syntax.LoginMissingPass' unless length $password; + $c->session->{prov_error} = 'Client.Syntax.LoginMissingUsername' unless length $username; + } + + $c->response->redirect($c->uri_for('/')); +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The login controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Controller/logout.pm b/lib/admin/Controller/logout.pm new file mode 100644 index 0000000..4c2ac86 --- /dev/null +++ b/lib/admin/Controller/logout.pm @@ -0,0 +1,59 @@ +package admin::Controller::logout; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +=head1 NAME + +admin::Controller::logout - Catalyst Controller + +=head1 DESCRIPTION + +This will log a user out. + +=head1 METHODS + +=head2 index + +The logout function. + +=cut + +sub index : Private { + my ( $self, $c ) = @_; + + $c->log->debug('***logout::index called'); + + $c->logout(); + + delete $c->session->{admin}; + + $c->response->redirect($c->uri_for('/')); +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The logout controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Controller/subscriber.pm b/lib/admin/Controller/subscriber.pm new file mode 100644 index 0000000..1cd1a6b --- /dev/null +++ b/lib/admin/Controller/subscriber.pm @@ -0,0 +1,850 @@ +package admin::Controller::subscriber; + +use strict; +use warnings; +use base 'Catalyst::Controller'; + +=head1 NAME + +admin::Controller::subscriber - Catalyst Controller + +=head1 DESCRIPTION + +This provides functionality for VoIP subscriber administration. + +=head1 METHODS + +=head2 index + +Display search form. + +=cut + +sub index : Private { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/subscriber.tt'; + + return 1; +} + +=head2 search + +Search for subscribers and display results. + +=cut + +sub search : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/subscriber.tt'; + + my $limit = 10; + + my $searchstring = $c->request->params->{search_string}; + my $offset = $c->request->params->{offset} || 0; + $offset = 0 if $offset !~ /^\d+$/; + + my $subscriber_list; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'search_subscribers', + { filter => { username => '%'.$searchstring.'%', + limit => $limit, + offset => $limit * $offset, + }, + }, + \$subscriber_list + ); + + $c->stash->{search_string} = $searchstring; + $c->stash->{searched} = 1; + if(ref $$subscriber_list{subscribers} eq 'ARRAY' and @{$$subscriber_list{subscribers}}) { + $c->stash->{subscriber_list} = $$subscriber_list{subscribers}; + $c->stash->{total_count} = $$subscriber_list{total_count}; + $c->stash->{offset} = $offset; + if($$subscriber_list{total_count} > @{$$subscriber_list{subscribers}}) { + # paginate! + my @pagination; + foreach my $page (0 .. int(($$subscriber_list{total_count} - 1) / $limit)) { + push @pagination, { offset => $page }; + } + $c->stash->{max_offset} = $#pagination; + if($#pagination > 10) { + if($offset <= 5) { + splice @pagination, 9, @pagination - (10), ({offset => -1}); + } else { + if($offset < @pagination - 6) { + splice @pagination, $offset + 4, @pagination - ($offset + 5), ({offset => -1}); + splice @pagination, 1, $offset - 4, ({offset => -1}); + } else { + splice @pagination, 1, @pagination - 10, ({offset => -1}); + } + } + } + $c->stash->{pagination} = \@pagination; + } + } + + return 1; +} + +=head2 detail + +Display subscriber details. + +=cut + +sub detail : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/subscriber_detail.tt'; + + my $is_new = $c->request->params->{new}; + my $preferences; + + unless($is_new) { + my $subscriber_id = $c->request->params->{subscriber_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + \$preferences + ); + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_voicebox_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + \$c->session->{subscriber}{voicebox_preferences} + ); + + $c->stash->{subscriber} = $c->session->{subscriber}; + $c->stash->{subscriber}{subscriber_id} = $subscriber_id; + $c->stash->{subscriber}{is_locked} = $c->model('Provisioning')->localize($c->view($c->config->{view})-> + config->{VARIABLES}{site_config}{language}, + 'Web.Subscriber.Lock'.$$preferences{lock}) + if $$preferences{lock}; + } else { + $c->stash->{account_id} = $c->request->params->{account_id}; + $c->stash->{edit_subscriber} = 1; + my $domains; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_domains', + undef, \$domains + ); + $c->stash->{domains} = $$domains{result}; + } + + my $db_prefs; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_preferences', + undef, \$db_prefs + ); + $c->session->{voip_preferences} = $$db_prefs{result}; + + ### restore data entered by the user ### + + if(ref $c->session->{restore_subscriber_input} eq 'HASH') { + if(ref $c->stash->{subscriber} eq 'HASH') { + $c->stash->{subscriber} = { %{$c->stash->{subscriber}}, %{$c->session->{restore_subscriber_input}} }; + } else { + $c->stash->{subscriber} = $c->session->{restore_subscriber_input}; + } + $c->stash->{subscriber}{edit_pass} = $c->session->{restore_subscriber_input}{password} + if defined $c->session->{restore_subscriber_input}{password}; + $c->stash->{subscriber}{edit_webpass} = $c->session->{restore_subscriber_input}{webpassword} + if defined $c->session->{restore_subscriber_input}{webpassword}; + delete $c->session->{restore_subscriber_input}; + } + if(ref $c->session->{restore_preferences_input} eq 'HASH') { + if(ref $preferences eq 'HASH') { + $preferences = { %$preferences, %{$c->session->{restore_preferences_input}} }; + } else { + $preferences = $c->session->{restore_preferences_input}; + } + delete $c->session->{restore_preferences_input}; + } + if(ref $c->session->{restore_vboxprefs_input} eq 'HASH') { + if(ref $c->stash->{subscriber}{voicebox_preferences} eq 'HASH') { + $c->stash->{subscriber}{voicebox_preferences} = { %{$c->stash->{subscriber}{voicebox_preferences}}, + %{$c->session->{restore_vboxprefs_input}} }; + } else { + $c->stash->{subscriber}{voicebox_preferences} = $c->session->{restore_vboxprefs_input}; + } + delete $c->session->{restore_vboxprefs_input}; + } + + ### build preference array for TT ### + + if(ref $c->session->{voip_preferences} eq 'ARRAY') { + + my $cftarget; + my @stashprefs; + + foreach my $pref (@{$c->session->{voip_preferences}}) { + + # not a subscriber preference + next if $$pref{attribute} eq 'cc'; + # managed separately + next if $$pref{attribute} eq 'lock'; + + # only for extensions enabled systems + next if ( $$pref{attribute} eq 'base_cli' + or $$pref{attribute} eq 'base_user' + or $$pref{attribute} eq 'extension' + or $$pref{attribute} eq 'has_extension' ) + and !$c->config->{extension_features}; + + + if($$pref{attribute} eq 'cfu' + or $$pref{attribute} eq 'cfb' + or $$pref{attribute} eq 'cft' + or $$pref{attribute} eq 'cfna') + { + if(defined $$preferences{$$pref{attribute}} and length $$preferences{$$pref{attribute}}) { + if($$preferences{$$pref{attribute}} =~ /voicebox\.local$/) { + $$cftarget{voicebox} = 1; + } else { + $$cftarget{sipuri} = $$preferences{$$pref{attribute}}; + $$cftarget{sipuri} =~ s/^sip://i; + if($$cftarget{sipuri} =~ /^\+?\d+\@/) { + $$cftarget{sipuri} =~ s/\@.*$//; + } + } + } + } elsif($$pref{attribute} eq 'cli') { + if(defined $$preferences{$$pref{attribute}} and length $$preferences{$$pref{attribute}}) { + $$preferences{$$pref{attribute}} =~ s/^sip://i; + $$preferences{$$pref{attribute}} =~ s/\@.*$// + if $$preferences{$$pref{attribute}} =~ /^\+?\d+\@/; + } + } + + push @stashprefs, + { key => $$pref{attribute}, + value => $$preferences{$$pref{attribute}}, + max_occur => $$pref{max_occur}, + error => $c->session->{messages}{$$pref{attribute}} + ? $c->model('Provisioning')->localize($c->view($c->config->{view})-> + config->{VARIABLES}{site_config}{language}, + $c->session->{messages}{$$pref{attribute}}) + : undef, + }; + } + + # OMG + # reorder preferences so "cftarget" appears just above "cfu" and friends + foreach my $stashpref (@stashprefs) { + if($$stashpref{key} eq 'cfu') { + push @{$c->stash->{subscriber}{preferences_array}}, + { key => 'cftarget', + value => $cftarget, + max_occur => 1, + error => $c->session->{messages}{cftarget} + ? $c->model('Provisioning')->localize($c->view($c->config->{view})-> + config->{VARIABLES}{site_config}{language}, + $c->session->{messages}{cftarget}) + : undef, + }; + } + push @{$c->stash->{subscriber}{preferences_array}}, $stashpref; + } + } + + $c->stash->{show_pass} = $c->request->params->{show_pass}; + $c->stash->{show_webpass} = $c->request->params->{show_webpass}; + $c->stash->{edit_subscriber} = $c->request->params->{edit_subscriber} + unless $is_new; + $c->stash->{edit_preferences} = $c->request->params->{edit_preferences}; + $c->stash->{edit_voicebox} = $c->request->params->{edit_voicebox}; + + return 1; +} + +=head2 update_subscriber + +Update subscriber data or create a new subscriber. + +=cut + +sub update_subscriber : Local { + my ( $self, $c ) = @_; + + my (%settings, %messages); + + my $subscriber_id = $c->request->params->{subscriber_id}; + if($subscriber_id) { + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + } else { + my $checkresult; + $c->session->{subscriber}{account_id} = $c->request->params->{account_id}; + + $c->session->{subscriber}{username} = $settings{webusername} = $c->request->params->{username}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'check_username', + $c->session->{subscriber}{username}, \$checkresult + ); + $messages{username} = 'Client.Syntax.MalformedUsername' unless($checkresult); + + $c->session->{subscriber}{domain} = $c->request->params->{domain}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'check_domain', + $c->session->{subscriber}{domain}, \$checkresult + ); + $messages{domain} = 'Client.Syntax.MalformedDomain' unless($checkresult); + } + + $settings{admin} = 1 if $c->request->params->{admin}; + + my $password = $c->request->params->{password}; + if(length $password) { + $settings{password} = $password; + if(length $password < 6) { + $messages{password} = 'Client.Voip.PassLength'; + } + } + my $webpassword = $c->request->params->{webpassword}; + if(length $webpassword) { + $settings{webpassword} = $webpassword; + if(length $webpassword < 6) { + $messages{webpassword} = 'Client.Voip.PassLength'; + } + } + + my $cc = $c->request->params->{cc}; + my $ac = $c->request->params->{ac}; + my $sn = $c->request->params->{sn}; + if(length $cc or length $ac or length $sn) { + $settings{cc} = $cc; + $settings{ac} = $ac; + $settings{sn} = $sn; + unless(length $cc and length $ac and length $sn) { + $messages{number} = 'Client.Voip.MissingNumberPart'; + } else { + $messages{number_cc} = 'Client.Voip.MalformedCc' + unless $cc =~ /^[1-9][0-9]{0,2}$/; + $messages{number_ac} = 'Client.Voip.MalformedAc' + unless $ac =~ /^[1-9][0-9]{0,4}$/; + $messages{number_sn} = 'Client.Voip.MalformedSn' + unless $sn =~ /^[1-9][0-9]+$/; + } + } + my $timezone = $c->request->params->{timezone}; + if(length $timezone) { + $settings{timezone} = $timezone; + $messages{timezone} = 'Client.Syntax.MalformedTimezone' + unless $timezone =~ m#^\w+/\w.+$#; + } + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', ($subscriber_id + ? 'update_voip_account_subscriber' + : 'add_voip_account_subscriber'), + { id => $c->session->{subscriber}{account_id}, + subscriber => { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + %settings + }, + }, + undef)) + { + $messages{submsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + if($subscriber_id) { + $c->response->redirect("/subscriber/detail?subscriber_id=$subscriber_id"); + } else { + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + \$c->session->{subscriber} + ); + $c->response->redirect("/subscriber/detail?subscriber_id=". $c->session->{subscriber}{subscriber_id}); + } + return; + } + } else { + $messages{suberr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_subscriber_input} = \%settings; + if($subscriber_id) { + $c->response->redirect("/subscriber/detail?subscriber_id=$subscriber_id&edit_subscriber=1"); + } else { + $c->session->{restore_subscriber_input}{username} = $c->session->{subscriber}{username}; + $c->response->redirect("/subscriber/detail?account_id=". $c->session->{subscriber}{account_id} ."&new=1"); + } + return; +} + +=head2 lock + +Locks a subscriber. + +=cut + +sub lock : Local { + my ( $self, $c ) = @_; + + my $subscriber_id = $c->request->params->{subscriber_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + + my $lock = $c->request->params->{lock}; + $c->model('Provisioning')->call_prov( $c, 'billing', 'lock_voip_account_subscriber', + { id => $c->session->{subscriber}{account_id}, + username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + lock => $lock, + }, + undef + ); + + $c->response->redirect("/subscriber/detail?subscriber_id=". $c->request->params->{subscriber_id}); +} + +=head2 terminate + +Terminates a subscriber. + +=cut + +sub terminate : Local { + my ( $self, $c ) = @_; + + my %messages; + + my $subscriber_id = $c->request->params->{subscriber_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + + if($c->model('Provisioning')->call_prov( $c, 'billing', 'terminate_voip_account_subscriber', + { id => $c->session->{subscriber}{account_id}, + username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + undef)) + { + $messages{topmsg} = 'Server.Voip.SubscriberDeleted'; + $c->session->{messages} = \%messages; + $c->response->redirect("/subscriber"); + return; + } + + $c->session->{messages} = \%messages; + $c->response->redirect("/subscriber/detail?subscriber_id=$subscriber_id"); + return; +} + +sub update_preferences : Local { + my ( $self, $c ) = @_; + + my %messages; + + my $subscriber_id = $c->request->params->{subscriber_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + my $preferences; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + \$preferences + ); + ## remove preferences that can't be changed + delete $$preferences{prepaid}; + delete $$preferences{base_cli}; + delete $$preferences{extension}; + delete $$preferences{base_user}; + delete $$preferences{has_extension}; + + ### blocklists ### + + my $block_in_mode = $c->request->params->{block_in_mode}; + if(defined $block_in_mode) { + $$preferences{block_in_mode} = $block_in_mode eq 'whitelist' ? 1 : 0; + } + my $block_out_mode = $c->request->params->{block_out_mode}; + if(defined $block_out_mode) { + $$preferences{block_out_mode} = $block_out_mode eq 'whitelist' ? 1 : 0; + } + + $$preferences{block_in_clir} = $c->request->params->{block_in_clir} ? 1 : undef; + + my $block_in_list = $c->request->params->{block_in_list}; + + ### call forwarding ### + + my $fw_target_select = $c->request->params->{fw_target}; + unless($fw_target_select) { + $messages{target} = 'Client.Voip.MalformedTargetClass'; + } + my $fw_target; + if($fw_target_select eq 'sipuri') { + $fw_target = $c->request->params->{fw_sipuri}; + + # normalize, so we can do some checks. + $fw_target =~ s/^sip://i; + if($fw_target =~ /^\+?\d+\@[a-z0-9.-]+$/i) { + $fw_target =~ s/\@.+$//; + } + + if($fw_target =~ /^\+?\d+$/) { + if($fw_target =~ /^\+[1-9][0-9]+$/) { + $fw_target = 'sip:'. $fw_target .'@'. $c->session->{subscriber}{domain}; + } elsif($fw_target =~ /^00[1-9][0-9]+$/) { + $fw_target =~ s/^00/+/; + $fw_target = 'sip:'. $fw_target .'@'. $c->session->{subscriber}{domain}; + } elsif($fw_target =~ /^0[1-9][0-9]+$/) { + $fw_target =~ s/^0/'+'.$c->session->{subscriber}{cc}/e; + $fw_target = 'sip:'. $fw_target .'@'. $c->session->{subscriber}{domain}; + } else { + $messages{target} = 'Client.Voip.MalformedNumber'; + $fw_target = $c->request->params->{fw_sipuri}; + } + } elsif($fw_target =~ /^[a-z0-9&=+\$,;?\/_.!~*'()-]+\@[a-z0-9.-]+$/i) { + $fw_target = 'sip:'. lc $fw_target; + } elsif($fw_target =~ /^[a-z0-9&=+\$,;?\/_.!~*'()-]+$/) { + $fw_target = 'sip:'. lc($fw_target) .'@'. $c->session->{subscriber}{domain}; + } else { + $messages{target} = 'Client.Voip.MalformedTarget'; + $fw_target = $c->request->params->{fw_sipuri}; + } + } elsif($fw_target_select eq 'voicebox') { + $fw_target = 'sip:vmu'.$c->session->{subscriber}{cc}.$c->session->{subscriber}{ac}.$c->session->{subscriber}{sn}.'@voicebox.local'; + } else { + # wtf? + } + + my $cfu = $c->request->params->{cfu}; + my $cfb = $c->request->params->{cfb}; + my $cft = $c->request->params->{cft}; + my $cfna = $c->request->params->{cfna}; + + # clear all forwards + $$preferences{cfu} = undef; + $$preferences{cft} = undef; + $$preferences{cfb} = undef; + $$preferences{cfna} = undef; + $$preferences{ringtimeout} = undef; + + unless(defined $cfu or defined $cfb or defined $cft or defined $cfna) { + delete $messages{target} if exists $messages{target}; + } else { + if(defined $cfu) { + # forward unconditionally + $$preferences{cfu} = $fw_target; + } else { + if(defined $cfb) { + $$preferences{cfb} = $fw_target; + } + if(defined $cft) { + $$preferences{cft} = $fw_target; + } + if(defined $cfna) { + $$preferences{cfna} = $fw_target; + } + } + } + + if(defined $$preferences{cft}) { + $$preferences{ringtimeout} = $c->request->params->{ringtimeout}; + unless(defined $$preferences{ringtimeout} and $$preferences{ringtimeout} =~ /^\d+$/ + and $$preferences{ringtimeout} < 301 and $$preferences{ringtimeout} > 4) + { + $messages{ringtimeout} = 'Client.Voip.MissingRingtimeout'; + } + } + + ### outgoing calls ### + + $$preferences{cli} = $c->request->params->{cli} or undef; + if(defined $$preferences{cli} and $$preferences{cli} =~ /^\d+$/) { + $$preferences{cli} = 'sip:'.$$preferences{cli}.'@'.$c->session->{subscriber}{domain}; + } + + $$preferences{clir} = $c->request->params->{clir} ? 1 : undef; + + $$preferences{cc} = $c->request->params->{cc} || undef; + if(defined $$preferences{cc} and $$preferences{cc} !~ /^[1-9]\d*$/) { + $messages{cc} = 'Client.Voip.MalformedCc'; + } + $$preferences{ac} = $c->request->params->{ac} || undef; + if(defined $$preferences{ac} and $$preferences{ac} !~ /^[1-9]\d*$/) { + $messages{ac} = 'Client.Voip.MalformedAc'; + } + $$preferences{svc_ac} = $c->request->params->{svc_ac} || undef; + if(defined $$preferences{svc_ac} and $$preferences{svc_ac} !~ /^[1-9]\d*$/) { + $messages{svc_ac} = 'Client.Voip.MalformedAc'; + } + $$preferences{emerg_ac} = $c->request->params->{emerg_ac} || undef; + if(defined $$preferences{emerg_ac} and $$preferences{emerg_ac} !~ /^[1-9]\d*$/) { + $messages{emerg_ac} = 'Client.Voip.MalformedAc'; + } + + ### save settings ### + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'voip', 'set_subscriber_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + preferences => $preferences, + }, + undef + )) + { + $messages{prefmsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/subscriber/detail?subscriber_id=$subscriber_id#userprefs"); + return; + + } + } else { + $messages{preferr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_preferences_input} = $preferences; + $c->response->redirect("/subscriber/detail?subscriber_id=$subscriber_id&edit_preferences=1#userprefs"); + return; + +} + +sub update_voicebox : Local { + my ( $self, $c ) = @_; + + my %messages; + + my $subscriber_id = $c->request->params->{subscriber_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + my $vboxprefs; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_voicebox_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + \$vboxprefs + ); + $$vboxprefs{password} = $c->request->params->{password} || undef; + if(defined $$vboxprefs{password} and $$vboxprefs{password} !~ /^\d{4}$/) { + $messages{vpin} = 'Client.Syntax.VoiceBoxPin'; + } + + $$vboxprefs{email} = $c->request->params->{email}; + if(defined $$vboxprefs{email} and length $$vboxprefs{email}) { + my $checkresult; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'check_email', + $$vboxprefs{email}, \$checkresult + ); + $messages{vemail} = 'Client.Syntax.Email' unless($checkresult); + } else { + $$vboxprefs{email} = undef; + } + + $$vboxprefs{attach} = $c->request->params->{attach} ? 1 : 0; + + ### save settings ### + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'voip', 'set_subscriber_voicebox_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + preferences => $vboxprefs, + }, + undef + )) + { + $messages{vboxmsg} = 'Server.Voip.SavedSettings'; + $c->session->{messages} = \%messages; + $c->response->redirect("/subscriber/detail?subscriber_id=$subscriber_id#vboxprefs"); + return; + } + } else { + $messages{vboxerr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->session->{restore_vboxprefs_input} = $vboxprefs; + $c->response->redirect("/subscriber/detail?subscriber_id=$subscriber_id&edit_voicebox=1#vboxprefs"); + return; +} + +sub edit_list : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/subscriber_edit_list.tt'; + + my %messages; + + my $subscriber_id = $c->request->params->{subscriber_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + my $preferences; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + \$preferences + ); + my $list = $c->request->params->{list_name}; + + if(defined $$preferences{$list}) { + my $block_list = ref $$preferences{$list} ? $$preferences{$list} : [ $$preferences{$list} ]; + + my @block_list_to_sort; + foreach my $blockentry (@$block_list) { + my $active = $blockentry =~ s/^#// ? 0 : 1; + $blockentry =~ s/^([1-9])/+$1/; + push @block_list_to_sort, { entry => $blockentry, active => $active }; + } + my $bg = ''; + my $i = 1; + foreach my $blockentry (sort {$a->{entry} cmp $b->{entry}} @block_list_to_sort) { + push @{$c->stash->{list_data}}, { number => $$blockentry{entry}, + background => $bg ? '' : 'alt', + id => $i++, + active => $$blockentry{active}, + }; + $bg = !$bg; + } + } + + $c->stash->{subscriber} = $c->session->{subscriber}; + $c->stash->{subscriber_id} = $subscriber_id; + $c->stash->{list_name} = $list; + if(defined $c->session->{blockaddtxt}) { + $c->stash->{blockaddtxt} = $c->session->{blockaddtxt}; + delete $c->session->{blockaddtxt}; + } + + return 1; +} + +sub do_edit_list : Local { + my ( $self, $c ) = @_; + + my %messages; + + my $subscriber_id = $c->request->params->{subscriber_id}; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_byid', + { subscriber_id => $subscriber_id }, + \$c->session->{subscriber} + ); + my $preferences; + return unless $c->model('Provisioning')->call_prov( $c, 'voip', 'get_subscriber_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + }, + \$preferences + ); + my $list = $c->request->params->{list_name}; + + # input text field to add new entry to block list + my $add = $c->request->params->{block_add}; + if(defined $add) { + if($add =~ /^\+?[?*0-9\[\]]+$/) { + if($add =~ /^[1-9\[]/) { + $add =~ s/^/$c->session->{subscriber}{cc}.$c->session->{subscriber}{ac}/e; + } elsif($add =~ /^0[^0]/) { + $add =~ s/^0/$c->session->{subscriber}{cc}/e; + } + $add =~ s/^\+/00/; + $add =~ s/^00+//; + my $blocklist = $$preferences{$list}; + $blocklist = [] unless defined $blocklist; + $blocklist = [ $blocklist ] unless ref $blocklist; + $$preferences{$list} = [ @$blocklist, $add ]; + } else { + $messages{msgadd} = 'Client.Voip.MalformedNumberPattern'; + $c->session->{blockaddtxt} = $add; + } + } + + # delete link next to entries in block list + my $del = $c->request->params->{block_del}; + if(defined $del) { + my $blocklist = $$preferences{$list}; + if(defined $blocklist) { + $del =~ s/^\+//; + $del =~ s/^0/$c->session->{subscriber}{cc}/e; + $blocklist = [ $blocklist ] unless ref $blocklist; + if($c->request->params->{block_stat}) { + $$preferences{$list} = [ grep { $_ ne $del } @$blocklist ]; + } else { + $$preferences{$list} = [ grep { $_ ne '#'.$del } @$blocklist ]; + } + } + } + + # activate/deactivate link next to entries in block list + my $act = $c->request->params->{block_act}; + if(defined $act) { + print STDERR "Got request to de/activate $act...\n"; + my $blocklist = $$preferences{$list}; + if(defined $blocklist) { + $act =~ s/^\+//; + $act =~ s/^0/$c->session->{subscriber}{cc}/e; + $blocklist = [ $blocklist ] unless ref $blocklist; + if($c->request->params->{block_stat}) { + $$preferences{$list} = [ grep { $_ ne $act } @$blocklist ]; + push @{$$preferences{$list}}, '#'.$act; + } else { + $$preferences{$list} = [ grep { $_ ne '#'.$act } @$blocklist ]; + push @{$$preferences{$list}}, $act; + } + } + } + + unless(keys %messages) { + $c->model('Provisioning')->call_prov( $c, 'voip', 'set_subscriber_preferences', + { username => $c->session->{subscriber}{username}, + domain => $c->session->{subscriber}{domain}, + preferences => { + $list => $$preferences{$list}, + }, + }, + undef + ); + } else { + $messages{numerr} = 'Client.Voip.InputErrorFound'; + } + + $c->session->{messages} = \%messages; + $c->response->redirect("/subscriber/edit_list?subscriber_id=$subscriber_id&list_name=$list"); + +} + + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Provisioning model, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The subscriber controller is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/Model/Provisioning.pm b/lib/admin/Model/Provisioning.pm new file mode 100644 index 0000000..e0be10a --- /dev/null +++ b/lib/admin/Model/Provisioning.pm @@ -0,0 +1,212 @@ +package admin::Model::Provisioning; + +use strict; +use warnings; +use base 'Catalyst::Model'; +use Scalar::Util; +use Catalyst::Plugin::Authentication; + +use Sipwise::Provisioning::Voip; +use Sipwise::Provisioning::Billing; + +=head1 NAME + +admin::Model::Provisioning - Sipwise provisioning catalyst model + +=head1 DESCRIPTION + +Catalyst Model that uses Sipwise::Provisioning::Voip to get and set VoIP +admin and user data. + +=cut + +sub new { + my $class = shift; + + my $self = {}; + $$self{voip} = Sipwise::Provisioning::Voip->new(); + $$self{billing} = Sipwise::Provisioning::Billing->new(); + + return bless $self, $class; +} + +sub call_prov { + # model, catalyst, scalar, scalar, hash-ref, scalar-ref + my ($self, $c, $backend, $function, $parameter, $result) = @_; + + $c->log->debug("***Provisioning::call_prov calling '$backend\::$function'"); + + eval { + $$result = $$self{$backend}->handle_request( $function, + { + authentication => { + type => 'admin', + username => $c->session->{admin}{login}, + password => $c->session->{admin}{password}, + }, + parameters => $parameter, + }); + }; + + if($@) { + if(ref $@ eq 'SOAP::Fault') { + $c->log->error("***Provisioning::call_prov: $backend\::$function failed: ". $@->faultstring); + $c->session->{prov_error} = $@->faultcode; + } else { + $c->log->error("***Provisioning::call_prov: $backend\::$function failed: $@"); + $c->session->{prov_error} = 'Server.Internal'; + } + return; + } + + return 1; +} + +########################## +# non-standard functions # +########################## + +sub login { + my ($self, $c, $admin, $password) = @_; + + $c->log->debug('***Provisioning::login called, authenticating...'); + + unless(defined $admin and length $admin) { + $c->session->{prov_error} = 'Client.Voip.MissingUsername'; + return; + } + unless(defined $password and length $password) { + $c->session->{prov_error} = 'Client.Voip.MissingPass'; + return; + } + + unless(Scalar::Util::blessed($admin) + and ($Catalyst::Plugin::Authentication::VERSION < 0.10003 + ? $admin->isa("Catalyst::Plugin::Authentication::User") + : $admin->isa("Catalyst::Authentication::User"))) + { + if(my $user_obj = $self->_get_admin($c, $admin)) { + $admin = $user_obj; + } else { + if($c->session->{prov_error} and $c->session->{prov_error} eq 'Server.Voip.NoSuchAdmin') { + $c->log->info("***Provisioning::login authentication failed for '$admin', unknown login."); + $c->session->{prov_error} = 'Client.Voip.AuthFailed'; + } + return; + } + } + if($self->_auth_admin($c, $admin, $password)) { + $c->set_authenticated($admin); + $c->log->debug('***Provisioning::login authentication succeeded.'); + $$admin{password} = $password; + $c->session->{admin} = $admin; + return 1; + } + + $c->log->info("***Provisioning::login authentication failed for '$$admin{login}', wrong password."); + $c->session->{prov_error} = 'Client.Voip.AuthFailed'; + return; +} + +sub localize { + my ($self, $lang, $messages) = @_; + + return unless defined $messages; + + if(ref $messages eq 'HASH') { + my %translations; + foreach my $msgname (keys %$messages) { + $translations{$msgname} = eval { $$self{voip}->get_localized_string({language => $lang, code => $$messages{$msgname}}) }; + unless(defined $translations{$msgname}) { + $translations{$msgname} = eval { $$self{voip}->get_localized_string({language => $lang, code => 'Server.Internal'}) }; + } + } + return \%translations; + } elsif(!ref $messages) { + my $translation = eval { $$self{voip}->get_localized_string({language => $lang, code => $messages}) }; + unless(defined $translation) { + $translation = eval { $$self{voip}->get_localized_string({language => $lang, code => 'Server.Internal'}) }; + } + return $translation; + } + + return; +} + + + +#################### +# helper functions # +#################### + +sub _get_admin { + my ($self, $c, $login) = @_; + + my $admin_obj = eval { + $$self{billing}->get_admin({login => $login}); + }; + if($@) { + if(ref $@ eq 'SOAP::Fault') { + $c->log->error("***Provisioning::_get_admin failed to get admin '$login' from DB: ". $@->faultstring) + unless $@->faultcode eq 'Server.Voip.NoSuchAdmin'; + $c->session->{prov_error} = $@->faultcode; + } else { + $c->log->error("***Provisioning::_get_admin failed to get admin '$login' from DB: $@."); + $c->session->{prov_error} = 'Server.Internal'; + } + return; + } + my $return = { %$admin_obj, id => $login, store => $self }; + if($Catalyst::Plugin::Authentication::VERSION < 0.10003) { + return bless $return, "Catalyst::Plugin::Authentication::User::Hash"; + } else { + return bless $return, "Catalyst::Authentication::User::Hash"; + } +} + +sub _auth_admin { + my ($self, $c, $admin, $pass) = @_; + + eval { $$self{billing}->authenticate_admin({ login => $$admin{login}, + password => $pass, + }); + }; + if($@) { + if(ref $@ eq 'SOAP::Fault') { + $c->log->error("***Provisioning::_auth_admin failed to auth admin '$$admin{login}': ". $@->faultstring); + $c->session->{prov_error} = $@->faultcode; + } else { + $c->log->error("***Provisioning::_auth_admin failed to auth admin '$$admin{login}': $@."); + $c->session->{prov_error} = 'Server.Internal'; + } + return; + } + + return 1; +} + +=head1 BUGS AND LIMITATIONS + +=over + +=item currently none + +=back + +=head1 SEE ALSO + +Sipwise::Provisioning::Voip, Sipwise::Provisioning::Billing, Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The Provisioning model is Copyright (c) 2007 Sipwise GmbH, Austria. All +rights reserved. + +=cut + +# ende gelaende +1; diff --git a/lib/admin/View/Sipwise.pm b/lib/admin/View/Sipwise.pm new file mode 100644 index 0000000..cb3e225 --- /dev/null +++ b/lib/admin/View/Sipwise.pm @@ -0,0 +1,64 @@ +package admin::View::Sipwise; + +use strict; +use warnings; +use base 'Catalyst::View::TT'; + +=head1 NAME + +admin::View::Sipwise - Sipwise Catalyst View for NGCP admin interface + +=head1 DESCRIPTION + +Sipwise Catalyst View for NGCP admin interface. + +=cut + +__PACKAGE__->config( +## PRE_PROCESS => 'config/main', +## TEMPLATE_EXTENSION => '.tt', + MACRO => 'debug(message) CALL Catalyst.log.debug(message)', + INCLUDE_PATH => [ + admin->path_to( 'root' ), + ], + WRAPPER => 'layout/wrapper', + ERROR => 'tt/error.tt', + CATALYST_VAR => 'Catalyst', + VARIABLES => { + site_config => { + language => 'en', + language_string => 'English', + css => [ '/css/sipwise.css', '/css/admin.css' ], + title => 'Sipwise NGCP admin interface', + company => { + name => 'Sipwise GmbH' + }, + }, + }, +); + +=head1 BUGS AND LIMITATIONS + +=over + +=item none. + +=back + +=head1 SEE ALSO + +Catalyst + +=head1 AUTHORS + +Daniel Tiefnig <dtiefnig@sipwise.com> + +=head1 COPYRIGHT + +The Sipwise view is Copyright (c) 2007 Sipwise GmbH, Austria. All rights +reserved. + +=cut + +# over and out +1; diff --git a/root/css/admin.css b/root/css/admin.css new file mode 100644 index 0000000..d918754 --- /dev/null +++ b/root/css/admin.css @@ -0,0 +1,260 @@ +/* + * Relative size definitions like "large" ,"0.8em", etc. + * don't work very well 'cross different browsers. + * Allthought it is recommended to use them, they don't + * give useable results. + */ +body { + font: 14px Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif !important; + /* hover effects for IE6 */ + behavior:url("/css/csshover.htc"); +} +h3 { + font-size: 18px !important; +} + +#header #sipwise_logo { + margin: 3px; + width: 200px; +} + +#header #heading { + position: absolute; + top: 20px; + left: 250px; + text-align: center; + width: 400px; + font-weight: bold; + font-size: 20px; +} + +#header #client_logo { + position: absolute; + top: 2px; + right: 2px; + margin: 3px; + width: 200px; + text-align: right; +} + + +/* hmm, menu doesn't extend container height ... */ +#container #content { + min-height: 570px; +} + +/* contentmenu's width and contentplace' margin-left should match */ +#container #contentmenu { + width: 230px; +} +#container #contentplace { + margin-left: 230px; +} + +/* red error messages */ +.errormsg { + width: auto; + margin: 0; + padding: 5px; + font-weight: bold; + color: #FF1010; +} +/* green success messages */ +.goodmsg { + width: auto; + margin: 0; + padding: 5px; + font-weight: bold; + color: #10BB40; +} +/* highlight text */ +.alert { + width: auto; + margin: 0; + padding: 0; + font-weight: bold; + color: #FF1010; +} + +/* increase vertical spacing in tables */ +#contentplace td { + padding: 3px 5px 3px 5px; +} + +/* some table alignment aliases */ +#contentplace .tdcenter { + text-align: center; +} + +#contentplace .tdmiddle { + vertical-align: top; +} + +#contentplace .tdkey { + font-weight: bold; + white-space: nowrap; +} + +#contentplace .table_header { + font-weight: bold; + white-space: nowrap; +} + +#contentplace .nowrap { + white-space: nowrap; +} + +/* format links and images in post div's */ +#contentplace .postlink label { + font-weight: bold; + cursor: pointer; + color: #006600; + text-decoration: none; +} +#contentplace .postlink label:hover { + color: #D60808; + text-decoration: underline; +} +#contentplace .postlink label:after { + content: "\bb"; + font-weight: bold; +} +#contentplace .postlink .hidden { + position: absolute; + top: -90000px; + left: -90000px; +} + +/* + * use smaller font for input elements (textfields) + * and select boxes + */ +#container #contentplace input { + font-size: 11px; + padding: 2px 2px 2px 3px; + border: 1px solid #606060; +} + +/* sigh, no border for checkboxes */ +#contentplace .checkbox { + border: none !important; +} + +#contentplace .table input { + margin: -2px 0 -2px 0; +} + +#contentplace select { + font-size: 11px; + border: 1px solid #606060; +} + +/* use smaller font for buttons */ +#contentplace .but { + font-size: 11px; + text-align: center; + border: 1px solid #606060; + background-color: #e0e0e0; + padding: 2px !important; + margin: 3px 0 5px 0; +} + +/* print ">>" after hyper refs */ +#contentplace a:after { + content: "\bb"; + font-weight: bold; +} +/* no ">>" in heading */ +#contentplace h3 a:after { + content: ""; +} + +/* specify position of edit/lock/delete action links */ +#contentplace .actions { + margin: 0 0 -17px 23px; + position: relative; +} +#contentplace .aaction { + margin: 0 8px 0 0; + font-weight: bold; +} + +/* colors for disabled input fields */ +#contentplace .disabled { + color: #808080; + background-color: #f4f4f4; +} + +#contentplace .txtpreference { + width: 160px; +} + +/* customize login form */ +#login { + border: none; + margin: 0 0 0 10px; +} +#login label { + font-weight: bold; +} +#login_user, #login_pass { + font-size: 11px; + margin: 3px 0 5px 0; + padding: 3px; + width: 120px; +} + +/* decrease input text field for password */ +#edit_pass, #edit_webpass { + width: 95px; +} + +/* decrease input text fields for cc/ac */ +.ishort { + width: 40px; +} + +/* increase spacing between password and show/hide href */ +#contentplace .apass { + vertical-align: middle; + margin: 0 0 0 5px; +} + +/* increase spacing between amount and add textfield in + * account balance edit + */ +#contentplace .account_add { + margin: 0 0 0 15px; +} +/* decrease input text field for amounts */ +#contentplace .account_add input { + width: 50px; +} + +/* floating menu for subscriber lock */ +#contentplace .floating { + z-index: 100; + display: none; + position: absolute; + top: 20px; + left: 50px; + border: 1px solid #006600; + background-color: #DDFFDD; + padding: 3px 10px 3px 5px; +} +#contentplace .menu_foo { + z-index: 200; + overflow: auto; + margin: 0; + width: 200px; + height: 200px; +} + +/* correct margin for active paginations */ +.pagination li.currentpage, .pagination li.disablepage { + margin: 0 5px 0 0; +} +/* no ">>" in pagination next / previous */ +#contentplace .pagination li.nextpage a:after { + content: ""; +} diff --git a/root/css/csshover.htc b/root/css/csshover.htc new file mode 100644 index 0000000..3ba936a --- /dev/null +++ b/root/css/csshover.htc @@ -0,0 +1,120 @@ +<attach event="ondocumentready" handler="parseStylesheets" /> +<script> +/** + * Whatever:hover - V1.42.060206 - hover & active + * ------------------------------------------------------------ + * (c) 2005 - Peter Nederlof + * Peterned - http://www.xs4all.nl/~peterned/ + * License - http://creativecommons.org/licenses/LGPL/2.1/ + * + * Whatever:hover is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Whatever:hover is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * Credits and thanks to: + * Arnoud Berendsen, Martin Reurings, Robert Hanson + * + * howto: body { behavior:url("csshover.htc"); } + * ------------------------------------------------------------ + */ + +var csshoverReg = /(^|\s)(([^a]([^ ]+)?)|(a([^#.][^ ]+)+)):(hover|active)/i, +currentSheet, doc = window.document, hoverEvents = [], activators = { + onhover:{on:'onmouseover', off:'onmouseout'}, + onactive:{on:'onmousedown', off:'onmouseup'} +} + +function parseStylesheets() { + if(!/MSIE (5|6)/.test(navigator.userAgent)) return; + window.attachEvent('onunload', unhookHoverEvents); + var sheets = doc.styleSheets, l = sheets.length; + for(var i=0; i<l; i++) + parseStylesheet(sheets[i]); +} + function parseStylesheet(sheet) { + if(sheet.imports) { + try { + var imports = sheet.imports, l = imports.length; + for(var i=0; i<l; i++) parseStylesheet(sheet.imports[i]); + } catch(securityException){} + } + + try { + var rules = (currentSheet = sheet).rules, l = rules.length; + for(var j=0; j<l; j++) parseCSSRule(rules[j]); + } catch(securityException){} + } + + function parseCSSRule(rule) { + var select = rule.selectorText, style = rule.style.cssText; + if(!csshoverReg.test(select) || !style) return; + + var pseudo = select.replace(/[^:]+:([a-z-]+).*/i, 'on$1'); + var newSelect = select.replace(/(\.([a-z0-9_-]+):[a-z]+)|(:[a-z]+)/gi, '.$2' + pseudo); + var className = (/\.([a-z0-9_-]*on(hover|active))/i).exec(newSelect)[1]; + var affected = select.replace(/:(hover|active).*$/, ''); + var elements = getElementsBySelect(affected); + if(elements.length == 0) return; + + currentSheet.addRule(newSelect, style); + for(var i=0; i<elements.length; i++) + new HoverElement(elements[i], className, activators[pseudo]); + } + +function HoverElement(node, className, events) { + if(!node.hovers) node.hovers = {}; + if(node.hovers[className]) return; + node.hovers[className] = true; + hookHoverEvent(node, events.on, function() { node.className += ' ' + className; }); + hookHoverEvent(node, events.off, function() { node.className = node.className.replace(new RegExp('\\s+'+className, 'g'),''); }); +} + function hookHoverEvent(node, type, handler) { + node.attachEvent(type, handler); + hoverEvents[hoverEvents.length] = { + node:node, type:type, handler:handler + }; + } + + function unhookHoverEvents() { + for(var e,i=0; i<hoverEvents.length; i++) { + e = hoverEvents[i]; + e.node.detachEvent(e.type, e.handler); + } + } + +function getElementsBySelect(rule) { + var parts, nodes = [doc]; + parts = rule.split(' '); + for(var i=0; i<parts.length; i++) { + nodes = getSelectedNodes(parts[i], nodes); + } return nodes; +} + function getSelectedNodes(select, elements) { + var result, node, nodes = []; + var identify = (/\#([a-z0-9_-]+)/i).exec(select); + if(identify) { + var element = doc.getElementById(identify[1]); + return element? [element]:nodes; + } + + var classname = (/\.([a-z0-9_-]+)/i).exec(select); + var tagName = select.replace(/(\.|\#|\:)[a-z0-9_-]+/i, ''); + var classReg = classname? new RegExp('\\b' + classname[1] + '\\b'):false; + for(var i=0; i<elements.length; i++) { + result = tagName? elements[i].all.tags(tagName):elements[i].all; + for(var j=0; j<result.length; j++) { + node = result[j]; + if(classReg && !classReg.test(node.className)) continue; + nodes[nodes.length] = node; + } + } + + return nodes; + } +</script> \ No newline at end of file diff --git a/root/css/sipwise.css b/root/css/sipwise.css new file mode 100644 index 0000000..337a2fe --- /dev/null +++ b/root/css/sipwise.css @@ -0,0 +1,552 @@ +body +{ + margin: 0; + padding: 0; + color: #333; + font: 0.8em Trebuchet MS, Lucida Sans Unicode, Arial, sans-serif; + background: #e0e0e0; +} + +a +{ + color: #006600; + text-decoration: none; +} + +a:hover +{ + color: #D60808; + text-decoration: underline; +} + +img +{ + border: none; +} + + +#container +{ + margin: 20px auto; + width: 900px; + position: relative; +} + + +* html #container +{ + width: 900px; + w\idth: 900px; + height: auto; +} + +#header +{ + background: #fff; + border: 2px solid #000; +} + +#headerlogin +{ + position: absolute; + top: 10px; + right: 20px; + margin: 0; +} + +#headermenu +{ +} + +#headermenu ul +{ + text-align: center; + margin-left: 0; + padding-left: 0; +} + +#headermenu li +{ + list-style-type: none; + font-size: normal; + font-weight: bold; + padding: 0.25em 1em; + display: inline; + border-left: 1px solid #e0e0e0; +} + +#headermenu li:first-child +{ + border-left: none; +} + + + +#headerlogo img +{ + display: block; + margin-left: auto; + margin-right: auto; +} + +#content +{ + background: #fff; + min-height: 400px; + height: auto !important; + height: 400px; /* IE hack */ + border: 2px solid #000; + margin-top: 10px; + padding: 10px; +} + +#contentmenu h3 +{ + font-weight: bold; + font-size: normal; + margin: 20px 20px 5px 10px; +} + +#contentmenu h3#first +{ + font-weight: bold; + font-size: normal; + margin: 0px 20px 5px 10px; +} + +#contentmenu +{ + float: left; + padding-right: 20px; + padding-top: 20px; +} + +#contentmenu ul +{ + width: 150px; + list-style-type: none; + margin: 0; + padding: 0 0 0 20px; +} + +#contentmenu ul li +{ + font-size: normal; + font-weight: normal; + border-bottom: 1px solid #e0e0e0; + display: block; + padding: 5px 0; +} +#contentmenu ul li a +{ + font-size: normal; + font-weight: bold; +} + +#contentplace +{ + margin-left: 220px; +} + +#contentplace .expert +{ + position: relative; + float: right; + margin-top: 10px; + margin-right: 30px; +} + + + +#contentplace h3 +{ + font-weight: bold; + font-size: large; + margin-left: 20px; +} + +#contentplace h3 img +{ + margin: 10px; + margin-bottom: 0px; + padding: 0px; +} + +#contentplace p +{ + border: 0; + margin: 10px; + padding: 0; +} + +#contentplace .p1 +{ + border: 1px solid #e0e0e0; + margin: 20px 20px 20px 20px; + padding: 10px 10px 10px 10px; +} + +#contentplace .p1full +{ + border: 1px solid #e0e0e0; + margin: 20px 20px 20px 20px; + padding: 10px 10px 20px 10px; +} + +#contentplace .p1center +{ + margin-left: auto; + margin-right: auto; + padding: 10px 10px 10px 10px; +} + +#contentplace .p2center +{ + margin-left: auto; + margin-right: auto; + padding: 10px 10px 10px 10px; +} + +#contentplace .p2right +{ + float: right; + padding: 10px 10px 10px 10px; +} + +#contentplace .p3 +{ + border: 0; + margin: 30px; + padding: 10px; + position: relative; +} + +#contentplace .p3image +{ + position: relative; + margin-bottom: 20px; + text-align: center; +} + +#contentplace .p3 .p3text +{ + margin: 0; + font-weight: bold; +} + + +#contentplace form input +{ + border: 1px solid #606060; + background-color: #fff; + vertical-align: middle; + padding: 3px; +} + +#contentplace form .nb +{ + border: none; + padding: 3px; +} + +#contentplace form .but +{ + border: 1px solid #606060; + background-color: #e0e0e0; + padding: 4px; + margin-top: 8px; + margin-bottom: 8px; +} + +#contentplace table +{ + padding: 0; + margin: 0; +} + +#contentplace .p1center table +{ + margin-left: auto; + margin-right: auto; + border: 1px solid #e0e0e0; + padding: 10px; +} + +#contentplace .p1center .tdcenter +{ + margin-left: auto; + margin-right: auto; + text-align: center; +} + +#contentplace .p2center table +{ + margin-left: auto; + margin-right: auto; + padding: 10px; +} + +#contentplace .p2center td +{ + text-align: center; + border: 1px solid #e0e0e0; + padding: 20px; + width: 200px; +} + +#contentplace .p2center .tdcenter +{ + margin-left: auto; + margin-right: auto; + text-align: center; +} + +#contentplace tr +{ + text-align: left; +} + +#contentplace td +{ + padding: 3px; + margin: 0; + border: 0; + text-align: left; +} + +#contentplace tr .desc +{ + background-color: #e0e0e0; + padding: 0; + margin: 0; +} + +#contentplace td .desc +{ + background-color: #e0e0e0; +} + +#contentplace .template table +{ + margin-left: auto; + margin-right: auto; + width: 100%; +} + +#contentplace .template tr +{ + vertical-align: top; +} + +#contentplace .template td +{ + padding-left: 15px; + padding-right: 15px; + padding-bottom: 20px; + width: 50%; + border: 1px solid #e0e0e0; +} + +#contentplace .template ul +{ + list-style-type: circle; +} + +#footer +{ + margin: 0; + padding: 20px; + text-align: center; + font-size: small; + font-weight: normal; + color: #606060; +} + +#contentplace .p1 .listing td +{ + padding: 0px 5px 0px 5px; +} + +#contentplace .p1 .listing td.center +{ + padding: 0px 5px 0px 5px; + text-align: center +} + +#contentplace .p1 .listing th +{ + padding: 5px; + text-align: center; +} + + +.p2 +{ +} + +.p2int +{ + padding-left: 20px; +} + +.error +{ + color: #f00; + padding-left: 20px; +} + +.errorExplanation +{ + color: #f00; + border: 1px solid #f00; + /*margin: 10px;*/ + margin: 20px 20px 20px 20px; + padding: 10px; +} + +.errorExplanation h2 +{ + font-weight: bold; + font-size: large; +} + +.errorExplanation h2.center +{ + font-weight: bold; + font-size: large; + text-align: center; +} + +#notice +{ + color: #060; + border: 1px solid #060; + margin: 20px 20px 20px 20px; + padding: 10px; + text-align: center; + font-weight: bold; + font-size: normal; +} + +#warning +{ + color: #f90; + border: 1px solid #f90; + margin: 20px 20px 20px 20px; + padding: 10px; + text-align: center; + font-weight: bold; + font-size: normal; +} + +#warning a +{ + color: #D60808; +} + +#confcode +{ + font: 10px Courier; + color: #000 +} + +#username +{ + font-weight: bold; +} + +.fcattable +{ + /*border: 1px solid #060;*/ + width: 100%; + +} + +.fcatheader +{ + background-color: #060; + color: #fff; +} + + +.price +{ + color: #f00; +} + +.price_free +{ + color: #0c0; +} + +.logo-1 +{ + color: #000000; +} + +.logo-2 +{ + color: #006600; +} + +.pagination +{ + padding: 2px; + margin-top: 10px; +} + +.pagination ul +{ + margin: 0; + padding: 0; + text-align: center; +} + +.pagination li +{ + list-style-type: none; + display: inline; + padding-bottom: 1px; +} + +.pagination a, .pagination a:visited +{ + padding: 0 5px; + border: 1px solid #060; + text-decoration: none; + color: #060; +} + +.pagination a:hover, .pagination a:active +{ + border: 1px solid #060; + color: #000; + background-color: lightyellow; +} + +.pagination li.currentpage +{ + font-weight: bold; + padding: 0 5px; + border: 1px solid #060; + background-color: #060; + color: #fff; +} + +.pagination li.disablepage +{ + padding: 0 5px; + border: 1px solid #929292; + color: #929292; +} + +.pagination li.nextpage +{ + font-weight: bold; +} + +* html .pagination li.currentpage, * html .pagination li.disablepage +{ + margin-right: 5px; + padding-right: 0; +} diff --git a/root/favicon.ico b/root/favicon.ico new file mode 100644 index 0000000..3af0982 Binary files /dev/null and b/root/favicon.ico differ diff --git a/root/js/openclose.js b/root/js/openclose.js new file mode 100644 index 0000000..8d5dda4 --- /dev/null +++ b/root/js/openclose.js @@ -0,0 +1,10 @@ +oc = "open"; +function openclose() { + if (oc == "open") { + document.getElementById('lock_menu').style.display = 'inline'; + oc = "close"; + } else { + document.getElementById('lock_menu').style.display = 'none'; + oc = "open"; + } +} diff --git a/root/layout/body b/root/layout/body new file mode 100644 index 0000000..f43f640 --- /dev/null +++ b/root/layout/body @@ -0,0 +1,21 @@ + <div id="container"> + +[% PROCESS layout/header %] + + <div id="content"> + +[% PROCESS layout/menu %] + + <div id="contentplace"> + [% IF prov_error %]<div class="errormsg">[% prov_error %]</div>[% END %] + [% IF messages.topmsg %]<div class="goodmsg">[% messages.topmsg %]</div>[% END %] + [% IF messages.toperr %]<div class="errormsg">[% messages.toperr %]</div>[% END %] + +[% content %] + </div> + + </div> + +[% PROCESS layout/footer %] + + </div> diff --git a/root/layout/footer b/root/layout/footer new file mode 100644 index 0000000..9315ddd --- /dev/null +++ b/root/layout/footer @@ -0,0 +1,6 @@ +<!-- BEGIN layout/footer --> + <div id="footer"> + Copyright 2007 Sipwise, Austria.<br /> + <a href="http://sipwise.com/">http://sipwise.com</a> + </div> +<!-- END layout/footer --> diff --git a/root/layout/header b/root/layout/header new file mode 100644 index 0000000..f8512fd --- /dev/null +++ b/root/layout/header @@ -0,0 +1,12 @@ +<!-- BEGIN layout/header --> + <div id="header"> + <div id="sipwise_logo"> + <a href="http://sipwise.com/"><img src="/static/images/sipwise_logo_96.png" alt="Sipwise GmbH, Austria" /></a> + </div> + <div id="heading">Sipwise NGCP Administration Interface</div> + <div id="client_logo"> + <a href="[% site_config.company.homepage %]"><img src="/static/images/client_logo.png" alt="" /></a> + </div> + </div> + +<!-- END layout/header --> diff --git a/root/layout/html b/root/layout/html new file mode 100644 index 0000000..500efe0 --- /dev/null +++ b/root/layout/html @@ -0,0 +1,28 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="[% site_config.language %]"> + +<head> + + <title>[% template.title or site_config.title %]</title> + + [% FOREACH css_file = site_config.css %] + <link rel="stylesheet" type="text/css" href="[% css_file %]" /> + [% END %] + + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <meta http-equiv="content-language" content="[% site_config.language %]" /> + + <meta name="description" content="[% template.title or site_config.title %]" /> + <meta name="title" content="[% template.title or site_config.title %]" /> + <meta name="Company" content="[% site_config.company.name %]" /> + <meta name="keywords" content="" /> + <meta name="Language" content="[% site_config.language_string %]" /> + +</head> + +<body> +[% content %] +</body> + +</html> diff --git a/root/layout/menu b/root/layout/menu new file mode 100644 index 0000000..b878459 --- /dev/null +++ b/root/layout/menu @@ -0,0 +1,35 @@ + <div id="contentmenu"> + + <h3 id="first">Authentication</h3> + <ul> + [% IF Catalyst.user_exists %] + <li>Logged in as <b>[% Catalyst.user.login %]</b></li> + <li><a href="/logout">» Logout</a></li> + [% ELSE %] + <li> </li> + <li>Not logged in</li> + [% END %] + </ul> + + [% IF Catalyst.session.admin %] + <h3>User Administration</h3> + <ul> + [% IF Catalyst.config.billing_features %] + <li><a href="/customer">Customers</a></li> + [% END %] + <li><a href="/account">Accounts</a></li> + <li><a href="/subscriber">Subscribers</a></li> +[%# <li><a href="/registration">Devices</a></li> %] +[%# <li><a href="/registration">Registrations</a></li> %] + </ul> + + <h3>System Administration</h3> + <ul> + <li><a href="/domain">Domains</a></li> + <li><a href="/admin">Administrators</a></li> +[%# <li><a href="/gwgroup">Gateway Settings</a></li> %] +[%# <li><a href="/sipserver">SIP Server Settings</a></li> %] + </ul> + [% END %] + + </div> diff --git a/root/layout/wrapper b/root/layout/wrapper new file mode 100644 index 0000000..73477d8 --- /dev/null +++ b/root/layout/wrapper @@ -0,0 +1,8 @@ +[% IF template.name.match('(\.html$|\.css$|\.js$|\.txt$)'); + debug("Passing page through as text: $template.name"); + content; + ELSE; + debug("Applying HTML page layout wrappers to $template.name\n"); + content WRAPPER layout/html + layout/body; + END; +-%] diff --git a/root/static/images/client_logo.png b/root/static/images/client_logo.png new file mode 100644 index 0000000..76ed90a Binary files /dev/null and b/root/static/images/client_logo.png differ diff --git a/root/static/images/dot_trans.gif b/root/static/images/dot_trans.gif new file mode 100644 index 0000000..ed8f380 Binary files /dev/null and b/root/static/images/dot_trans.gif differ diff --git a/root/static/images/sipwise_logo_96.png b/root/static/images/sipwise_logo_96.png new file mode 100644 index 0000000..24174ba Binary files /dev/null and b/root/static/images/sipwise_logo_96.png differ diff --git a/root/tt/account.tt b/root/tt/account.tt new file mode 100644 index 0000000..eaf9c6e --- /dev/null +++ b/root/tt/account.tt @@ -0,0 +1,52 @@ + <h3>Get by ID</h3> + <div class="p1"> + <form action="/account/getbyid" method="GET"> + <input type="text" id="account_id" name="account_id" value="[% account_id %]" /> + <input type="submit" value="Get »" class="but" /> + </form> + [% IF messages.accsearcherr %]<div class="errormsg">[% messages.accsearcherr %]</div>[% END %] + </div> + + <h3>Search by subscriber</h3> + <div class="p1"> + <form action="/subscriber/search" method="post"> + <input type="text" id="search_subscriber" name="search_string" value="[% search_string %]" /> + <input type="submit" value="Search »" class="but" /> + </form> + </div> + + <h3>Create new account</h3> + <div class="p1"> + <form action="/account/create_account" method="post"> + <input type="submit" value="Create »" class="but" /> + </form> + </div> + + [% IF subscriber_list %] + + <h3>Subscribers</h3> + + <div class="p1"> + <table> + <tr><td>SIP URI</td><td class="tdcenter">Subscriber ID</td><td class="tdcenter">Account ID</td></tr> + + [% FOREACH subscriber = subscriber_list %] + + <tr> + <td><a href="/subscriber/detail?subscriber_id=[% subscriber.id %]"> + [% subscriber.username %]@[% subscriber.domain %] + </a></td> + <td class="tdcenter"><a href="/subscriber/detail?subscriber_id=[% subscriber.id %]"> + [% subscriber.id %] + </a></td> + <td class="tdcenter"><a href="/account/detail?account_id=[% subscriber.account_id %]"> + [% subscriber.account_id %] + </a></td> + </tr> + + [% END %] + + </table> + </div> + + [% END %] diff --git a/root/tt/account_detail.tt b/root/tt/account_detail.tt new file mode 100644 index 0000000..2b2d2c8 --- /dev/null +++ b/root/tt/account_detail.tt @@ -0,0 +1,167 @@ + <h3> VoIP Account + <a class="noarrow" href="detail?account_id=[% account.id %]"> + #[% account.id %]</a> + </h3> + + [% IF ! account.terminate_timestamp %] + <div class="actions"> + [% IF billing_features %] + <a href="detail?account_id=[% account.id %]&edit_account=1" class="aaction">edit</a> + [% END %] + [% IF edit_account %] + <a href="detail?account_id=[% account.id %]" class="aaction">cancel</a> + [% ELSE %] + [% IF account.is_locked %] + <a href="lock?account_id=[% account.id %]&lock=none" class="aaction">unlock</a> + [% ELSE %] + <a href="lock?account_id=[% account.id %]&lock=global" class="aaction">lock</a> + [% END %] + <a href="terminate?account_id=[% account.id %]" class="aaction">terminate</a> + [% END %] + </div> + [% END %] + <div class="p1"> + [% IF account.is_locked %] + <div class="alert">Account is LOCKED!</div> + [% END %] + [% IF messages.accmsg %]<div class="goodmsg">[% messages.accmsg %]</div>[% END %] + [% IF messages.accerr %]<div class="errormsg">[% messages.accerr %]</div>[% END %] + <form action="update_account" method="post"> + <input type="hidden" name="account_id" value="[% account.id %]" /> + <table> + <tr><td class="tdkey">ID:</td><td>[% account.id %]</td></tr> + + [% IF billing_features %] + <tr> + <td class="tdkey">customer:</td> + <td> + [% IF account.customer_id %] + <a href="/customer/detail?customer_id=[% account.customer_id %]">[% account.customer_id %]</a> + [% END %] + </td> + </tr> + <tr> + <td class="tdkey">product:</td> + <td> + [% IF edit_account %] + <select size="1" name="product"> + [% FOREACH product = products %] + <option[% IF product.name == account.product %] selected="selected"[% END %]>[% product.name %]</option> + [% END %] + </select> + [% ELSE %] + [% account.product %] + [% END %] + </td> + </tr> + <tr> + <td class="tdkey">billing profile:</td> + <td> + [% IF edit_account %] + <select size="1" name="billing_profile"> + [% FOREACH profile = billing_profiles %] + <option[% IF profile.name == account.billing_profile %] selected="selected"[% END %]>[% profile.name %]</option> + [% END %] + </select> + [% ELSE %] + [% account.billing_profile %] + [% END %] + </td> + </tr> + [% END %] + + <tr><td class="tdkey">created:</td><td>[% account.create_timestamp %]</td></tr> + <tr><td class="tdkey">modified:</td><td>[% account.modify_timestamp %]</td></tr> + [% IF account.terminate_timestamp %] + <tr><td class="tdkey">terminated:</td><td><div class="alert">[% account.terminate_timestamp %]</div></td></tr> + [% END %] + </table> + [% IF edit_account %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + + [% IF billing_features %] + <h3>Account Balance</h3> + + [% IF ! account.terminate_timestamp %] + <div class="actions"> + <a href="detail?account_id=[% account.id %]&edit_balance=1" class="aaction">edit</a> + [% IF edit_balance %] + <a href="detail?account_id=[% account.id %]" class="aaction">cancel</a> + [% END %] + </div> + [% END %] + <div class="p1"> + [% IF messages.balmsg %]<div class="goodmsg">[% messages.balmsg %]</div>[% END %] + [% IF messages.balerr %]<div class="errormsg">[% messages.balerr %]</div>[% END %] + Current totals + <form action="update_balance" method="post"> + <input type="hidden" name="account_id" value="[% account.id %]" /> + <table> + <tr> + <td class="tdkey">cash:</td> + <td class="nowrap">€ [% account.balance.cash_balance %]</td> + <td> + [% IF edit_balance %] + <div class="account_add">add <input type="text" name="add_cash" value="[% balanceadd.cash %]" /> Euro</div> + [% END %] + </td> + </tr> + [% IF messages.addcash %]<tr><td /><td /><td><div class="errormsg">[% messages.addcash %]</div></td></tr>[% END %] + <tr> + <td class="tdkey">free time:</td> + <td class="nowrap">[% account.balance.free_time_balance %] s</td> + <td> + [% IF edit_balance %] + <div class="account_add">add <input type="text" name="add_time" value="[% balanceadd.free_time %]" /> seconds</div> + [% END %] + </td> + </tr> + [% IF messages.addtime %]<tr><td /><td /><td><div class="errormsg">[% messages.addtime %]</div></td></tr>[% END %] + </table> + [% IF edit_balance %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + <div class="p1"> + Spent this billing interval + <table> + <tr><td class="tdkey">cash:</td><td>€ [% account.balance.cash_balance_interval %]</td></tr> + <tr><td class="tdkey">free time:</td><td>[% account.balance.free_time_balance_interval %] s</td></tr> + </table> + </div> + [% END %] + + <h3>Subscribers</h3> + + <div class="actions"> + <a href="/subscriber/detail?account_id=[% account.id %]&new=1" class="aaction">create new</a> + </div> + <div class="p1"> + [% IF account.subscribers %] + <table> + [% FOREACH subscriber = account.subscribers %] + <tr> + <td> + [% IF subscriber.subscriber_id %] + <a href="/subscriber/detail?subscriber_id=[% subscriber.subscriber_id %]"> + [% subscriber.username %]@[% subscriber.domain %]</a> + [% ELSE %] + [% subscriber.username %]@[% subscriber.domain %] + [% END %] + </td> + <td>[% IF subscriber.sn %]+[% subscriber.cc %] [% subscriber.ac %] [% subscriber.sn %][% END %]</td> + <td>[% subscriber.uuid %]</td> + </tr> + [% END %] + </table> + [% ELSE %] + <div> + This account does not have any subscribers yet. + </div> + [% END %] + </div> + diff --git a/root/tt/admin.tt b/root/tt/admin.tt new file mode 100644 index 0000000..a9414a0 --- /dev/null +++ b/root/tt/admin.tt @@ -0,0 +1,119 @@ + <h3>Manage Administrator Accounts</h3> + + <div class="p1"> + [% IF messages.eadmmsg %]<div class="goodmsg">[% messages.eadmmsg %]</div>[% END %] + [% IF messages.eadmerr %]<div class="errormsg">[% messages.eadmerr %]</div>[% END %] + + <table> + <tr class="table_header"> + <td>login</td> + <td>password</td> + <td>master</td> + <td>active</td> + <td /> + <td /> + </tr> + [% id = 0 %] + [% FOREACH admin = admins %] + <tr> + <td>[% admin.login %]</td> + [% IF admin.login == edit_admin %] + <form action="/admin/do_edit_admin" method="post"> + <input type="hidden" name="admin" value="[% admin.login %]" /> + <td><input type="password" name="password" value="" /></td> + <td class="tdcenter"> + <input type="checkbox" class="checkbox" name="is_master" + [% IF admin.login == Catalyst.session.admin.login %]disabled="disabled"[% END %] + [% IF erefill.is_master or !erefill && admin.is_master %]checked="checked" [% END %] /> + </td> + <td class="tdcenter"> + <input type="checkbox" class="checkbox" name="is_active" + [% IF admin.login == Catalyst.session.admin.login %]disabled="disabled"[% END %] + [% IF erefill.is_active or !erefill && admin.is_active %]checked="checked" [% END %] /> + </td> + <td> + <div class="postlink"> + <label for="admsave[% id %]">save</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="admsave[% id %]" /> + </div> + </td> + </form> + <td><a href="/admin" class="aaction">cancel</a></td> + [% ELSE %] + <td>********</td> + <td class="tdcenter"><input type="checkbox" class="checkbox" disabled="disabled"[% IF admin.is_master %] checked="checked"[% END %] /></td> + <td class="tdcenter"><input type="checkbox" class="checkbox" disabled="disabled"[% IF admin.is_active %] checked="checked"[% END %] /></td> + <td><a href="/admin?edit_admin=[% admin.login %]" class="aaction">edit</a></td> + [% IF admin.login != Catalyst.session.admin.login %] + <form action="/admin/do_delete_admin" method="post"> + <td> + <input type="hidden" name="admin" value="[% admin.login %]" /> + <div class="postlink"> + <label for="admdel[% id %]">delete</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="admdel[% id %]" /> + </div> + </td> + </form> + [% ELSE %] + <td /> + [% END %] + [% END %] + </tr> + [% IF admin.login == edit_admin %] + [% IF messages.epass %] + <tr><td colspan="5"> + <div class="errormsg"> + [% messages.epass %] + </div> + </td></tr> + [% END %] + [% END %] + [% id = id + 1 %] + [% END %] + </table> + </div> + + [% IF Catalyst.session.admin.is_master || Catalyst.session.admin.is_superuser %] + <h3>Create Administrator Account</h3> + + <div class="p1"> + [% IF messages.cadmmsg %]<div class="goodmsg">[% messages.cadmmsg %]</div>[% END %] + [% IF messages.cadmerr %]<div class="errormsg">[% messages.cadmerr %]</div>[% END %] + + <table> + <tr class="table_header"> + <td>login</td> + <td>password</td> + <td>master</td> + <td>active</td> + <td /> + <td /> + </tr> + <form action="/admin/do_create_admin" method="post"> + <tr> + <td><input type="text" size="20" name="admin" id="adminaddtxt" value="[% arefill.admin %]" /></td> + <td><input type="password" name="password" value="" /></td> + <td class="tdcenter"><input type="checkbox" class="checkbox" name="is_master"[% IF arefill.is_master %] checked="checked"[% END %] /></td> + <td class="tdcenter"><input type="checkbox" class="checkbox" name="is_active"[% IF arefill.is_active %] checked="checked"[% END %] /></td> + <td> + <div class="postlink"> + <label for="adminadd">Add</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="adminadd" /> + </div> + </td> + <td /> + </tr> + [% IF messages.alogin || messages.apass %] + <tr><td colspan="5"> + <div class="errormsg"> + [% messages.alogin %] + [% IF messages.alogin && messages.apass %]<br />[% END %] + [% messages.apass %] + </div> + </td></tr> + [% END %] + </form> + </table> + </div> + [% END %] + diff --git a/root/tt/customer.tt b/root/tt/customer.tt new file mode 100644 index 0000000..0be93c0 --- /dev/null +++ b/root/tt/customer.tt @@ -0,0 +1,58 @@ + <h3>Get by ID</h3> + <div class="p1"> + <form action="/customer/getbyid" method="GET"> + <input type="text" id="customer_id" name="customer_id" value="[% customer_id %]" /> + <input type="submit" value="Get »" class="but" /> + </form> + [% IF messages.accgeterr %]<div class="errormsg">[% messages.accgeterr %]</div>[% END %] + </div> + + <h3>Search by contacts</h3> + <div class="p1"> + <form action="/customer/search" method="post"> + <input type="text" id="search_string" name="search_string" value="[% search_string %]" /> + <input type="submit" value="Search »" class="but" /> + </form> + [% IF messages.accsearcherr %]<div class="errormsg">[% messages.accsearcherr %]</div>[% END %] + </div> +<!-- + <h3>Create new customer</h3> + <div class="p1"> + <form action="/customer/create_customer" method="post"> + <input type="submit" value="Create »" class="but" /> + </form> + </div> +--> + [% IF customer_list %] + + <h3>Customers</h3> + + <div class="p1"> + <table> + <tr class="table_header"><td>CustomerID</td><td>Name</td><td>Firma</td></tr> + + [% FOREACH customer = customer_list %] + + <tr> + <td><a href="/customer/detail?customer_id=[% customer.id %]"> + [% customer.id %] + </a></td> + <td><a href="/customer/detail?customer_id=[% customer.id %]"> + [% customer.contact.firstname %] [% customer.contact.lastname %] + </a></td> + [% IF customer.contact.company %] + <td><a href="/customer/detail?customer_id=[% customer.id %]"> + [% customer.contact.company %] + </a></td> + [% ELSE %] + <td /> + [% END %] + </tr> + + [% END %] + + </table> + </div> + + [% END %] + diff --git a/root/tt/customer_detail.tt b/root/tt/customer_detail.tt new file mode 100644 index 0000000..d2f5b72 --- /dev/null +++ b/root/tt/customer_detail.tt @@ -0,0 +1,378 @@ + <h3> Customer + <a class="noarrow" href="detail?customer_id=[% customer.id %]"> + #[% customer.id %]</a> + </h3> + + <div class="actions"> + <a href="detail?customer_id=[% customer.id %]&edit_customer=1" class="aaction">edit</a> + [% IF edit_customer %] + <a href="detail?customer_id=[% customer.id %]" class="aaction">cancel</a> + [% END %] + </div> + + <div class="p1"> + [% IF messages.custmsg %]<div class="goodmsg">[% messages.custmsg %]</div>[% END %] + [% IF messages.custerr %]<div class="errormsg">[% messages.custerr %]</div>[% END %] + <form action="update_customer" method="post"> + <input type="hidden" name="customer_id" value="[% customer.id %]" /> + <table> + <tr><td class="tdkey">ID:</td><td>[% customer.id %]</td></tr> + <tr> + <td class="tdkey">shopuser:</td> + <td> + <input type="text" id="shopuser" [% IF ! edit_customer %]class="disabled" disabled="disabled"[% END %] + name="shopuser" value="[% customer.shopuser %]" /> + </td> + </tr> + <tr> + <td class="tdkey">shoppass:</td> + <td> + [% IF edit_customer %] + <input type="text" id="shoppass" name="shoppass" value="[% customer.edit_shoppass %]" /> + [% ELSE %] + <input type="text" id="shoppass" name="shoppass" class="disabled" disabled="disabled" + [% IF show_pass %] + value="[% customer.shoppass %]" /> <a href="?customer_id=[% customer.id %]" class="apass">Hide</a> + [% ELSE %] + [% IF customer.shoppass %] + value="********" /> <a href="?customer_id=[% customer.id %]&show_pass=1" class="apass">Show</a> + [% ELSE %] + value="" /> + [% END %] + [% END %] + [% END %] + </td> + </tr> + <tr><td class="tdkey">created:</td><td>[% customer.create_timestamp %]</td></tr> + <tr><td class="tdkey">modified:</td><td>[% customer.modify_timestamp %]</td></tr> + </table> + [% IF edit_customer %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + + <h3 id="contact">Contact</h3> + + <div class="actions"> + <a href="detail?customer_id=[% customer.id %]&edit_contact=1#contact" class="aaction">edit</a> + [% IF edit_contact %] + <a href="detail?customer_id=[% customer.id %]#contact" class="aaction">cancel</a> + [% END %] + </div> + + <div class="p1"> + [% IF messages.contmsg %]<div class="goodmsg">[% messages.contmsg %]</div>[% END %] + [% IF messages.conterr %]<div class="errormsg">[% messages.conterr %]</div>[% END %] + <form action="update_contact#contact" method="post"> + <input type="hidden" name="ctype" value="contact" /> + <input type="hidden" name="customer_id" value="[% customer.id %]" /> + <table> + <tr> + <td class="tdkey">name:</td> + <td> + <select [% IF ! edit_contact %]class="disabled" disabled="disabled" [% END %] name="gender"> + <option value="male"[% IF customer.contact.gender == "male" %] selected="selected"[% END %]>Mr.</option> + <option value="female"[% IF customer.contact.gender == "female" %] selected="selected"[% END %]>Ms./Mrs.</option> + </select> + <input type="text" id="cont_firstname" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="firstname" value="[% customer.contact.firstname %]" /> + <input type="text" id="cont_lastname" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="lastname" value="[% customer.contact.lastname %]" /> + </td> + </tr> + <tr> + <td class="tdkey">comm. register num.:</td> + <td> + <input type="text" id="cont_comregnum" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="comregnum" value="[% customer.contact.comregnum %]" /> + </td> + </tr> + <tr> + <td class="tdkey">company:</td> + <td> + <input type="text" id="cont_company" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="company" value="[% customer.contact.company %]" /> + </td> + </tr> + <tr> + <td class="tdkey">street:</td> + <td> + <input type="text" id="cont_street" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="street" value="[% customer.contact.street %]" /> + </td> + </tr> + <tr> + <td class="tdkey">postcode / city:</td> + <td> + <input type="text" id="cont_postcode" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="postcode" value="[% customer.contact.postcode %]" /> + <input type="text" id="cont_city" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="city" value="[% customer.contact.city %]" /> + </td> + </tr> + <tr> + <td class="tdkey">phonenumber:</td> + <td> + <input type="text" id="cont_phonenumber" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="phonenumber" value="[% customer.contact.phonenumber %]" /> + </td> + </tr> + <tr> + <td class="tdkey">mobilenumber:</td> + <td> + <input type="text" id="cont_mobilenumber" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="mobilenumber" value="[% customer.contact.mobilenumber %]" /> + </td> + </tr> + <tr> + <td class="tdkey">email:</td> + <td> + <input type="text" id="cont_email" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="email" value="[% customer.contact.email %]" /> + </td> + </tr> + <tr> + <td class="tdkey">newsletter:</td> + <td> + <input type="checkbox" id="cont_newsletter" [% IF ! edit_contact %]class="disabled" disabled="disabled"[% END %] + name="newsletter" [% IF customer.contact.newsletter %]checked="checked"[% END %] /> + </td> + </tr> + <tr> + <td class="tdkey">created:</td> + <td> [% customer.contact.create_timestamp %]</td> + </tr> + <tr> + <td class="tdkey">modified:</td> + <td> [% customer.contact.modify_timestamp %]</td> + </tr> + </table> + [% IF edit_contact %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + + <h3 id="commercial">Commercial Contact</h3> + + <div class="actions"> + <a href="detail?customer_id=[% customer.id %]&edit_commercial=1#commercial" class="aaction">edit</a> + [% IF edit_commercial %] + <a href="detail?customer_id=[% customer.id %]#commercial" class="aaction">cancel</a> + [% END %] + </div> + + <div class="p1"> + [% IF messages.commmsg %]<div class="goodmsg">[% messages.commmsg %]</div>[% END %] + [% IF messages.commerr %]<div class="errormsg">[% messages.commerr %]</div>[% END %] + <form action="update_contact#commercial" method="post"> + <input type="hidden" name="ctype" value="comm_contact" /> + <input type="hidden" name="customer_id" value="[% customer.id %]" /> + <table> + <tr> + <td class="tdkey">name:</td> + <td> + <select [% IF ! edit_commercial %]class="disabled" disabled="disabled" [% END %] name="gender"> + <option value="male"[% IF customer.comm_contact.gender == "male" %] selected="selected"[% END %]>Mr.</option> + <option value="female"[% IF customer.comm_contact.gender == "female" %] selected="selected"[% END %]>Ms./Mrs.</option> + </select> + <input type="text" id="comm_firstname" [% IF ! edit_commercial %]class="disabled" disabled="disabled"[% END %] + name="firstname" value="[% customer.comm_contact.firstname %]" /> + <input type="text" id="comm_lastname" [% IF ! edit_commercial %]class="disabled" disabled="disabled"[% END %] + name="lastname" value="[% customer.comm_contact.lastname %]" /> + </td> + </tr> + <tr> + <td class="tdkey">phonenumber:</td> + <td> + <input type="text" id="comm_phonenumber" [% IF ! edit_commercial %]class="disabled" disabled="disabled"[% END %] + name="phonenumber" value="[% customer.comm_contact.phonenumber %]" /> + </td> + </tr> + <tr> + <td class="tdkey">mobilenumber:</td> + <td> + <input type="text" id="comm_mobilenumber" [% IF ! edit_commercial %]class="disabled" disabled="disabled"[% END %] + name="mobilenumber" value="[% customer.comm_contact.mobilenumber %]" /> + </td> + </tr> + <tr> + <td class="tdkey">email:</td> + <td> + <input type="text" id="comm_email" [% IF ! edit_commercial %]class="disabled" disabled="disabled"[% END %] + name="email" value="[% customer.comm_contact.email %]" /> + </td> + </tr> + <tr> + <td class="tdkey">newsletter:</td> + <td> + <input type="checkbox" id="comm_newsletter" [% IF ! edit_commercial %]class="disabled" disabled="disabled"[% END %] + name="newsletter" [% IF customer.comm_contact.newsletter %]checked="checked"[% END %] /> + </td> + </tr> + <tr> + <td class="tdkey">created:</td> + <td> [% customer.comm_contact.create_timestamp %]</td> + </tr> + <tr> + <td class="tdkey">modified:</td> + <td> [% customer.comm_contact.modify_timestamp %]</td> + </tr> + </table> + [% IF edit_commercial %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + + <h3 id="technical">Technical Contact</h3> + + <div class="actions"> + <a href="detail?customer_id=[% customer.id %]&edit_technical=1#technical" class="aaction">edit</a> + [% IF edit_technical %] + <a href="detail?customer_id=[% customer.id %]#technical" class="aaction">cancel</a> + [% END %] + </div> + + <div class="p1"> + [% IF messages.techmsg %]<div class="goodmsg">[% messages.techmsg %]</div>[% END %] + [% IF messages.techerr %]<div class="errormsg">[% messages.techerr %]</div>[% END %] + <form action="update_contact#technical" method="post"> + <input type="hidden" name="ctype" value="tech_contact" /> + <input type="hidden" name="customer_id" value="[% customer.id %]" /> + <table> + <tr> + <td class="tdkey">name:</td> + <td> + <select [% IF ! edit_technical %]class="disabled" disabled="disabled" [% END %] name="gender"> + <option value="male"[% IF customer.tech_contact.gender == "male" %] selected="selected"[% END %]>Mr.</option> + <option value="female"[% IF customer.tech_contact.gender == "female" %] selected="selected"[% END %]>Ms./Mrs.</option> + </select> + <input type="text" id="tech_firstname" [% IF ! edit_technical %]class="disabled" disabled="disabled"[% END %] + name="firstname" value="[% customer.tech_contact.firstname %]" /> + <input type="text" id="tech_lastname" [% IF ! edit_technical %]class="disabled" disabled="disabled"[% END %] + name="lastname" value="[% customer.tech_contact.lastname %]" /> + </td> + </tr> + <tr> + <td class="tdkey">phonenumber:</td> + <td> + <input type="text" id="tech_phonenumber" [% IF ! edit_technical %]class="disabled" disabled="disabled"[% END %] + name="phonenumber" value="[% customer.tech_contact.phonenumber %]" /> + </td> + </tr> + <tr> + <td class="tdkey">mobilenumber:</td> + <td> + <input type="text" id="tech_mobilenumber" [% IF ! edit_technical %]class="disabled" disabled="disabled"[% END %] + name="mobilenumber" value="[% customer.tech_contact.mobilenumber %]" /> + </td> + </tr> + <tr> + <td class="tdkey">email:</td> + <td> + <input type="text" id="tech_email" [% IF ! edit_technical %]class="disabled" disabled="disabled"[% END %] + name="email" value="[% customer.tech_contact.email %]" /> + </td> + </tr> + <tr> + <td class="tdkey">newsletter:</td> + <td> + <input type="checkbox" id="tech_newsletter" [% IF ! edit_technical %]class="disabled" disabled="disabled"[% END %] + name="newsletter" [% IF customer.tech_contact.newsletter %]checked="checked"[% END %] /> + </td> + </tr> + <tr> + <td class="tdkey">created:</td> + <td> [% customer.tech_contact.create_timestamp %]</td> + </tr> + <tr> + <td class="tdkey">modified:</td> + <td> [% customer.tech_contact.modify_timestamp %]</td> + </tr> + </table> + [% IF edit_technical %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + + <h3>Contracts</h3> + +<!-- <div class="actions"> + <a href="/subscriber/detail?customer_id=[% customer.id %]&new=1" class="aaction">create new</a> + </div> +--> <div class="p1"> + [% IF customer.contracts %] + <table> + <tr> + <td class="table_header">ContractID</td> + <td class="table_header">Product</td> + <td class="table_header">status</td> + <td class="table_header">created</td> + </tr> + [% FOREACH contract = customer.contracts %] + <tr> + <td> + [% IF contract.class == 'voip' %] + <a href="/account/detail?account_id=[% contract.id %]"> + [% contract.id %] + </a> + [% ELSE %] + [% contract.id %] + [% END %] + </td> + <td> + [% IF contract.class == 'voip' %] + <a href="/account/detail?account_id=[% contract.id %]"> + [% contract.product %] + </a> + [% ELSE %] + [% contract.product %] + [% END %] + </td> + <td>[% contract.status %]</td> + <td>[% contract.create_timestamp %]</td> + </tr> + [% END %] + </table> + [% ELSE %] + <div> + This customer does not have any contracts yet. + </div> + [% END %] + </div> + + <h3>Orders</h3> + +<!-- <div class="actions"> + <a href="/subscriber/detail?customer_id=[% customer.id %]&new=1" class="aaction">create new</a> + </div> +--> <div class="p1"> + [% IF customer.orders %] + <table> + <tr> + <td class="table_header">OrderID</td> + <td class="table_header">status</td> + <td class="table_header">created</td> + <td class="table_header">modified</td> + <td class="table_header">completed</td> + </tr> + [% FOREACH order = customer.orders %] + <tr> + <td>[% order.id %]</td> + <td>[% order.status %]</td> + <td>[% order.create_timestamp %]</td> + <td>[% order.modify_timestamp %]</td> + <td>[% order.complete_timestamp %]</td> + </tr> + [% END %] + </table> + [% ELSE %] + <div> + This customer does not have any orders yet. + </div> + [% END %] + </div> + diff --git a/root/tt/default.tt b/root/tt/default.tt new file mode 100644 index 0000000..e69de29 diff --git a/root/tt/domain.tt b/root/tt/domain.tt new file mode 100644 index 0000000..a1090f1 --- /dev/null +++ b/root/tt/domain.tt @@ -0,0 +1,104 @@ + <h3>Edit Domains</h3> + + <div class="p1"> + [% IF messages.edommsg %]<div class="goodmsg">[% messages.edommsg %]</div>[% END %] + [% IF messages.edomerr %]<div class="errormsg">[% messages.edomerr %]</div>[% END %] + + <table> + <tr class="table_header"> + <td>domain</td> + <td>country code</td> + <td>timezone</td> + <td /> + <td /> + </tr> + [% id = 0 %] + [% FOREACH domain = domains %] + <tr> + <td>[% domain.domain %]</td> + [% IF domain.domain == edit_domain %] + <form action="/domain/do_edit_domain" method="post"> + <input type="hidden" name="domain" value="[% domain.domain %]" /> + <td class="tdcenter"><input class="ishort" name="cc" value="[% domain.cc %]" /></td> + <td><input name="timezone" value="[% domain.timezone %]" /></td> + <td> + <div class="postlink"> + <label for="domsave[% id %]">save</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="domsave[% id %]" /> + </div> + </td> + </form> + <td><a href="/domain" class="aaction">cancel</a></td> + [% ELSE %] + <td class="tdcenter">[% domain.cc %]</td> + <td>[% domain.timezone %]</td> + <td><a href="/domain?edit_domain=[% domain.domain %]" class="aaction">edit</a></td> + <form action="/domain/do_delete_domain" method="post"> + <input type="hidden" name="domain" value="[% domain.domain %]" /> + <td> + <div class="postlink"> + <label for="domdel[% id %]">delete</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="domdel[% id %]" /> + </div> + </td> + </form> + [% END %] + </tr> + [% IF domain.domain == edit_domain %] + [% IF messages.ecc || messages.etimezone %] + <tr><td colspan="5"> + <div class="errormsg"> + [% messages.ecc %] + [% IF messages.ecc && messages.etimezone %]<br />[% END %] + [% messages.etimezone %] + </div> + </td></tr> + [% END %] + [% END %] + [% id = id + 1 %] + [% END %] + </table> + </div> + + <h3>Create domain</h3> + + <div class="p1"> + [% IF messages.cdommsg %]<div class="goodmsg">[% messages.cdommsg %]</div>[% END %] + [% IF messages.cdomerr %]<div class="errormsg">[% messages.cdomerr %]</div>[% END %] + + <table> + <tr class="table_header"> + <td>domain</td> + <td>country code</td> + <td>timezone</td> + <td /> + <td /> + </tr> + <form action="/domain/do_create_domain" method="post"> + <tr> + <td><input type="text" size="20" name="domain" id="domainaddtxt" value="[% arefill.domain %]" /></td> + <td class="tdcenter"><input class="ishort" name="cc" value="[% arefill.cc %]" /></td> + <td><input name="timezone" value="[% arefill.timezone %]" /></td> + <td> + <div class="postlink"> + <label for="domainadd">Add</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="domainadd" /> + </div> + </td> + <td /> + </tr> + [% IF messages.acc || messages.atimezone %] + <tr><td colspan="5"> + <div class="errormsg"> + [% messages.acc %] + [% IF messages.acc && messages.atimezone %]<br />[% END %] + [% messages.atimezone %] + </div> + </td></tr> + [% END %] + </form> + + </table> + + </div> + diff --git a/root/tt/login.tt b/root/tt/login.tt new file mode 100644 index 0000000..93c873c --- /dev/null +++ b/root/tt/login.tt @@ -0,0 +1,10 @@ + <h3>Login</h3> + <div class="p1"> + <form id="login" action="/login" method="post"> + <label for="login_user">Username</label><br /> + <input type="text" id="login_user" name="username" /><br /> + <label for="login_pass">Password</label><br /> + <input type="password" id="login_pass" name="password" /><br /> + <input type="submit" value="Login »" class="but" /> + </form> + </div> diff --git a/root/tt/subscriber.tt b/root/tt/subscriber.tt new file mode 100644 index 0000000..ff3d1a7 --- /dev/null +++ b/root/tt/subscriber.tt @@ -0,0 +1,83 @@ + <h3>Search Subscribers</h3> + <div class="p1"> + <form action="/subscriber/search" method="post"> + <input type="text" id="search_subscriber" name="search_string" value="[% search_string %]" /> + <input type="submit" value="Search »" class="but" /> + </form> + </div> + + [% IF subscriber_list %] + + <h3>Subscribers</h3> + + <div class="p1"> + <table> + <tr><td>SIP URI</td><td class="tdcenter">Account ID</td></tr> + + [% FOREACH subscriber = subscriber_list %] + + <tr> + <td> + [% IF subscriber.subscriber_id %] + <a href="/subscriber/detail?subscriber_id=[% subscriber.subscriber_id %]"> + [% subscriber.username %]@[% subscriber.domain %]</a> + [% ELSE %] + [% subscriber.username %]@[% subscriber.domain %] + [% END %] + </td> + <td class="tdcenter"> + <a href="/account/detail?account_id=[% subscriber.account_id %]"> + [% subscriber.account_id %]</a> + </td> + </tr> + + [% END %] + </table> + + [% IF pagination %] + <div class="pagination"> + <ul> + [% IF offset == 0 %] + <li class="disablepage">« prev</li> + [% ELSE %] + <li class="nextpage"> + <a href="/subscriber/search?search_string=[% search_string %]&offset=[% offset - 1 %]">« prev</a> + </li> + [% END %] + [% FOREACH pagine = pagination %] + [% IF pagine.offset == offset %] + <li class="currentpage"> + [% pagine.offset + 1 %] + </li> + [% ELSIF pagine.offset == -1 %] + ... + [% ELSE %] + <li> + <a href="/subscriber/search?search_string=[% search_string + %]&offset=[% pagine.offset %]">[% pagine.offset + 1 %]</a> + </li> + [% END %] + [% END %] + [% IF offset >= max_offset %] + <li class="disablepage"> + next » + </li> + [% ELSE %] + <li class="nextpage"> + <a href="/subscriber/search?search_string=[% search_string %]&offset=[% offset + 1 %]">next »</a> + </li> + [% END %] + </ul> + </div> + [% END %] + + </div> + + [% ELSIF searched %] + + <div class="p1"> + No matching subscribers found. + </div> + + [% END %] + diff --git a/root/tt/subscriber_detail.tt b/root/tt/subscriber_detail.tt new file mode 100644 index 0000000..468b695 --- /dev/null +++ b/root/tt/subscriber_detail.tt @@ -0,0 +1,333 @@ + [% IF subscriber.subscriber_id %] + <h3> Subscriber + <a class="noarrow" href="detail?subscriber_id=[% subscriber.subscriber_id %]"> + [% subscriber.username %]@[% subscriber.domain %]</a> + </h3> + + <div class="actions"> + <a href="detail?subscriber_id=[% subscriber.subscriber_id %]&edit_subscriber=1" class="aaction">edit</a> + [% ELSE %] + <h3>New subscriber</h3> + + <div class="actions"> + [% END %] + [% IF edit_subscriber %] + [% IF subscriber.subscriber_id %] + <a href="detail?subscriber_id=[% subscriber.subscriber_id %]" class="aaction">cancel</a> + [% ELSE %] + <a href="/account/detail?account_id=[% account_id %]" class="aaction">cancel</a> + [% END %] + [% ELSE %] + <script language="JavaScript" src="/js/openclose.js" type="text/javascript"></script> + <a href="javascript:;" class="aaction" onclick="javascript:openclose();" onkeypress="javascript:openclose();">lock</a> + <span id="lock_menu" class="floating" onmouseout="javascript:openclose();"> + <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&lock=none" class="aaction">unlock</a><br /> + <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&lock=foreign" class="aaction">foreign</a><br /> + <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&lock=outgoing" class="aaction">all outgoing</a><br /> + <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&lock=incoming" class="aaction">incoming and outgoing</a><br /> + <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&lock=global" class="aaction">global</a><br /> + </span> + <a href="terminate?subscriber_id=[% subscriber.subscriber_id %]" class="aaction">terminate</a> + [% END %] + </div> + <div class="p1"> + [% IF subscriber.is_locked %] + <div class="alert">[% subscriber.is_locked %]</div> + [% END %] + [% IF messages.submsg %]<div class="goodmsg">[% messages.submsg %]</div>[% END %] + [% IF messages.suberr %]<div class="errormsg">[% messages.suberr %]</div>[% END %] + <form action="update_subscriber" method="post"> + <input type="hidden" name="subscriber_id" value="[% subscriber.subscriber_id %]" /> + <input type="hidden" name="account_id" value="[% account_id %]" /> + <table class="table"> + <tr><td class="tdkey">ID:</td><td>[% subscriber.subscriber_id %]</td></tr> + <tr> + <td class="tdkey">account ID:</td> + <td> + <a href="/account/detail?account_id=[% subscriber.account_id || account_id %]"> + [% subscriber.account_id || account_id %]</a> + </td> + </tr> + <tr><td class="tdkey">user:</td><td>[% subscriber.webusername %]</td></tr> + [% IF subscriber.subscriber_id %] + <tr><td class="tdkey">SIP URI:</td><td>[% subscriber.username %]@[% subscriber.domain %]</td></tr> + [% ELSE %] + <tr> + <td class="tdkey">SIP URI:</td> + <td> + <input type="text" name="username" value="[% subscriber.username %]" /> + @ + <select size="1" name="domain"> + [% FOREACH sdom = domains %] + <option>[% sdom.domain %]</option> + [% END %] + </select> + </td> + </tr> + [% END %] + <tr> + <td class="tdkey">E.164 number:</td> + <td> + [% IF edit_subscriber %] + <input type="text" name="cc" class="ishort" value="[% subscriber.cc %]" /> + <input type="text" name="ac" class="ishort" value="[% subscriber.ac %]" /> + <input type="text" name="sn" value="[% subscriber.sn %]" /> + [% ELSE %] + <input type="text" class="disabled" disabled="disabled" + [% IF subscriber.sn %] + value="+[% subscriber.cc %] [% subscriber.ac %] [% subscriber.sn %]" /> + [% ELSE %] + value="" /> + [% END %] + [% END %] + </td> + </tr> + [% IF messages.number %]<tr><td /><td><div class="errormsg">[% messages.number %]</div></td></tr>[% END %] + [% IF messages.number_cc %]<tr><td /><td><div class="errormsg">[% messages.number_cc %]</div></td></tr>[% END %] + [% IF messages.number_ac %]<tr><td /><td><div class="errormsg">[% messages.number_ac %]</div></td></tr>[% END %] + [% IF messages.number_sn %]<tr><td /><td><div class="errormsg">[% messages.number_sn %]</div></td></tr>[% END %] + <tr> + <td class="tdkey">web password:</td> + <td> + [% IF edit_subscriber %] + <input type="text" name="webpassword" id="edit_webpass" value="[% subscriber.edit_webpass %]" /> + [% ELSE %] + <input type="text" id="edit_webpass" class="disabled" disabled="disabled" + [% IF show_webpass %] + value="[% subscriber.webpassword %]" /> <a href="?subscriber_id=[% subscriber.subscriber_id %]" class="apass">Hide</a> + [% ELSE %] + [% IF subscriber.webpassword %] + value="********" /> <a href="?subscriber_id=[% subscriber.subscriber_id %]&show_webpass=1" class="apass">Show</a> + [% ELSE %] + value="" /> + [% END %] + [% END %] + [% END %] + </td> + </tr> + <tr> + <td class="tdkey">SIP password:</td> + <td> + [% IF edit_subscriber %] + <input type="text" name="password" id="edit_pass" value="[% subscriber.edit_pass %]" /> + [% ELSE %] + <input type="text" id="edit_pass" class="disabled" disabled="disabled" + [% IF show_pass %] + value="[% subscriber.password %]" /> <a href="?subscriber_id=[% subscriber.subscriber_id %]" class="apass">Hide</a> + [% ELSE %] + [% IF subscriber.password %] + value="********" /> <a href="?subscriber_id=[% subscriber.subscriber_id %]&show_pass=1" class="apass">Show</a> + [% ELSE %] + value="" /> + [% END %] + [% END %] + [% END %] + </td> + </tr> + [% IF messages.password %]<tr><td /><td><div class="errormsg">[% messages.password %]</div></td></tr>[% END %] + <tr> + <td class="tdkey">administrative:</td> + <td> + <input type="checkbox" name="admin" class="checkbox" [% IF ! edit_subscriber %]disabled="disabled"[% END %] + [% IF subscriber.admin %]checked="checked"[% END %] /> + </td> + </tr> + <tr> + <td class="tdkey">timezone:</td> + <td> + <input type="text" name="timezone" [% IF ! edit_subscriber %]class="disabled" disabled="disabled"[% END %] + value="[% subscriber.timezone %]" /> + </td> + </tr> + [% IF messages.timezone %]<tr><td /><td><div class="errormsg">[% messages.timezone %]</div></td></tr>[% END %] + <tr><td class="tdkey">UUID:</td><td>[% subscriber.uuid %]</td></tr> + <tr><td class="tdkey">created:</td><td>[% subscriber.create_timestamp %]</td></tr> + <tr><td class="tdkey">modified:</td><td>[% subscriber.modify_timestamp %]</td></tr> + </table> + + [% IF edit_subscriber %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + + <h3 id="userprefs">User Preferences</h3> + + <div class="actions"> + <a href="detail?subscriber_id=[% subscriber.subscriber_id %]&edit_preferences=1#userprefs" class="aaction">edit</a> + [% IF edit_preferences %] + <a href="detail?subscriber_id=[% subscriber.subscriber_id %]#userprefs" class="aaction">cancel</a> + [% END %] + </div> + <div class="p1"> + [% IF messages.prefmsg %]<div class="goodmsg">[% messages.prefmsg %]</div>[% END %] + [% IF messages.preferr %]<div class="errormsg">[% messages.preferr %]</div>[% END %] + <form action="update_preferences" method="post"> + <input type="hidden" name="subscriber_id" value="[% subscriber.subscriber_id %]" /> + <table> + [% FOREACH preference = subscriber.preferences_array %] + [% IF preference.max_occur == 1 %] + [% IF preference.key == "block_in_mode" || preference.key == "block_out_mode" %] + <tr> + <td class="tdkey">[% preference.key %]:</td> + <td> + <select size="1" name="[% preference.key %]" + [% IF ! edit_preferences %]class="disabled" disabled="disabled"[% END %] > + <option [% IF ! preference.value %]selected="selected"[% END %]>blacklist</option> + <option [% IF preference.value %]selected="selected"[% END %]>whitelist</option> + </select> + </td> + </tr> + [% ELSIF preference.key == "block_in_clir" + || preference.key == "clir" + || preference.key == "cfu" + || preference.key == "cfb" + || preference.key == "cft" + || preference.key == "cfna" %] + <tr> + <td class="tdkey">[% preference.key %]:</td> + <td> + <input type="checkbox" name="[% preference.key %]" class="checkbox" + [% IF ! edit_preferences %]disabled="disabled"[% END %] + [% IF preference.value %]checked="checked"[% END %] /> + </td> + </tr> + [% ELSIF preference.key == "prepaid" + || preference.key == "has_extension" %] + <tr> + <td class="tdkey">[% preference.key %]:</td> + <td> + <input type="checkbox" name="[% preference.key %]" class="checkbox" + disabled="disabled" [% IF preference.value %]checked="checked"[% END %] /> + </td> + </tr> + [% ELSIF preference.key == "cftarget" %] + <tr> + <td class="tdkey">[% preference.key %]:</td> + <td> + [% IF edit_preferences %] + <label for="cfvoicebox"> + <input type="radio" id="cfvoicebox" value="voicebox" name="fw_target" class="radio" + [% IF ! preference.value.sipuri %]checked="checked"[% END %] /> + Voicebox + </label> + <br clear="all" /> + <label for="cfsipuri"> + <input type="radio" id="cfsipuri" value="sipuri" name="fw_target" class="radio" + [% IF preference.value.sipuri %]checked="checked"[% END %] /> + number or SIP-URI: + </label> + <input type="text" id="cfsipuritxt" name="fw_sipuri" size="25" value="[% preference.value.sipuri %]" + [% IF ! edit_preferences %]class="disabled" disabled="disabled"[% END %] /> + [% ELSE %] + [% IF preference.value.sipuri %] + <input type="text" size="25" value="[% preference.value.sipuri %]" + class="disabled" disabled="disabled" /> + [% ELSE %] + Voicebox + [% END %] + [% END %] + </td> + </tr> + [% IF messages.target %]<tr><td /><td><div class="errormsg">[% messages.target %]</div></td></tr>[% END %] + [% ELSIF preference.key == "base_cli" + || preference.key == "extension" + || preference.key == "lock" + || preference.key == "base_user" %] + <tr> + <td class="tdkey">[% preference.key %]:</td> + <td> + <input type="text" name="[% preference.key %]" + class="disabled txtpreference" disabled="disabled" + value="[% preference.value %]" /> + </td> + </tr> + [% ELSE %] + <tr> + <td class="tdkey">[% preference.key %]:</td> + <td> + <input type="text" name="[% preference.key %]" [% IF ! edit_preferences %] + class="disabled txtpreference" disabled="disabled" + [% ELSE %] + class="txtpreference" + [% END %] + value="[% preference.value %]" /> + </td> + </tr> + [% END %] + [% ELSE %] + <tr> + <td class="tdkey">[% preference.key %]:</td> + <td> + [% IF preference.value %] + <select size="1" name="[% preference.key %]"> + [% FOREACH pref_entry = preference.value %] + <option>[% pref_entry %]</option> + [% END %] + </select> + [% ELSE %] + <select size="1" name="[% preference.key %]"> + <option /> + </select> + [% END %] + [% IF ! edit_preferences %] + + <a href="edit_list?subscriber_id=[% subscriber.subscriber_id %]&list_name=[% preference.key %]" + class="aaction">edit list</a> + [% END %] + </td> + </tr> + [% END %] + [% IF preference.error %]<tr><td /><td><div class="errormsg">[% preference.error %]</div></td></tr>[% END %] + [% END %] + </table> + [% IF edit_preferences %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + + <h3 id="vboxprefs">Voicebox Preferences</h3> + + <div class="actions"> + <a href="detail?subscriber_id=[% subscriber.subscriber_id %]&edit_voicebox=1#vboxprefs" class="aaction">edit</a> + [% IF edit_voicebox %] + <a href="detail?subscriber_id=[% subscriber.subscriber_id %]#vboxprefs" class="aaction">cancel</a> + [% END %] + </div> + <div class="p1"> + [% IF messages.vboxmsg %]<div class="goodmsg">[% messages.vboxmsg %]</div>[% END %] + [% IF messages.vboxerr %]<div class="errormsg">[% messages.vboxerr %]</div>[% END %] + <form action="update_voicebox" method="post"> + <input type="hidden" name="subscriber_id" value="[% subscriber.subscriber_id %]" /> + <table> + <tr> + <td class="tdkey">PIN:</td> + <td> + <input type="text" name="password" [% IF ! edit_voicebox %]class="disabled" disabled="disabled"[% END %] + value="[% subscriber.voicebox_preferences.password %]" /> + </td> + </tr> + [% IF messages.vpin %]<tr><td /><td><div class="errormsg">[% messages.vpin %]</div></td></tr>[% END %] + <tr> + <td class="tdkey">E-Mail:</td> + <td> + <input type="text" name="email" [% IF ! edit_voicebox %]class="disabled" disabled="disabled"[% END %] + value="[% subscriber.voicebox_preferences.email %]" /> + </td> + </tr> + [% IF messages.vemail %]<tr><td /><td><div class="errormsg">[% messages.vemail %]</div></td></tr>[% END %] + <tr> + <td class="tdkey">Attach WAV:</td> + <td> + <input type="checkbox" name="attach" class="checkbox" [% IF ! edit_voicebox %]disabled="disabled"[% END %] + [% IF subscriber.voicebox_preferences.attach %]checked="checked"[% END %] /> + </td> + </tr> + </table> + [% IF edit_voicebox %] + <input type="submit" class="but" value="Save »" /> + [% END %] + </form> + </div> + diff --git a/root/tt/subscriber_edit_list.tt b/root/tt/subscriber_edit_list.tt new file mode 100644 index 0000000..db647f0 --- /dev/null +++ b/root/tt/subscriber_edit_list.tt @@ -0,0 +1,69 @@ + <h3>Edit [% list_name %] for + <a class="noarrow" href="detail?subscriber_id=[% subscriber_id %]"> + [% subscriber.username %]@[% subscriber.domain %]</a> + </h3> + + <div class="actions"> + <a href="detail?subscriber_id=[% subscriber_id %]" class="aaction">back</a> + </div> + <div class="p1"> + [% IF messages.nummsg %]<div class="goodmsg">[% messages.nummsg %]</div>[% END %] + [% IF messages.numerr %]<div class="errormsg">[% messages.numerr %]</div>[% END %] + + <table> + [% FOREACH blockentry = list_data %] + <tr class="[% blockentry.background %]"> + <td > [% blockentry.number %] </td> + <form action="do_edit_list" method="post"> + <td > + <input type="hidden" name="subscriber_id" value="[% subscriber_id %]" /> + <input type="hidden" name="list_name" value="[% list_name %]" /> + <input type="hidden" name="block_del" value="[% blockentry.number %]" /> + <input type="hidden" name="block_stat" value="[% blockentry.active %]" /> + <div class="postlink"> + <label for="listdel[% blockentry.id %]">delete</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="listdel[% blockentry.id %]" /> + </div> + </td> + </form> + <form action="do_edit_list" method="post"> + <td> + <input type="hidden" name="subscriber_id" value="[% subscriber_id %]" /> + <input type="hidden" name="list_name" value="[% list_name %]" /> + [% IF blockentry.active == 1 %] + <input type="hidden" name="block_act" value="[% blockentry.number %]" /> + <input type="hidden" name="block_stat" value="1" /> + <div class="postlink"> + <label for="blockact[% blockentry.id %]">deactivate</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="blockact[% blockentry.id %]" /> + </div> + [% ELSIF blockentry.active == 0 %] + <input type="hidden" name="block_act" value="[% blockentry.number %]" /> + <input type="hidden" name="block_stat" value="0" /> + <div class="postlink"> + <label for="blockact[% blockentry.id %]">activate</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="blockact[% blockentry.id %]" /> + </div> + [% END %] + </td> + </form> + </tr> + [% END %] + </table> + + <br /> + + <form action="/subscriber/do_edit_list" method="post"> + <input type="hidden" name="subscriber_id" value="[% subscriber_id %]" /> + <input type="hidden" name="list_name" value="[% list_name %]" /> + <label for="blockaddtxt">New entry:</label><br /> + <div class="postlink"> + <input type="text" size="20" name="block_add" id="blockaddtxt" value="[% blockaddtxt %]" /> + <label for="blockadd">Add entry</label> + <input type="image" class="hidden" src="/static/images/dot_trans.gif" alt="" id="blockadd" /> + [% IF messages.msgadd %]<div class="errormsg">[% messages.msgadd %]</div>[% END %] + </div><br clear="all" /> + </form> + + </div> + diff --git a/script/admin_cgi.pl b/script/admin_cgi.pl new file mode 100755 index 0000000..85bc580 --- /dev/null +++ b/script/admin_cgi.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl -w + +BEGIN { $ENV{CATALYST_ENGINE} ||= 'CGI' } + +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use admin; + +admin->run; + +1; + +=head1 NAME + +admin_cgi.pl - Catalyst CGI + +=head1 SYNOPSIS + +See L<Catalyst::Manual> + +=head1 DESCRIPTION + +Run a Catalyst application as a cgi script. + +=head1 AUTHOR + +Sebastian Riedel, C<sri@oook.de> + +=head1 COPYRIGHT + + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/script/admin_create.pl b/script/admin_create.pl new file mode 100755 index 0000000..376c2eb --- /dev/null +++ b/script/admin_create.pl @@ -0,0 +1,75 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use Catalyst::Helper; + +my $force = 0; +my $mech = 0; +my $help = 0; + +GetOptions( + 'nonew|force' => \$force, + 'mech|mechanize' => \$mech, + 'help|?' => \$help + ); + +pod2usage(1) if ( $help || !$ARGV[0] ); + +my $helper = Catalyst::Helper->new( { '.newfiles' => !$force, mech => $mech } ); + +pod2usage(1) unless $helper->mk_component( 'admin', @ARGV ); + +1; + +=head1 NAME + +admin_create.pl - Create a new Catalyst Component + +=head1 SYNOPSIS + +admin_create.pl [options] model|view|controller name [helper] [options] + + Options: + -force don't create a .new file where a file to be created exists + -mechanize use Test::WWW::Mechanize::Catalyst for tests if available + -help display this help and exits + + Examples: + admin_create.pl controller My::Controller + admin_create.pl controller My::Controller BindLex + admin_create.pl -mechanize controller My::Controller + admin_create.pl view My::View + admin_create.pl view MyView TT + admin_create.pl view TT TT + admin_create.pl model My::Model + admin_create.pl model SomeDB DBIC::Schema MyApp::Schema create=dynamic\ + dbi:SQLite:/tmp/my.db + admin_create.pl model AnotherDB DBIC::Schema MyApp::Schema create=static\ + dbi:Pg:dbname=foo root 4321 + + See also: + perldoc Catalyst::Manual + perldoc Catalyst::Manual::Intro + +=head1 DESCRIPTION + +Create a new Catalyst Component. + +Existing component files are not overwritten. If any of the component files +to be created already exist the file will be written with a '.new' suffix. +This behavior can be suppressed with the C<-force> option. + +=head1 AUTHOR + +Sebastian Riedel, C<sri@oook.de> +Maintained by the Catalyst Core Team. + +=head1 COPYRIGHT + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/script/admin_fastcgi.pl b/script/admin_fastcgi.pl new file mode 100755 index 0000000..770d857 --- /dev/null +++ b/script/admin_fastcgi.pl @@ -0,0 +1,80 @@ +#!/usr/bin/perl -w + +BEGIN { $ENV{CATALYST_ENGINE} ||= 'FastCGI' } + +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use admin; + +my $help = 0; +my ( $listen, $nproc, $pidfile, $manager, $detach, $keep_stderr ); + +GetOptions( + 'help|?' => \$help, + 'listen|l=s' => \$listen, + 'nproc|n=i' => \$nproc, + 'pidfile|p=s' => \$pidfile, + 'manager|M=s' => \$manager, + 'daemon|d' => \$detach, + 'keeperr|e' => \$keep_stderr, +); + +pod2usage(1) if $help; + +admin->run( + $listen, + { nproc => $nproc, + pidfile => $pidfile, + manager => $manager, + detach => $detach, + keep_stderr => $keep_stderr, + } +); + +1; + +=head1 NAME + +admin_fastcgi.pl - Catalyst FastCGI + +=head1 SYNOPSIS + +admin_fastcgi.pl [options] + + Options: + -? -help display this help and exits + -l -listen Socket path to listen on + (defaults to standard input) + can be HOST:PORT, :PORT or a + filesystem path + -n -nproc specify number of processes to keep + to serve requests (defaults to 1, + requires -listen) + -p -pidfile specify filename for pid file + (requires -listen) + -d -daemon daemonize (requires -listen) + -M -manager specify alternate process manager + (FCGI::ProcManager sub-class) + or empty string to disable + -e -keeperr send error messages to STDOUT, not + to the webserver + +=head1 DESCRIPTION + +Run a Catalyst application as fastcgi. + +=head1 AUTHOR + +Sebastian Riedel, C<sri@oook.de> +Maintained by the Catalyst Core Team. + +=head1 COPYRIGHT + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/script/admin_server.pl b/script/admin_server.pl new file mode 100755 index 0000000..f0bc004 --- /dev/null +++ b/script/admin_server.pl @@ -0,0 +1,111 @@ +#!/usr/bin/perl -w + +BEGIN { + $ENV{CATALYST_ENGINE} ||= 'HTTP'; + $ENV{CATALYST_SCRIPT_GEN} = 30; + require Catalyst::Engine::HTTP; +} + +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use FindBin; +use lib "$FindBin::Bin/../lib"; + +my $debug = 0; +my $fork = 0; +my $help = 0; +my $host = undef; +my $port = $ENV{ADMIN_PORT} || $ENV{CATALYST_PORT} || 3000; +my $keepalive = 0; +my $restart = $ENV{ADMIN_RELOAD} || $ENV{CATALYST_RELOAD} || 0; +my $restart_delay = 1; +my $restart_regex = '\.yml$|\.yaml$|\.pm$'; +my $restart_directory = undef; + +my @argv = @ARGV; + +GetOptions( + 'debug|d' => \$debug, + 'fork' => \$fork, + 'help|?' => \$help, + 'host=s' => \$host, + 'port=s' => \$port, + 'keepalive|k' => \$keepalive, + 'restart|r' => \$restart, + 'restartdelay|rd=s' => \$restart_delay, + 'restartregex|rr=s' => \$restart_regex, + 'restartdirectory=s' => \$restart_directory, +); + +pod2usage(1) if $help; + +if ( $restart && $ENV{CATALYST_ENGINE} eq 'HTTP' ) { + $ENV{CATALYST_ENGINE} = 'HTTP::Restarter'; +} +if ( $debug ) { + $ENV{CATALYST_DEBUG} = 1; +} + +# This is require instead of use so that the above environment +# variables can be set at runtime. +require admin; + +admin->run( $port, $host, { + argv => \@argv, + 'fork' => $fork, + keepalive => $keepalive, + restart => $restart, + restart_delay => $restart_delay, + restart_regex => qr/$restart_regex/, + restart_directory => $restart_directory, +} ); + +1; + +=head1 NAME + +admin_server.pl - Catalyst Testserver + +=head1 SYNOPSIS + +admin_server.pl [options] + + Options: + -d -debug force debug mode + -f -fork handle each request in a new process + (defaults to false) + -? -help display this help and exits + -host host (defaults to all) + -p -port port (defaults to 3000) + -k -keepalive enable keep-alive connections + -r -restart restart when files get modified + (defaults to false) + -rd -restartdelay delay between file checks + -rr -restartregex regex match files that trigger + a restart when modified + (defaults to '\.yml$|\.yaml$|\.pm$') + -restartdirectory the directory to search for + modified files + (defaults to '../') + + See also: + perldoc Catalyst::Manual + perldoc Catalyst::Manual::Intro + +=head1 DESCRIPTION + +Run a Catalyst Testserver for this application. + +=head1 AUTHOR + +Sebastian Riedel, C<sri@oook.de> +Maintained by the Catalyst Core Team. + +=head1 COPYRIGHT + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/script/admin_test.pl b/script/admin_test.pl new file mode 100755 index 0000000..3285981 --- /dev/null +++ b/script/admin_test.pl @@ -0,0 +1,54 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use Getopt::Long; +use Pod::Usage; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Catalyst::Test 'admin'; + +my $help = 0; + +GetOptions( 'help|?' => \$help ); + +pod2usage(1) if ( $help || !$ARGV[0] ); + +print request($ARGV[0])->content . "\n"; + +1; + +=head1 NAME + +admin_test.pl - Catalyst Test + +=head1 SYNOPSIS + +admin_test.pl [options] uri + + Options: + -help display this help and exits + + Examples: + admin_test.pl http://localhost/some_action + admin_test.pl /some_action + + See also: + perldoc Catalyst::Manual + perldoc Catalyst::Manual::Intro + +=head1 DESCRIPTION + +Run a Catalyst action from the command line. + +=head1 AUTHOR + +Sebastian Riedel, C<sri@oook.de> +Maintained by the Catalyst Core Team. + +=head1 COPYRIGHT + +This library is free software, you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut