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">&#187; Logout</a></li>
+        [% ELSE %]
+          <li>&nbsp;</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 &#187;" 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 &#187;" class="but" />
+          </form>
+        </div>
+
+        <h3>Create new account</h3>
+        <div class="p1">
+          <form action="/account/create_account" method="post">
+            <input type="submit" value="Create &#187;" 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 %]&amp;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 %]&amp;lock=none" class="aaction">unlock</a>
+          [% ELSE %]
+          <a href="lock?account_id=[% account.id %]&amp;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 &#187;" />
+            [% END %]
+          </form>
+        </div>
+
+      [% IF billing_features %]
+        <h3>Account Balance</h3>
+
+        [% IF ! account.terminate_timestamp %]
+        <div class="actions">
+          <a href="detail?account_id=[% account.id %]&amp;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">&euro; [% 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 &#187;" />
+          [% END %]
+          </form>
+        </div>
+        <div class="p1">
+          Spent this billing interval
+          <table>
+            <tr><td class="tdkey">cash:</td><td>&euro; [% 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 %]&amp;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 &#187;" 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 &#187;" 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 &#187;" 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 %]&amp;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 %]&amp;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 &#187;" />
+            [% END %]
+          </form>
+        </div>
+
+        <h3 id="contact">Contact</h3>
+
+        <div class="actions">
+          <a href="detail?customer_id=[% customer.id %]&amp;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 &#187;" />
+          [% END %]
+          </form>
+        </div>
+
+        <h3 id="commercial">Commercial Contact</h3>
+
+        <div class="actions">
+          <a href="detail?customer_id=[% customer.id %]&amp;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 &#187;" />
+          [% END %]
+          </form>
+        </div>
+
+        <h3 id="technical">Technical Contact</h3>
+
+        <div class="actions">
+          <a href="detail?customer_id=[% customer.id %]&amp;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 &#187;" />
+          [% END %]
+          </form>
+        </div>
+
+        <h3>Contracts</h3>
+
+<!--        <div class="actions">
+            <a href="/subscriber/detail?customer_id=[% customer.id %]&amp;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 %]&amp;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 &#187;" 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 &#187;" 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">&#171; prev</li>
+                [% ELSE %]
+                  <li class="nextpage">
+                    <a href="/subscriber/search?search_string=[% search_string %]&amp;offset=[% offset - 1 %]">&#171; 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
+                               %]&amp;offset=[% pagine.offset %]">[% pagine.offset + 1 %]</a>
+                    </li>
+                  [% END %]
+                [% END %]
+                [% IF offset >= max_offset %]
+                  <li class="disablepage">
+                    next &#187;
+                  </li>
+                [% ELSE %]
+                  <li class="nextpage">
+                    <a href="/subscriber/search?search_string=[% search_string %]&amp;offset=[% offset + 1 %]">next &#187;</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 %]&amp;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 %]&amp;lock=none" class="aaction">unlock</a><br />
+            <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&amp;lock=foreign" class="aaction">foreign</a><br />
+            <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&amp;lock=outgoing" class="aaction">all outgoing</a><br />
+            <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&amp;lock=incoming" class="aaction">incoming and outgoing</a><br />
+            <a href="lock?subscriber_id=[% subscriber.subscriber_id %]&amp;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 %]&amp;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 %]&amp;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 &#187;" />
+          [% END %]
+          </form>
+        </div>
+
+        <h3 id="userprefs">User Preferences</h3>
+
+        <div class="actions">
+          <a href="detail?subscriber_id=[% subscriber.subscriber_id %]&amp;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 %]
+                    &nbsp;
+                    <a href="edit_list?subscriber_id=[% subscriber.subscriber_id %]&amp;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 &#187;" />
+          [% END %]
+          </form>
+        </div>
+
+        <h3 id="vboxprefs">Voicebox Preferences</h3>
+
+        <div class="actions">
+          <a href="detail?subscriber_id=[% subscriber.subscriber_id %]&amp;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 &#187;" />
+          [% 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 %]" /> &nbsp;
+              <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