From d8ac08282dad52789b8bea785e8cddae44085483 Mon Sep 17 00:00:00 2001 From: Moritz Bunkus Date: Wed, 5 Jun 2013 11:33:10 +0200 Subject: [PATCH] Admin: Teile von admin.pl in neuen Controller Admin verschoben; Mandanten anzeigen --- SL/Auth.pm | 18 +- SL/Controller/Admin.pm | 181 ++++++++++++++++++ SL/Controller/Base.pm | 5 +- SL/DB.pm | 13 +- SL/DB/AuthClient.pm | 3 +- SL/DB/AuthGroup.pm | 4 +- SL/DB/AuthUser.pm | 15 +- SL/DB/Manager/AuthClient.pm | 20 ++ SL/DB/Manager/AuthGroup.pm | 20 ++ SL/DB/Manager/AuthUser.pm | 20 ++ SL/DBConnect.pm | 7 + SL/Dispatcher.pm | 39 ++-- SL/Dispatcher/AuthHandler.pm | 6 +- SL/Dispatcher/AuthHandler/Admin.pm | 3 + bin/mozilla/admin.pl | 133 +------------ css/kivitendo/main.css | 5 + css/lx-office-erp/main.css | 5 + locale/de/all | 14 +- templates/webpages/admin/adminlogin.html | 25 ++- templates/webpages/admin/backup_dataset.html | 5 +- .../admin/backup_dataset_email_done.html | 7 +- .../webpages/admin/check_auth_database.html | 28 ++- .../webpages/admin/check_auth_tables.html | 24 ++- templates/webpages/admin/dbadmin.html | 4 +- templates/webpages/admin/dbcreate.html | 12 +- templates/webpages/admin/dbdelete.html | 11 +- .../webpages/admin/dbupgrade_all_done.html | 9 +- templates/webpages/admin/delete_dataset.html | 2 +- templates/webpages/admin/list_users.html | 119 +++++++----- .../admin/restore_dataset_start_footer.html | 9 +- templates/webpages/admin/update_dataset.html | 2 +- templates/webpages/dbupgrade/footer.html | 5 +- 32 files changed, 467 insertions(+), 306 deletions(-) create mode 100644 SL/Controller/Admin.pm create mode 100644 SL/DB/Manager/AuthClient.pm create mode 100644 SL/DB/Manager/AuthGroup.pm create mode 100644 SL/DB/Manager/AuthUser.pm diff --git a/SL/Auth.pm b/SL/Auth.pm index 23a9771e7..420724b4c 100644 --- a/SL/Auth.pm +++ b/SL/Auth.pm @@ -566,7 +566,7 @@ sub restore_session { if (!$session_id) { $main::lxdebug->leave_sub(); - return SESSION_NONE; + return $self->session_restore_result(SESSION_NONE()); } my ($dbh, $query, $sth, $cookie, $ref, $form); @@ -576,7 +576,7 @@ sub restore_session { # Don't fail if the auth DB doesn't yet. if (!( $dbh = $self->dbconnect(1) )) { $::lxdebug->leave_sub; - return SESSION_NONE; + return $self->session_restore_result(SESSION_NONE()); } # Don't fail if the "auth" schema doesn't exist yet, e.g. if the @@ -586,7 +586,7 @@ sub restore_session { if (!($sth = $dbh->prepare($query)) || !$sth->execute($session_id)) { $sth->finish if $sth; $::lxdebug->leave_sub; - return SESSION_NONE; + return $self->session_restore_result(SESSION_NONE()); } $cookie = $sth->fetchrow_hashref; @@ -605,7 +605,7 @@ sub restore_session { if ($cookie_is_bad) { $self->destroy_session(); $main::lxdebug->leave_sub(); - return $cookie ? SESSION_EXPIRED : SESSION_NONE; + return $self->session_restore_result($cookie ? SESSION_EXPIRED() : SESSION_NONE()); } if ($self->{column_information}->has('auto_restore')) { @@ -616,7 +616,15 @@ sub restore_session { $main::lxdebug->leave_sub(); - return SESSION_OK; + return $self->session_restore_result(SESSION_OK()); +} + +sub session_restore_result { + my $self = shift; + if (@_) { + $self->{session_restore_result} = $_[0]; + } + return $self->{session_restore_result}; } sub _load_without_auto_restore_column { diff --git a/SL/Controller/Admin.pm b/SL/Controller/Admin.pm new file mode 100644 index 000000000..04d9c759b --- /dev/null +++ b/SL/Controller/Admin.pm @@ -0,0 +1,181 @@ +package SL::Controller::Admin; + +use strict; + +use parent qw(SL::Controller::Base); + +use IO::File; + +use SL::DB::AuthUser; +use SL::DB::AuthGroup; +use SL::Helper::Flash; +use SL::Locale::String qw(t8); + +use Rose::Object::MakeMethods::Generic +( + 'scalar --get_set_init' => [ qw(client user nologin_file_name db_cfg) ], +); + +__PACKAGE__->run_before(\&setup_layout); + +sub get_auth_level { "admin" }; +sub keep_auth_vars { + my ($class, %params) = @_; + return $params{action} eq 'login'; +} + +# +# actions +# + +sub action_login { + my ($self) = @_; + + return $self->login_form if !$::form->{do_login}; + return if !$self->authenticate_root; + return if !$self->check_auth_db_and_tables; + return if $self->apply_dbupgrade_scripts; + $self->redirect_to(action => 'list_clients_and_users'); +} + +sub action_logout { + my ($self) = @_; + $::auth->destroy_session; + $self->redirect_to(action => 'login'); +} + +sub action_apply_dbupgrade_scripts { + my ($self) = @_; + + return if $self->apply_dbupgrade_scripts; + $self->action_list_clients_and_users; +} + +sub action_create_auth_db { + my ($self) = @_; + + $::auth->create_database(superuser => $::form->{db_superuser}, + superuser_password => $::form->{db_superuser_password}, + template => $::form->{db_template}); + $self->check_auth_db_and_tables; +} + +sub action_create_auth_tables { + my ($self) = @_; + + $::auth->create_tables; + $::auth->set_session_value('admin_password', $::lx_office_conf{authentication}->{admin_password}); + $::auth->create_or_refresh_session; + + my $group = (SL::DB::Manager::AuthGroup->get_all(limit => 1))[0]; + if (!$group) { + SL::DB::AuthGroup->new( + name => t8('Full Access'), + description => t8('Full access to all functions'), + rights => [ map { SL::DB::AuthGroupRight->new(right => $_, granted => 1) } SL::Auth::all_rights() ], + )->save; + } + + if (!$self->apply_dbupgrade_scripts) { + $self->action_login; + } +} + +sub action_list_clients_and_users { + my ($self) = @_; + + $self->render( + "admin/list_users", + CLIENTS => SL::DB::Manager::AuthClient->get_all_sorted, + USERS => SL::DB::Manager::AuthUser->get_all_sorted, + LOCKED => (-e $self->nologin_file_name), + title => "kivitendo " . $::locale->text('Administration'), + ); +} + +sub action_unlock_system { + my ($self) = @_; + unlink $self->nologin_file_name; + flash_later('info', t8('Lockfile removed!')); + $self->redirect_to(action => 'list_clients_and_users'); +} + +sub action_lock_system { + my ($self) = @_; + + my $fh = IO::File->new($self->nologin_file_name, "w"); + if (!$fh) { + $::form->error(t8('Cannot create Lock!')); + + } else { + $fh->close; + flash_later('info', t8('Lockfile created!')); + $self->redirect_to(action => 'list_clients_and_users'); + } +} + +# +# initializers +# + +sub init_db_cfg { $::lx_office_conf{'authentication/database'} } +sub init_nologin_file_name { $::lx_office_conf{paths}->{userspath} . '/nologin'; } +sub init_client { SL::DB::AuthClient->new(id => $::form->{client_id})->load; } +sub init_user { SL::DB::AuthUser ->new(id => $::form->{user_id} )->load; } + +# +# filters +# + +sub setup_layout { + my ($self, $action) = @_; + + $::request->layout(SL::Layout::Dispatcher->new(style => 'admin')); + $::request->layout->use_stylesheet("lx-office-erp.css"); + $::form->{favicon} = "favicon.ico"; +} + +# +# helpers +# + +sub login_form { + my ($self, %params) = @_; + $::request->layout->focus('#admin_password'); + $self->render('admin/adminlogin', title => t8('kivitendo v#1 administration', $::form->{version}), %params); +} + +sub check_auth_db_and_tables { + my ($self) = @_; + + if (!$::auth->check_database) { + $self->render('admin/check_auth_database', title => t8('Authentification database creation')); + return 0; + } + + if (!$::auth->check_tables) { + $self->render('admin/check_auth_tables', title => t8('Authentification tables creation')); + return 0; + } + + return 1; +} + +sub apply_dbupgrade_scripts { + return SL::DBUpgrade2->new(form => $::form, dbdriver => 'Pg', auth => 1)->apply_admin_dbupgrade_scripts(1); +} + +sub authenticate_root { + my ($self) = @_; + + return 1 if $::auth->authenticate_root($::form->{'{AUTH}admin_password'}) == $::auth->OK(); + + $::auth->punish_wrong_login; + $::auth->delete_session_value('admin_password'); + + $self->login_form(error => t8('Incorrect Password!')); + + return undef; +} + +1; diff --git a/SL/Controller/Base.pm b/SL/Controller/Base.pm index c6977d0e1..88f10b7bd 100644 --- a/SL/Controller/Base.pm +++ b/SL/Controller/Base.pm @@ -562,13 +562,16 @@ C (authentication as a normal user suffices) with a possible future value C (which would require no authentication but is not yet implemented). -=item C +=item C May be overridden by a controller. If falsish (the default) all form variables whose name starts with C<{AUTH}> are removed before the request is routed. Only controllers that handle login requests themselves should return trueish for this function. +C<$params{action}> contains the action name that the request will be +dispatched to. + =item C Returns the name of the curernt controller package without the diff --git a/SL/DB.pm b/SL/DB.pm index 01251141e..28626ac66 100644 --- a/SL/DB.pm +++ b/SL/DB.pm @@ -49,7 +49,7 @@ sub _register_db { my %connect_settings; my $initial_sql; - if ($type eq 'KIVITENDO_AUTH') { + if (($type eq 'KIVITENDO_AUTH') && $::auth && $::auth->{DB_config} && $::auth->session_tables_present) { %connect_settings = ( driver => 'Pg', database => $::auth->{DB_config}->{db}, host => $::auth->{DB_config}->{host} || 'localhost', @@ -58,11 +58,9 @@ sub _register_db { password => $::auth->{DB_config}->{password}, connect_options => { pg_enable_utf8 => $::locale && $::locale->is_utf8, }); - } elsif (!%::myconfig) { - $type = 'KIVITENDO_EMPTY'; - %connect_settings = ( driver => 'Pg' ); + } - } else { + if (!%connect_settings && %::myconfig) { my $european_dates = 0; if ($::myconfig{dateformat}) { $european_dates = 1 if $_dateformats{ $::myconfig{dateformat} } @@ -80,6 +78,11 @@ sub _register_db { european_dates => $european_dates); } + if (!%connect_settings) { + $type = 'KIVITENDO_EMPTY'; + %connect_settings = ( driver => 'Pg' ); + } + my %flattened_settings = _flatten_settings(%connect_settings); $domain = 'KIVITENDO' if $type =~ m/^KIVITENDO/; diff --git a/SL/DB/AuthClient.pm b/SL/DB/AuthClient.pm index 5291da209..a37c93011 100644 --- a/SL/DB/AuthClient.pm +++ b/SL/DB/AuthClient.pm @@ -6,12 +6,11 @@ package SL::DB::AuthClient; use strict; use SL::DB::MetaSetup::AuthClient; +use SL::DB::Manager::AuthClient; # Creates get_all, get_all_count, get_all_iterator, delete_all and update_all. __PACKAGE__->meta->schema('auth'); -__PACKAGE__->meta->make_manager_class; - __PACKAGE__->meta->add_relationship( users => { type => 'many to many', diff --git a/SL/DB/AuthGroup.pm b/SL/DB/AuthGroup.pm index 3d301775f..ce47bd32f 100644 --- a/SL/DB/AuthGroup.pm +++ b/SL/DB/AuthGroup.pm @@ -6,11 +6,9 @@ package SL::DB::AuthGroup; use strict; use SL::DB::MetaSetup::AuthGroup; +use SL::DB::Manager::AuthGroup; use SL::DB::AuthGroupRight; -# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all. -__PACKAGE__->meta->make_manager_class; - __PACKAGE__->meta->schema('auth'); __PACKAGE__->meta->add_relationship( diff --git a/SL/DB/AuthUser.pm b/SL/DB/AuthUser.pm index c1b46b4ef..41644eb1f 100644 --- a/SL/DB/AuthUser.pm +++ b/SL/DB/AuthUser.pm @@ -8,11 +8,9 @@ use strict; use List::Util qw(first); use SL::DB::MetaSetup::AuthUser; +use SL::DB::Manager::AuthUser; use SL::DB::AuthUserGroup; -# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all. -__PACKAGE__->meta->make_manager_class; - __PACKAGE__->meta->schema('auth'); __PACKAGE__->meta->add_relationship( @@ -44,4 +42,15 @@ sub get_config_value { return $cfg ? $cfg->cfg_value : undef; } +sub config_values { + my $self = shift; + + if (0 != scalar(@_)) { + my %settings = (ref($_[0]) || '') eq 'HASH' ? %{ $_[0] } : @_; + $self->configs([ map { SL::DB::AuthUserConfig->new(cfg_key => $_, cfg_value => $settings{$_}) } keys %settings ]); + } + + return { map { ($_->cfg_key => $_->cfg_value) } @{ $self->configs } }; +} + 1; diff --git a/SL/DB/Manager/AuthClient.pm b/SL/DB/Manager/AuthClient.pm new file mode 100644 index 000000000..ccfb97eff --- /dev/null +++ b/SL/DB/Manager/AuthClient.pm @@ -0,0 +1,20 @@ +package SL::DB::Manager::AuthClient; + +use strict; + +use SL::DB::Helper::Manager; +use base qw(SL::DB::Helper::Manager); + +use SL::DB::Helper::Paginated; +use SL::DB::Helper::Sorted; + +sub object_class { 'SL::DB::AuthClient' } + +__PACKAGE__->make_manager_methods; + +sub _sort_spec { + return ( default => [ 'name', 1 ], + columns => { SIMPLE => 'ALL' } ); +} + +1; diff --git a/SL/DB/Manager/AuthGroup.pm b/SL/DB/Manager/AuthGroup.pm new file mode 100644 index 000000000..985f834de --- /dev/null +++ b/SL/DB/Manager/AuthGroup.pm @@ -0,0 +1,20 @@ +package SL::DB::Manager::AuthGroup; + +use strict; + +use SL::DB::Helper::Manager; +use base qw(SL::DB::Helper::Manager); + +use SL::DB::Helper::Paginated; +use SL::DB::Helper::Sorted; + +sub object_class { 'SL::DB::AuthGroup' } + +__PACKAGE__->make_manager_methods; + +sub _sort_spec { + return ( default => [ 'name', 1 ], + columns => { SIMPLE => 'ALL' } ); +} + +1; diff --git a/SL/DB/Manager/AuthUser.pm b/SL/DB/Manager/AuthUser.pm new file mode 100644 index 000000000..809562215 --- /dev/null +++ b/SL/DB/Manager/AuthUser.pm @@ -0,0 +1,20 @@ +package SL::DB::Manager::AuthUser; + +use strict; + +use SL::DB::Helper::Manager; +use base qw(SL::DB::Helper::Manager); + +use SL::DB::Helper::Paginated; +use SL::DB::Helper::Sorted; + +sub object_class { 'SL::DB::AuthUser' } + +__PACKAGE__->make_manager_methods; + +sub _sort_spec { + return ( default => [ 'login', 1 ], + columns => { SIMPLE => 'ALL' } ); +} + +1; diff --git a/SL/DBConnect.pm b/SL/DBConnect.pm index 8dc978758..3ad5dfdf7 100644 --- a/SL/DBConnect.pm +++ b/SL/DBConnect.pm @@ -7,6 +7,13 @@ use DBI; sub connect { shift; + # print STDERR "Starting full caller dump:\n"; + # my $level = 0; + # while (my ($dummy, $filename, $line, $subroutine) = caller $level) { + # print STDERR " ${subroutine} from ${filename}:${line}\n"; + # $level++; + # } + return DBI->connect(@_) unless $::lx_office_conf{debug} && $::lx_office_conf{debug}->{dbix_log4perl}; require Log::Log4perl; diff --git a/SL/Dispatcher.pm b/SL/Dispatcher.pm index 01b6aff2e..1261aa72b 100644 --- a/SL/Dispatcher.pm +++ b/SL/Dispatcher.pm @@ -59,16 +59,18 @@ sub interface_type { return $self->{interface} eq 'cgi' ? 'CGI' : 'FastCGI'; } +sub is_admin_request { + my %params = @_; + return ($params{script} eq 'admin.pl') || (($params{routing_type} eq 'controller') && ($params{script_name} eq 'Admin')); +} + sub pre_request_checks { + my (%params) = @_; + _check_for_old_config_files(); - if (!$::auth->session_tables_present) { - if ($::form->{script} eq 'admin.pl') { - ::run(); - ::end_of_request(); - } else { - show_error('login_screen/auth_db_unreachable'); - } + if (!$::auth->session_tables_present && !is_admin_request(%params)) { + show_error('login_screen/auth_db_unreachable'); } if ($::request->type !~ m/^ (?: html | js | json ) $/x) { @@ -231,9 +233,12 @@ sub handle_request { } eval { - pre_request_checks(); + pre_request_checks(script => $script, action => $action, routing_type => $routing_type, script_name => $script_name); - $::form->error($::locale->text('System currently down for maintenance!')) if -e ($::lx_office_conf{paths}->{userspath} . "/nologin") && $script ne 'admin'; + if ( (-e ($::lx_office_conf{paths}->{userspath} . "/nologin")) + && !is_admin_request(script => $script, script_name => $script_name, routing_type => $routing_type)) { + $::form->error($::locale->text('System currently down for maintenance!')); + } # For compatibility with a lot of database upgrade scripts etc: # Re-write request to old 'login.pl?action=login' to new @@ -246,15 +251,8 @@ sub handle_request { if (($script eq 'login') && !$action) { print $::request->{cgi}->redirect('controller.pl?action=LoginScreen/user_login'); - } elsif ($script eq 'admin') { - $::form->{titlebar} = "kivitendo " . $::locale->text('Version') . " $::form->{version}"; - ::run($session_result); - } else { - if (SL::Auth::SESSION_EXPIRED == $session_result) { - print $::request->{cgi}->redirect('controller.pl?action=LoginScreen/user_login&error=session'); - ::end_of_request(); - } + $self->redirect_to_login($script) if SL::Auth::SESSION_EXPIRED == $session_result; my %auth_result = $self->{auth_handler}->handle( routing_type => $routing_type, @@ -320,6 +318,13 @@ sub handle_request { $::lxdebug->leave_sub; } +sub redirect_to_login { + my ($self, $script) = @_; + my $action = $script =~ m/^admin/i ? 'Admin/login' : 'LoginScreen/user_login&error=session'; + print $::request->cgi->redirect("controller.pl?action=${action}"); + ::end_of_request(); +} + sub unrequire_bin_mozilla { my $self = shift; return unless $self->_interface_is_fcgi; diff --git a/SL/Dispatcher/AuthHandler.pm b/SL/Dispatcher/AuthHandler.pm index 4c352bdbb..449d68492 100644 --- a/SL/Dispatcher/AuthHandler.pm +++ b/SL/Dispatcher/AuthHandler.pm @@ -1,4 +1,4 @@ - package SL::Dispatcher::AuthHandler; +package SL::Dispatcher::AuthHandler; use strict; @@ -18,7 +18,7 @@ sub handle { my $handler_name = "SL::Dispatcher::AuthHandler::" . ucfirst($auth_level); $self->{handlers} ||= {}; $self->{handlers}->{$handler_name} ||= $handler_name->new; - my $ok = $self->{handlers}->{$handler_name}->handle; + my $ok = $self->{handlers}->{$handler_name}->handle(%param); return ( auth_level => $auth_level, @@ -40,7 +40,7 @@ sub get_auth_level { sub get_keep_auth_vars { my ($self, %param) = @_; - return $param{routing_type} eq 'controller' ? "SL::Controller::$param{controller}"->keep_auth_vars_in_form : 0; + return $param{routing_type} eq 'controller' ? "SL::Controller::$param{controller}"->keep_auth_vars_in_form(action => $param{action}) : 0; } 1; diff --git a/SL/Dispatcher/AuthHandler/Admin.pm b/SL/Dispatcher/AuthHandler/Admin.pm index a7b649cf2..baacc67e3 100644 --- a/SL/Dispatcher/AuthHandler/Admin.pm +++ b/SL/Dispatcher/AuthHandler/Admin.pm @@ -6,11 +6,14 @@ use parent qw(Rose::Object); use SL::Layout::Dispatcher; sub handle { + my ($self, %params) = @_; + %::myconfig = (); return 1 if $::auth->get_api_token_cookie; return 1 if $::form->{'{AUTH}admin_password'} && ($::auth->authenticate_root($::form->{'{AUTH}admin_password'}) == $::auth->OK()); return 1 if !$::form->{'{AUTH}admin_password'} && ($::auth->authenticate_root($::auth->get_session_value('admin_password')) == $::auth->OK()); + return 1 if $params{action} eq 'login'; $::request->{layout} = SL::Layout::Dispatcher->new(style => 'admin'); diff --git a/bin/mozilla/admin.pl b/bin/mozilla/admin.pl index 464c6de9a..67c5969fd 100755 --- a/bin/mozilla/admin.pl +++ b/bin/mozilla/admin.pl @@ -47,6 +47,8 @@ use Sys::Hostname; use SL::Auth; use SL::Auth::PasswordPolicy; +use SL::DB::AuthClient; +use SL::DB::AuthUser; use SL::Form; use SL::Iconv; use SL::Mailer; @@ -105,7 +107,6 @@ sub run { } else { if ($auth->session_tables_present()) { delete $::form->{'{AUTH}admin_password'}; - _apply_dbupgrade_scripts(); } call_sub($locale->findsub($form->{action})); @@ -130,99 +131,6 @@ sub adminlogin { print $form->parse_html_template('admin/adminlogin'); } -sub login { - check_auth_db_and_tables(); - list_users(); -} - -sub logout { - $main::auth->destroy_session(); - adminlogin(); -} - -sub check_auth_db_and_tables { - my $form = $main::form; - my $locale = $main::locale; - - my %params; - - map { $params{"db_${_}"} = $main::auth->{DB_config}->{$_} } keys %{ $auth->{DB_config} }; - - $params{admin_password} = $::lx_office_conf{authentication}->{admin_password}; - - if (!$main::auth->check_database()) { - $form->{title} = $locale->text('Authentification database creation'); - $form->header(); - print $form->parse_html_template('admin/check_auth_database', \%params); - - ::end_of_request(); - } - - if (!$main::auth->check_tables()) { - $form->{title} = $locale->text('Authentification tables creation'); - $form->header(); - print $form->parse_html_template('admin/check_auth_tables', \%params); - - ::end_of_request(); - } -} - -sub create_auth_db { - my $form = $main::form; - - $main::auth->create_database('superuser' => $form->{db_superuser}, - 'superuser_password' => $form->{db_superuser_password}, - 'template' => $form->{db_template}); - login(); -} - -sub create_auth_tables { - my $form = $main::form; - my $locale = $main::locale; - - $main::auth->create_tables(); - $main::auth->set_session_value('admin_password', $form->{'{AUTH}admin_password'}); - $main::auth->create_or_refresh_session(); - - my $memberfile = $::lx_office_conf{paths}->{memberfile}; - if (!-f $memberfile) { - # New installation -- create a standard group with full access - my %members; - my $group = { - 'name' => $locale->text('Full Access'), - 'description' => $locale->text('Full access to all functions'), - 'rights' => { map { $_ => 1 } SL::Auth::all_rights() }, - 'members' => [ map { $_->{id} } values %members ], - }; - - $main::auth->save_group($group); - } - - _apply_dbupgrade_scripts(); - login(); -} - -sub list_users { - my $form = $main::form; - my $locale = $main::locale; - - my %members = $main::auth->read_all_users(); - - delete $members{"root login"}; - - for (values %members) { - $_->{templates} =~ s|.*/||; - $_->{login_url} = $::locale->is_utf8 ? Encode::encode('utf-8-strict', $_->{login}) : $_->{login_url}; - } - - $form->{title} = "kivitendo " . $locale->text('Administration'); - $form->{LOCKED} = -e _nologin_file_name(); - $form->{MEMBERS} = [ @members{sort { lc $a cmp lc $b } keys %members} ]; - - $form->header(); - print $form->parse_html_template("admin/list_users"); -} - sub add_user { $::form->{title} = "kivitendo " . $::locale->text('Administration') . " / " . $::locale->text('Add User'); @@ -249,9 +157,6 @@ sub edit_user { # get user my $user = User->new(id => $::form->{user}{id}); - # strip basedir from templates directory - $user->{templates} =~ s|.*/||; - edit_user_form($user); } @@ -923,32 +828,6 @@ sub restore_dataset_start { rmdir $tmpdir; } -sub unlock_system { - my $form = $main::form; - my $locale = $main::locale; - - unlink _nologin_file_name();; - - $form->{callback} = "admin.pl?action=list_users"; - - $form->redirect($locale->text('Lockfile removed!')); - -} - -sub lock_system { - my $form = $main::form; - my $locale = $main::locale; - - open(FH, ">", _nologin_file_name()) - or $form->error($locale->text('Cannot create Lock!')); - close(FH); - - $form->{callback} = "admin.pl?action=list_users"; - - $form->redirect($locale->text('Lockfile created!')); - -} - sub yes { call_sub($main::form->{yes_nextsub}); } @@ -1006,14 +885,6 @@ sub dispatcher { $form->error($locale->text('No action defined.')); } -sub _apply_dbupgrade_scripts { - ::end_of_request() if SL::DBUpgrade2->new(form => $::form, dbdriver => 'Pg', auth => 1)->apply_admin_dbupgrade_scripts(1); -} - -sub _nologin_file_name { - return $::lx_office_conf{paths}->{userspath} . '/nologin'; -} - sub _search_templates { my %templates = SL::Template->available_templates; diff --git a/css/kivitendo/main.css b/css/kivitendo/main.css index 3653c526f..e765fbfba 100644 --- a/css/kivitendo/main.css +++ b/css/kivitendo/main.css @@ -370,3 +370,8 @@ label { color: #ccc; font-style: italic; } + +.link_separator { + margin-left: 6px; + margin-right: 6px; +} diff --git a/css/lx-office-erp/main.css b/css/lx-office-erp/main.css index 799cbab3a..27a3aa221 100644 --- a/css/lx-office-erp/main.css +++ b/css/lx-office-erp/main.css @@ -423,3 +423,8 @@ label { color: #aaa; font-style: italic; } + +.link_separator { + margin-left: 6px; + margin-right: 6px; +} diff --git a/locale/de/all b/locale/de/all index 1301aebcd..8400806da 100755 --- a/locale/de/all +++ b/locale/de/all @@ -127,6 +127,7 @@ $self->{texts} = { 'Add Accounts Receivables Transaction' => 'Debitorenbuchung erfassen', 'Add Assembly' => 'Erzeugnis erfassen', 'Add Buchungsgruppe' => 'Buchungsgruppe erfassen', + 'Add Client' => 'Neuer Mandant', 'Add Credit Note' => 'Gutschrift erfassen', 'Add Customer' => 'Kunde erfassen', 'Add Delivery Note' => 'Lieferschein erfassen', @@ -439,6 +440,7 @@ $self->{texts} = { 'Client #1' => 'Mandant #1', 'Client Configuration' => 'Mandantenkonfiguration', 'Client Configuration saved!' => 'Mandantenkonfiguration gespeichert!', + 'Client list' => 'Mandantenliste', 'Client name' => 'Mandantenname', 'Close' => 'Übernehmen', 'Close Books up to' => 'Die Bücher abschließen bis zum', @@ -1031,7 +1033,7 @@ $self->{texts} = { 'If you have not chosen for example the category revenue for a tax and you choose an revenue account to create a transfer in the general ledger, this tax will not be displayed in the tax dropdown.' => 'Wenn Sie z.B. die Kategory Erlös für eine Steuer nicht gewählt haben und ein Erlöskonto beim Erstellen einer Dialogbuchung wählen, wird diese Steuer auch nicht im Dropdown-Menü für die Steuern angezeigt.', 'If you see this message, you most likely just setup your LX-Office and haven\'t added any entry types. If this is the case, the option is accessible for administrators in the System menu.' => 'Wenn Sie diese Meldung sehen haben Sie wahrscheinlich ein frisches LX-Office Setup und noch keine Buchungsgruppen eingerichtet. Ein Administrator kann dies im Systemmenü erledigen.', 'If you select a base unit then you also have to enter a factor.' => 'Wenn Sie eine Basiseinheit auswählen, dann müssen Sie auch einen Faktor eingeben.', - 'If you want to change any of these parameters then press the "Back" button, edit the file "config/kivitendo.conf" and login into the admin module again.' => 'Wenn Sie einen der Parameter ändern wollen, so drücken Sie auf den "Zurück"-Button, bearbeiten Sie die Datei "config/kivitendo.conf", und melden Sie sich erneut im Administrationsbereich an.', + 'If you want to change any of these parameters then press the "Back" button, edit the file "config/kivitendo.conf" and login into the admin module again.' => 'Wenn Sie einen der Parameter ändern wollen, so drücken Sie auf den "Zurück"-Button, bearbeiten Sie die Datei "config/kivitendo.conf", und melden Sie sich erneut im Administrationsbereich an.', 'If you want to delete such a dataset you have to edit the user(s) that are using the dataset in question and have them use another dataset.' => 'Wenn Sie eine solche Datenbank löschen wollen, so müssen Sie zuerst die Benutzer bearbeiten, die die fragliche Datenbank benutzen, und sie so ändern, dass sie eine andere Datenbank benutzen.', 'If you want to set up the authentication database yourself then log in to the administration panel. kivitendo will then create the database and tables for you.' => 'Wenn Sie die Authentifizierungs-Datenbank selber einrichten wollen, so melden Sie sich im Administrationsbereich an. kivitendo wird dann die Datenbank und die erforderlichen Tabellen für Sie anlegen.', 'If your old bins match exactly Bins in the Warehouse CLICK on AUTOMATICALLY MATCH BINS.' => 'Falls die alte Lagerplatz-Beschreibung in Stammdaten genau mit einem Lagerplatz in einem vorhandenem Lager übereinstimmt, KLICK auf LAGERPLÄTZE AUTOMATISCH ZUWEISEN', @@ -1047,6 +1049,7 @@ $self->{texts} = { 'Import result' => 'Import-Ergebnis', 'Import summary' => 'Import-Zusammenfassung', 'In order to do that hit the button "Delete transaction".' => 'Drücken Sie dafür auf den Button "Buchung löschen".', + 'In order to use kivitendo you have to create at least one client, one user, and grant that user access to the client.' => 'Um kivitendo zu nutzen, müssen Sie mindestens einen Mandanten und einen Benutzer anlegen sowie dem Benutzer den Zugriff auf diesen Mandanten gewähren.', 'In the latter case the tables needed by kivitendo will be created in that database.' => 'In letzterem Fall werden die von kivitendo benötigten Tabellen in dieser existierenden Datenbank angelegt.', 'In version 2.4.0 the administrator has to enter a list of units in the administrative section.' => 'Vor Version 2.4.0 musste der Benutzer die Konten bei jeder Ware und jeder Dienstleistung einzeln auswählen.', 'In-line' => 'im Text', @@ -1155,7 +1158,6 @@ $self->{texts} = { 'Language saved!' => 'Sprache gespeichert!', 'Languages' => 'Sprachen', 'Languages and translations' => 'Sprachen und Übersetzungen', - 'Last Action' => 'Letzte Aktivität', 'Last Article Number' => 'Letzte Artikelnummer', 'Last Assembly Number' => 'Letzte Erzeugnisnummer', 'Last Cost' => 'Einkaufspreis', @@ -1320,6 +1322,7 @@ $self->{texts} = { 'No bank information has been entered in this vendor\'s master data entry. You cannot create bank transfers unless you enter bank information.' => 'Für diesen Lieferanten wurden in seinen Stammdaten keine Kontodaten hinterlegt. Solange dies nicht geschehen ist, können Sie keine Überweisungen für den Lieferanten anlegen.', 'No bins have been added to this warehouse yet.' => 'Es wurden zu diesem Lager noch keine Lagerplätze angelegt.', 'No business has been created yet.' => 'Es wurden noch kein Kunden-/Lieferantentyp erfasst.', + 'No clients have been created yet.' => 'Es wurden noch keine Mandanten angelegt.', 'No contact selected to delete' => 'Keine Ansprechperson zum Löschen ausgewählt', 'No customer has been selected yet.' => 'Es wurde noch kein Kunde ausgewählt.', 'No data was found.' => 'Es wurden keine Daten gefunden.', @@ -1343,6 +1346,7 @@ $self->{texts} = { 'No transaction selected!' => 'Keine Transaktion ausgewählt', 'No transfers were executed in this export.' => 'In diesem SEPA-Export wurden keine Überweisungen ausgeführt.', 'No unknown units where found.' => 'Es wurden keine unbekannten Einheiten gefunden.', + 'No users have been created yet.' => 'Es wurden noch keine Benutzer angelegt.', 'No valid number entered for pricegroup "#1".' => 'Für Preisgruppe "#1" wurde keine gültige Nummer eingegeben.', 'No vendor has been selected yet.' => 'Es wurde noch kein Lieferant ausgewählt.', 'No warehouse has been created yet or the quantity of the bins is not configured yet.' => 'Es wurde noch kein Lager angelegt, bzw. die dazugehörigen Lagerplätze sind noch nicht konfiguriert.', @@ -2320,8 +2324,8 @@ $self->{texts} = { 'User Config' => 'Einstellungen', 'User Login' => 'Als Benutzer anmelden', 'User access' => 'Benutzerzugriff', - 'User data migration' => 'Benutzerdatenmigration', 'User deleted!' => 'Benutzer gelöscht!', + 'User list' => 'Benutzerliste', 'User login' => 'Benutzeranmeldung', 'User name' => 'Benutzername', 'User saved!' => 'Benutzer gespeichert!', @@ -2535,14 +2539,13 @@ $self->{texts} = { 'invoice_list' => 'debitorenbuchungsliste', 'kivitendo' => 'kivitendo', 'kivitendo Homepage' => 'Infos zu kivitendo', - 'kivitendo administration' => 'kivitendo Administration', 'kivitendo can fix these problems automatically.' => 'kivitendo kann solche Probleme automatisch beheben.', 'kivitendo has been extended to handle multiple clients within a single installation.' => 'kivitendo wurde um Mandantenfähigkeit erweitert.', - 'kivitendo has been switched to group-based access restrictions.' => 'kivitendo wurde auf eine gruppenbasierte Benutzerzugriffsverwaltung umgestellt.', 'kivitendo has found one or more problems in the general ledger.' => 'kivitendo hat ein oder mehrere Probleme im Hauptbuch gefunden.', 'kivitendo is about to update the database [ #1 ].' => 'kivitendo wird gleich die Datenbank [ #1 ] aktualisieren.', 'kivitendo is now able to manage warehouses instead of just tracking the amount of goods in your system.' => 'kivitendo enthält jetzt auch echte Lagerverwaultung anstatt reiner Mengenzählung.', 'kivitendo needs to update the authentication database before you can proceed.' => 'kivitendo muss die Authentifizierungsdatenbank aktualisieren, bevor Sie fortfahren können.', + 'kivitendo v#1 administration' => 'kivitendo v#1 Administration', 'kivitendo website (external)' => 'kivitendo-Webseite (extern)', 'kivitendo will then update the database automatically.' => 'kivitendo wird die Datenbank daraufhin automatisch aktualisieren.', 'lead deleted!' => 'Kundenquelle gelöscht', @@ -2567,7 +2570,6 @@ $self->{texts} = { 'not configured' => 'nicht konfiguriert', 'not delivered' => 'nicht geliefert', 'not executed' => 'nicht ausgeführt', - 'not logged in' => 'nicht eingeloggt', 'not running' => 'läuft nicht', 'not set' => 'nicht gesetzt', 'not shipped' => 'nicht geliefert', diff --git a/templates/webpages/admin/adminlogin.html b/templates/webpages/admin/adminlogin.html index 8cbaf29d7..263bef8dc 100644 --- a/templates/webpages/admin/adminlogin.html +++ b/templates/webpages/admin/adminlogin.html @@ -1,20 +1,22 @@ [%- USE T8 %] [% USE HTML %] -[% USE LxERP%] +[% USE LxERP %][%- USE L -%]
- - @@ -45,7 +46,3 @@  |  [%- LxERP.t8('Documentation') %]

- - diff --git a/templates/webpages/admin/backup_dataset.html b/templates/webpages/admin/backup_dataset.html index 128d46016..4306460af 100644 --- a/templates/webpages/admin/backup_dataset.html +++ b/templates/webpages/admin/backup_dataset.html @@ -82,16 +82,15 @@ - + -

- [% 'Back' | $T8 %] + [% 'Back' | $T8 %] diff --git a/templates/webpages/admin/backup_dataset_email_done.html b/templates/webpages/admin/backup_dataset_email_done.html index e1573a6f1..a70a2ee96 100644 --- a/templates/webpages/admin/backup_dataset_email_done.html +++ b/templates/webpages/admin/backup_dataset_email_done.html @@ -1,11 +1,8 @@ [%- USE T8 %] [%- USE LxERP %] -[%- USE HTML %] +[%- USE HTML %][%- USE L -%]

[% title %]

[% LxERP.t8('The dataset backup has been sent via email to #1.', to) | html %]

-
- - -
+

[% L.link("controller.pl?action=Admin/list_clients_and_users", LxERP.t8("Continue")) %] diff --git a/templates/webpages/admin/check_auth_database.html b/templates/webpages/admin/check_auth_database.html index c40b8ec7c..2a3c51d9f 100644 --- a/templates/webpages/admin/check_auth_database.html +++ b/templates/webpages/admin/check_auth_database.html @@ -1,9 +1,11 @@ [%- USE T8 %] -[%- USE HTML %] +[%- USE HTML %][%- USE L -%][%- USE LxERP -%]

[% title %]

-
+ + [%- L.hidden_tag("action", 'Admin/create_auth_db') %] + [%- L.hidden_tag("{AUTH}admin_password", LXCONFIG.authentication.admin_password) %]

[% 'The database for user management and authentication does not exist. You can create let kivitendo create it with the following parameters:' | $T8 %] @@ -12,19 +14,19 @@ - + - + - + - +
[% 'Host' | $T8 %]:[% HTML.escape(db_host) %][% HTML.escape(SELF.db_cfg.host) %]
[% 'Port' | $T8 %]:[% HTML.escape(db_port) %][% HTML.escape(SELF.db_cfg.port) %]
[% 'User name' | $T8 %]:[% HTML.escape(db_user) %][% HTML.escape(SELF.db_cfg.user) %]
[% 'Database name' | $T8 %]:[% HTML.escape(db_db) %][% HTML.escape(SELF.db_cfg.db) %]
@@ -38,22 +40,18 @@ [% 'If the database user listed above does not have the right to create a database then enter the name and password of the superuser below:' | $T8 %]

- - + - +
[% 'Superuser name' | $T8 %]:[% L.input_tag('db_superuser', '') %]
[% 'Password' | $T8 %]:[% L.input_tag('db_superuser_password', '', type='password') %]
- - - - - - + [% L.submit_tag("dummy", LxERP.t8("Create Dataset")) %] + [% L.button_tag("history.back()", LxERP.t8("Back")) %]
diff --git a/templates/webpages/admin/check_auth_tables.html b/templates/webpages/admin/check_auth_tables.html index 0e1d80aca..13a89f464 100644 --- a/templates/webpages/admin/check_auth_tables.html +++ b/templates/webpages/admin/check_auth_tables.html @@ -1,5 +1,5 @@ [%- USE T8 %] -[%- USE HTML %] +[%- USE HTML %][%- USE LxERP -%][%- USE L -%]

[% title %]

@@ -10,32 +10,30 @@ - + - + - + - +
[% 'Host' | $T8 %]:[% HTML.escape(db_host) %][% HTML.escape(SELF.db_cfg.host) %]
[% 'Port' | $T8 %]:[% HTML.escape(db_port) %][% HTML.escape(SELF.db_cfg.port) %]
[% 'User name' | $T8 %]:[% HTML.escape(db_user) %][% HTML.escape(SELF.db_cfg.user) %]
[% 'Database name' | $T8 %]:[% HTML.escape(db_db) %][% HTML.escape(SELF.db_cfg.db) %]

- [% 'If you want to change any of these parameters then press the "Back" button, edit the file "config/kivitendo.conf" and login into the admin module again.' | $T8 %] + [% 'If you want to change any of these parameters then press the "Back" button, edit the file "config/kivitendo.conf" and login into the admin module again.' | $T8 %]

-
- - - - - - + + [%- L.hidden_tag("action", 'Admin/create_auth_tables') %] + [%- L.hidden_tag("{AUTH}admin_password", LXCONFIG.authentication.admin_password) %] + [% L.submit_tag("dummy", LxERP.t8("Create tables")) %] + [% L.button_tag("history.back()", LxERP.t8("Back")) %]
diff --git a/templates/webpages/admin/dbadmin.html b/templates/webpages/admin/dbadmin.html index 23fb80501..bccd51494 100644 --- a/templates/webpages/admin/dbadmin.html +++ b/templates/webpages/admin/dbadmin.html @@ -3,7 +3,7 @@

[% title %]

- [% 'Back' | $T8 %] + [% 'Back' | $T8 %] @@ -48,7 +48,7 @@
- +
diff --git a/templates/webpages/admin/dbcreate.html b/templates/webpages/admin/dbcreate.html index b42c746be..67a19f7e3 100644 --- a/templates/webpages/admin/dbcreate.html +++ b/templates/webpages/admin/dbcreate.html @@ -1,14 +1,8 @@ [%- USE T8 %] [%- USE HTML %] -[%- USE LxERP %] +[%- USE LxERP %][%- USE L -%]

[% title %]

- +

[% LxERP.t8('The dataset #1 has been successfully created.', db) | html %]

-

[% LxERP.t8('The dataset #1 has been successfully created.', db) | html %]

- - - -

- -
+

[% L.link("controller.pl?action=Admin/list_clients_and_users", LxERP.t8("Continue")) %]

diff --git a/templates/webpages/admin/dbdelete.html b/templates/webpages/admin/dbdelete.html index a7aa79876..65f3311cc 100644 --- a/templates/webpages/admin/dbdelete.html +++ b/templates/webpages/admin/dbdelete.html @@ -1,13 +1,8 @@ [%- USE T8 %] [%- USE LxERP %] -[%- USE HTML %] +[%- USE HTML %][%- USE L -%]

[% title %]

-
+

[% LxERP.t8('The database #1 has been successfully deleted.', db) | html %]

-

[% LxERP.t8('The database #1 has been successfully deleted.', db) | html %]

- - - -

-
+

[% L.link("controller.pl?action=Admin/list_clients_and_users", LxERP.t8("Continue")) %]

diff --git a/templates/webpages/admin/dbupgrade_all_done.html b/templates/webpages/admin/dbupgrade_all_done.html index b88757a3b..aa3829739 100644 --- a/templates/webpages/admin/dbupgrade_all_done.html +++ b/templates/webpages/admin/dbupgrade_all_done.html @@ -1,5 +1,5 @@ [%- USE T8 %] -[% USE HTML%] +[% USE HTML%][%- USE LxERP -%][%- USE L -%] [% IF NOTHING_TO_DO %]

[% 'No datasets have been selected.' | $T8 %]

@@ -10,9 +10,4 @@

[% 'All database upgrades have been applied.' | $T8 %]

[% END %] -
- - - -
- +

[% L.link("controller.pl?action=Admin/list_clients_and_users", LxERP.t8("Continue")) %]

diff --git a/templates/webpages/admin/delete_dataset.html b/templates/webpages/admin/delete_dataset.html index e66dadf26..8c9961678 100644 --- a/templates/webpages/admin/delete_dataset.html +++ b/templates/webpages/admin/delete_dataset.html @@ -18,7 +18,7 @@ - + diff --git a/templates/webpages/admin/list_users.html b/templates/webpages/admin/list_users.html index 67a2d621d..0d75e6b0c 100644 --- a/templates/webpages/admin/list_users.html +++ b/templates/webpages/admin/list_users.html @@ -1,59 +1,90 @@ [%- USE T8 %] -[%- USE HTML %] +[%- USE HTML %][%- USE LxERP -%][%- USE L -%]

[% title %]

-

- - - - - - - - - - - - - +
+ - [% FOREACH row = MEMBERS %] -
- - - - - - - - - - +
+[%- IF !CLIENTS.size %] +

+ [% LxERP.t8("No clients have been created yet.") %] + [% LxERP.t8("In order to use kivitendo you have to create at least one client, one user, and grant that user access to the client.") %] +

+ +[%- ELSE %] +
[% 'Login Name' | $T8 %][% 'Name' | $T8 %][% 'Company' | $T8 %][% 'Templates' | $T8 %][% 'Print' | $T8 %][% 'Language' | $T8 %][% 'Dataset' | $T8 %][% 'Host' | $T8 %][% 'Last Action' | $T8 %]
 [% HTML.escape(row.login) %] [% HTML.escape(row.name) %] [% HTML.escape(row.company) %] [% HTML.escape(row.templates) %] [% HTML.escape(row.template_format) %] [% HTML.escape(row.countrycode) %] [% HTML.escape(row.dbname) %] [% IF row.dbhost %][% HTML.escape(row.dbhost) %][% ELSE %]localhost[% END %]  - [% IF( row.last_action ) %] - [% HTML.escape(row.last_action) %] - [% ELSE %] - [% 'not logged in' | $T8 %] - [% END %]
+ + + + + + + + + [%- FOREACH client = CLIENTS %] + + + + + + - [% END %] + [%- END %] +
[% 'Client name' | $T8 %][% 'Database ID' | $T8 %][% 'Database name' | $T8 %][% 'Database Host' | $T8 %][% 'Database User' | $T8 %]
[% HTML.escape(client.name) %][% HTML.escape(client.id) %][% HTML.escape(client.dbname) %][% HTML.escape(client.dbhost) %][% IF client.dbport %]:[%- HTML.escape(client.dbport) %][%- END %][% HTML.escape(client.dbuser) %]
+[%- END %] + - -


-

+
+[%- IF !USERS.size %] +

+ [% LxERP.t8("No users have been created yet.") %] + [% LxERP.t8("In order to use kivitendo you have to create at least one client, one user, and grant that user access to the client.") %] +

+[%- ELSE %] + + + + + + + + [% FOREACH user = USERS %] + [%- SET config = user.config_values %] + + + + + + [% END %] +
[% 'Login Name' | $T8 %][% 'Name' | $T8 %][% 'Language' | $T8 %]
[% HTML.escape(user.login) %][% HTML.escape(config.name) %][% HTML.escape(config.countrycode) %]
+[%- END %] +
+ - - - - + [% L.link(SELF.url_for(action="add_client"), LxERP.t8("Add Client")) %] + | + [% L.link(SELF.url_for(action="add_user"), LxERP.t8("Add User")) %] + | + [% L.link(SELF.url_for(action="edit_groups"), LxERP.t8("Edit groups")) %] + | + [% L.link(SELF.url_for(action="pg_database_administration", controller="admin.pl"), LxERP.t8("Pg Database Administration")) %] + | + [% L.link(SELF.url_for(action="printer_management", controller="admin.pl"), LxERP.t8("Printer Management")) %] + | [% IF LOCKED %] - - [% ELSE %] - + [% L.link(SELF.url_for(action="unlock_system"), LxERP.t8("Unlock System")) %] + [% ELSE %] + [% L.link(SELF.url_for(action="lock_system"), LxERP.t8("Lock System")) %] [% END %] - + | + [% L.link(SELF.url_for(action="logout"), LxERP.t8("Logout")) %]

[% 'Click on login name to edit!' | $T8 %]

diff --git a/templates/webpages/admin/restore_dataset_start_footer.html b/templates/webpages/admin/restore_dataset_start_footer.html index baece7d7e..fd414dc23 100644 --- a/templates/webpages/admin/restore_dataset_start_footer.html +++ b/templates/webpages/admin/restore_dataset_start_footer.html @@ -1,6 +1,6 @@ [%- USE T8 %] [%- USE LxERP %] -[% USE HTML %] +[% USE HTML %][%- USE L -%]
@@ -9,7 +9,6 @@ [%- LxERP.t8('The program\'s exit code was #1 ("0" usually means that everything went OK).', retval) | html %]

- - - - +

+ [% L.link("controller.pl?action=Admin/list_clients_and_users", LxERP.t8("Continue")) %] +

diff --git a/templates/webpages/admin/update_dataset.html b/templates/webpages/admin/update_dataset.html index 293ab88c1..fddf5c3dc 100644 --- a/templates/webpages/admin/update_dataset.html +++ b/templates/webpages/admin/update_dataset.html @@ -39,7 +39,7 @@ - +
diff --git a/templates/webpages/dbupgrade/footer.html b/templates/webpages/dbupgrade/footer.html index b1a2a5060..187eed4fb 100644 --- a/templates/webpages/dbupgrade/footer.html +++ b/templates/webpages/dbupgrade/footer.html @@ -1,10 +1,9 @@ [%- USE T8 %] [% USE HTML %]

[% '...done' | $T8 %]

-
+ - +

- -- 2.20.1