From 41e9261d94e47c97d95a82ad6492f6a6a23519b5 Mon Sep 17 00:00:00 2001
From: Daniel Tiefnig <dtiefnig@sipwise.com>
Date: Thu, 7 Feb 2008 18:15:42 +0000
Subject: [PATCH] renamed www/admin to www_admin created new directory layout
 for branching and tagging

---
 admin.yml                              |   3 +
 lib/admin.pm                           |  81 +++
 lib/admin/Controller/Root.pm           | 121 ++++
 lib/admin/Controller/account.pm        | 331 ++++++++++
 lib/admin/Controller/admin.pm          | 200 ++++++
 lib/admin/Controller/customer.pm       | 328 ++++++++++
 lib/admin/Controller/domain.pm         | 191 ++++++
 lib/admin/Controller/login.pm          |  67 ++
 lib/admin/Controller/logout.pm         |  59 ++
 lib/admin/Controller/subscriber.pm     | 850 +++++++++++++++++++++++++
 lib/admin/Model/Provisioning.pm        | 212 ++++++
 lib/admin/View/Sipwise.pm              |  64 ++
 root/css/admin.css                     | 260 ++++++++
 root/css/csshover.htc                  | 120 ++++
 root/css/sipwise.css                   | 552 ++++++++++++++++
 root/favicon.ico                       | Bin 0 -> 1406 bytes
 root/js/openclose.js                   |  10 +
 root/layout/body                       |  21 +
 root/layout/footer                     |   6 +
 root/layout/header                     |  12 +
 root/layout/html                       |  28 +
 root/layout/menu                       |  35 +
 root/layout/wrapper                    |   8 +
 root/static/images/client_logo.png     | Bin 0 -> 3854 bytes
 root/static/images/dot_trans.gif       | Bin 0 -> 832 bytes
 root/static/images/sipwise_logo_96.png | Bin 0 -> 2959 bytes
 root/tt/account.tt                     |  52 ++
 root/tt/account_detail.tt              | 167 +++++
 root/tt/admin.tt                       | 119 ++++
 root/tt/customer.tt                    |  58 ++
 root/tt/customer_detail.tt             | 378 +++++++++++
 root/tt/default.tt                     |   0
 root/tt/domain.tt                      | 104 +++
 root/tt/login.tt                       |  10 +
 root/tt/subscriber.tt                  |  83 +++
 root/tt/subscriber_detail.tt           | 333 ++++++++++
 root/tt/subscriber_edit_list.tt        |  69 ++
 script/admin_cgi.pl                    |  37 ++
 script/admin_create.pl                 |  75 +++
 script/admin_fastcgi.pl                |  80 +++
 script/admin_server.pl                 | 111 ++++
 script/admin_test.pl                   |  54 ++
 42 files changed, 5289 insertions(+)
 create mode 100644 admin.yml
 create mode 100644 lib/admin.pm
 create mode 100644 lib/admin/Controller/Root.pm
 create mode 100644 lib/admin/Controller/account.pm
 create mode 100644 lib/admin/Controller/admin.pm
 create mode 100644 lib/admin/Controller/customer.pm
 create mode 100644 lib/admin/Controller/domain.pm
 create mode 100644 lib/admin/Controller/login.pm
 create mode 100644 lib/admin/Controller/logout.pm
 create mode 100644 lib/admin/Controller/subscriber.pm
 create mode 100644 lib/admin/Model/Provisioning.pm
 create mode 100644 lib/admin/View/Sipwise.pm
 create mode 100644 root/css/admin.css
 create mode 100644 root/css/csshover.htc
 create mode 100644 root/css/sipwise.css
 create mode 100644 root/favicon.ico
 create mode 100644 root/js/openclose.js
 create mode 100644 root/layout/body
 create mode 100644 root/layout/footer
 create mode 100644 root/layout/header
 create mode 100644 root/layout/html
 create mode 100644 root/layout/menu
 create mode 100644 root/layout/wrapper
 create mode 100644 root/static/images/client_logo.png
 create mode 100644 root/static/images/dot_trans.gif
 create mode 100644 root/static/images/sipwise_logo_96.png
 create mode 100644 root/tt/account.tt
 create mode 100644 root/tt/account_detail.tt
 create mode 100644 root/tt/admin.tt
 create mode 100644 root/tt/customer.tt
 create mode 100644 root/tt/customer_detail.tt
 create mode 100644 root/tt/default.tt
 create mode 100644 root/tt/domain.tt
 create mode 100644 root/tt/login.tt
 create mode 100644 root/tt/subscriber.tt
 create mode 100644 root/tt/subscriber_detail.tt
 create mode 100644 root/tt/subscriber_edit_list.tt
 create mode 100755 script/admin_cgi.pl
 create mode 100755 script/admin_create.pl
 create mode 100755 script/admin_fastcgi.pl
 create mode 100755 script/admin_server.pl
 create mode 100755 script/admin_test.pl

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 0000000000000000000000000000000000000000..3af0982e618f0fe542cc52d2fee3b6cc63e8305a
GIT binary patch
literal 1406
zcmZQzU<5(|0R|w+!H~hqz#zuJz@P!dKp_SNAO?x!1ZhC|bf5_tj0{YfObjfUEDRhu
z91Prf+zdQ<JPa}wG7PG<stjuNY7CZLmJHrgycv9_`ZD-V^JPd{l*CZ7wuGT#eFa0$
z?jD9|2d6R2JUo+O<Hd~(n=fr<*m`9v!}crN87@A#$Z+M^6^1A8pD?`o{EFerk1q`0
zetl#3{_8u#&)+{8e*gW=@bBM0hX4Qn<8<bz#ApbNh5+?LK$&QuD4~F)R$hdYSr*Ed
z6yWAyV_@KdaOL=!AkJjumjj9@@gN+{qok}Xj^Hvdh$|}#BJ%~6m4%S`Ldwe0><9sN
qX`q3kz!DAuSVh4$O7XFP_$<6q5C<qL%ZLdJi^;%vcxk9nWK#gd{ZkSE

literal 0
HcmV?d00001

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 0000000000000000000000000000000000000000..76ed90a8ac0bbc41ba03b73eef2428213e2fc18c
GIT binary patch
literal 3854
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
Q5&!@I07*qoM6N<$f+rzxGynhq

literal 0
HcmV?d00001

diff --git a/root/static/images/dot_trans.gif b/root/static/images/dot_trans.gif
new file mode 100644
index 0000000000000000000000000000000000000000..ed8f3808f05c4c45884323e2e1b09bd447126863
GIT binary patch
literal 832
zcmV-G1Hb%7Nk%w1VF3UE0OtSz000010RaL60s{jB1Ox;H1qB8M1_uWR2nYxX2?+`c
z3JVJh3=9kn4Gj(s4i66x5D*X%5fKs+5)%^>6ciK{6%`g178e&67#J8C85tTH8XFrM
z92^`S9UUGX9v>ecARr(iAt53nA|oRsBqSsyB_$>%CMPE+C@3f?DJd!{Dl021EG#T7
zEiEoCE-x=HFfcGNF)=bSGBYzXG&D3dH8nOiHa9mnI5;>tIXOByIy*Z%JUl!-Jv}}?
zK0iM{KtMo2K|w-7LPJACL_|bIMMXwNMn^|SNJvOYNl8jdN=r*iOiWBoO-)WtPESuy
zP*6}&QBhJ-Qd3h?R8&+|RaI72R##V7SXfwDSy@_IT3cINTwGjTU0q&YUSD5dU|?Wj
zVPRroVq;@tWMpJzWo2e&W@l$-XlQ6@X=!R|YHMq2Y;0_8ZEbFDZf|dIaBy&OadC2T
za&vQYbaZreb#-=jc6WDoczAeud3kzzdV70&e0+R;eSLm@et&;|fPjF3fq{a8f`fyD
zgoK2Jg@uNOhKGlTh=_=ZiHVAeii?YjjEszpjg5|uj*pLzkdTm(k&%*;l9Q8@l$4Z}
zm6ev3mY0{8n3$NEnVFiJnwy)OoSdAUot>VZo}ZteprD|kp`oIpqNAguq@<*!rKP5(
zrl+T;sHmu^si~@}s;jH3tgNi9t*x%EuCK4Ju&}VPv9YqUva_?Zw6wIfwY9dkwzs#p
zxVX5vxw*Q!y1To(yu7@<y}iD^zQ4b}z`(%4!NJ19!o$PE#KgqK#l^<P#>dCU$jHda
z$;ryf%FD~k%*@Qq&CSlv&d<-!(9qD)(b3Y<($mw^)YR0~)z#M4*4Nk9*x1<F+1c9K
z+S}XP+}zyV-QC{a-rwKf;Nall;o;)q;^X7v<mBY#<>lt)=I7_<=;-L_>FMg~>g((4
z?Ck9A?d|UF?(gsK@bK{Q@$vHV^7Hfa^z`)g_4W4l_V@Sq`1ttw`T6?#`uqF){QUg=
z{r&#_{{R2~A^sIZa%Ew3Wn>_CX>@2HRA^-&M@dak03rDV0SW;B04x9i000310RR99
K1OWd91OPjwdW{?a

literal 0
HcmV?d00001

diff --git a/root/static/images/sipwise_logo_96.png b/root/static/images/sipwise_logo_96.png
new file mode 100644
index 0000000000000000000000000000000000000000..24174ba33e324ae35a957472cc5924f7b3d411e5
GIT binary patch
literal 2959
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
FV1n#}&gB3A

literal 0
HcmV?d00001

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