Merge branch 'master' of vc.linet-services.de:public/lx-office-erp
authorThomas Heck <theck@linet-services.de>
Thu, 30 Aug 2012 11:52:32 +0000 (13:52 +0200)
committerThomas Heck <theck@linet-services.de>
Thu, 30 Aug 2012 11:52:32 +0000 (13:52 +0200)
20 files changed:
SL/Controller/BackgroundJob.pm
SL/Controller/BackgroundJobHistory.pm [new file with mode: 0644]
SL/Controller/TaskServer.pm [new file with mode: 0644]
SL/DB/BackgroundJob.pm
SL/DB/BackgroundJobHistory.pm
SL/DB/Manager/BackgroundJobHistory.pm [new file with mode: 0644]
SL/InstallationCheck.pm
SL/System/TaskServer.pm
SL/Template/Plugin/L.pm
css/Mobile/background_jobs.css [new symlink]
css/Win2000/background_jobs.css [new symlink]
css/kivitendo/background_jobs.css [new symlink]
css/lx-office-erp/background_jobs.css [new file with mode: 0644]
locale/de/all
scripts/task_server.pl
templates/webpages/background_job/form.html
templates/webpages/background_job/list.html
templates/webpages/background_job_history/list.html [new file with mode: 0644]
templates/webpages/background_job_history/show.html [new file with mode: 0644]
templates/webpages/task_server/show.html [new file with mode: 0644]

index 3176eb6..57ec5c3 100644 (file)
@@ -83,7 +83,10 @@ sub action_execute {
     flash_later('error', $::locale->text('There was an error executing the background job.'));
   }
 
-  $self->redirect_to(controller => 'BackgroundJobHistory', action => 'show', id => $history->id);
+  $self->redirect_to(controller => 'BackgroundJobHistory',
+                     action     => 'show',
+                     id         => $history->id,
+                     back_to    => $self->url_for(action => 'edit', id => $self->background_job->id));
 }
 
 #
diff --git a/SL/Controller/BackgroundJobHistory.pm b/SL/Controller/BackgroundJobHistory.pm
new file mode 100644 (file)
index 0000000..54d7338
--- /dev/null
@@ -0,0 +1,69 @@
+package SL::Controller::BackgroundJobHistory;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::DB::BackgroundJobHistory;
+use SL::Helper::Flash;
+use SL::System::TaskServer;
+
+use Rose::Object::MakeMethods::Generic
+(
+  scalar                  => [ qw(history) ],
+  'scalar --get_set_init' => [ qw(task_server) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+__PACKAGE__->run_before('add_stylesheet');
+__PACKAGE__->run_before('check_task_server');
+
+#
+# actions
+#
+
+sub action_list {
+  my ($self) = @_;
+
+  $self->render('background_job_history/list',
+                title   => $::locale->text('Background job history'),
+                ENTRIES => SL::DB::Manager::BackgroundJobHistory->get_all_sorted);
+}
+
+sub action_show {
+  my ($self) = @_;
+
+  my $back_to = $::form->{back_to} || $self->url_for(action => 'list');
+
+  $self->history(SL::DB::BackgroundJobHistory->new(id => $::form->{id})->load);
+  $self->render('background_job_history/show',
+                title   => $::locale->text('View background job execution result'),
+                back_to => $back_to);
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('admin');
+}
+
+#
+# helpers
+#
+
+sub init_task_server {
+  return SL::System::TaskServer->new;
+}
+
+sub check_task_server {
+  my ($self) = @_;
+  flash('warning', $::locale->text('The task server does not appear to be running.')) if !$self->task_server->is_running;
+}
+
+sub add_stylesheet {
+  $::form->use_stylesheet('lx-office-erp/background_jobs.css');
+}
+
+1;
diff --git a/SL/Controller/TaskServer.pm b/SL/Controller/TaskServer.pm
new file mode 100644 (file)
index 0000000..2649f5f
--- /dev/null
@@ -0,0 +1,87 @@
+package SL::Controller::TaskServer;
+
+use strict;
+
+use parent qw(SL::Controller::Base);
+
+use SL::Helper::Flash;
+use SL::System::TaskServer;
+
+use Rose::Object::MakeMethods::Generic
+(
+  'scalar --get_set_init' => [ qw(task_server) ],
+);
+
+__PACKAGE__->run_before('check_auth');
+
+#
+# actions
+#
+
+sub action_show {
+  my ($self) = @_;
+
+  $::form->use_stylesheet('lx-office-erp/background_jobs.css');
+
+  flash('warning', $::locale->text('The task server does not appear to be running.')) if !$self->task_server->is_running;
+
+  $self->render('task_server/show',
+                title               => $::locale->text('Task server status'),
+                last_command_output => $::auth->get_session_value('TaskServer::last_command_output'));
+}
+
+sub action_start {
+  my ($self) = @_;
+
+  if ($self->task_server->is_running) {
+    flash_later('error', $::locale->text('The task server is already running.'));
+
+  } else {
+    if ($self->task_server->start) {
+      flash_later('info', $::locale->text('The task server was started successfully.'));
+    } else {
+      flash_later('error', $::locale->text('Starting the task server failed.'));
+    }
+
+    $::auth->set_session_value('TaskServer::last_command_output' => $self->task_server->last_command_output);
+  }
+
+  $self->redirect_to(action => 'show');
+}
+
+sub action_stop {
+  my ($self) = @_;
+
+  if (!$self->task_server->is_running) {
+    flash_later('error', $::locale->text('The task server is not running.'));
+
+  } else {
+    if ($self->task_server->stop) {
+      flash_later('info', $::locale->text('The task server was stopped successfully.'));
+    } else {
+      flash_later('error', $::locale->text('Stopping the task server failed. Output:'));
+    }
+
+    $::auth->set_session_value('TaskServer::last_command_output' => $self->task_server->last_command_output);
+  }
+
+  $self->redirect_to(action => 'show');
+}
+
+#
+# filters
+#
+
+sub check_auth {
+  $::auth->assert('admin');
+}
+
+#
+# helpers
+#
+
+sub init_task_server {
+  return SL::System::TaskServer->new;
+}
+
+1;
index ce1c399..9f2f7cb 100644 (file)
@@ -10,7 +10,6 @@ use SL::DB::Manager::BackgroundJob;
 
 use SL::DB::BackgroundJobHistory;
 
-use SL::BackgroundJob::Test;
 use SL::System::Process;
 
 __PACKAGE__->before_save('_before_save_set_next_run_at');
@@ -38,6 +37,7 @@ sub run {
   my $history;
 
   my $ok = eval {
+    eval "require $package" or die $@;
     my $result = $package->new->run($self);
 
     $history = SL::DB::BackgroundJobHistory
@@ -90,7 +90,7 @@ sub validate {
   }
 
   eval {
-    DateTime::Event::Cron->new_from_cron($self->cron_spec)->next(DateTime->now_local);
+    DateTime::Event::Cron->new_from_cron($self->cron_spec || '* * * * *')->next(DateTime->now_local);
     1;
   } or push @errors, $::locale->text('The execution schedule is invalid.');
 
index f8e08f8..4be8f66 100644 (file)
@@ -6,8 +6,6 @@ package SL::DB::BackgroundJobHistory;
 use strict;
 
 use SL::DB::MetaSetup::BackgroundJobHistory;
-
-# Creates get_all, get_all_count, get_all_iterator, delete_all and update_all.
-__PACKAGE__->meta->make_manager_class;
+use SL::DB::Manager::BackgroundJobHistory;
 
 1;
diff --git a/SL/DB/Manager/BackgroundJobHistory.pm b/SL/DB/Manager/BackgroundJobHistory.pm
new file mode 100644 (file)
index 0000000..132188f
--- /dev/null
@@ -0,0 +1,19 @@
+package SL::DB::Manager::BackgroundJobHistory;
+
+use strict;
+
+use SL::DB::Helper::Manager;
+use base qw(SL::DB::Helper::Manager);
+
+use SL::DB::Helper::Sorted;
+
+sub object_class { 'SL::DB::BackgroundJobHistory' }
+
+__PACKAGE__->make_manager_methods;
+
+sub _sort_spec {
+  return ( default => [ 'run_at', 1 ],
+           columns => { SIMPLE => 'ALL' } );
+}
+
+1;
index 6ae1494..397a99f 100644 (file)
@@ -60,7 +60,7 @@ sub module_available {
   my $module  = $_[0];
   my $version = $_[1] || '' ;
 
-  my $got = eval "use $module $version; 1";
+  my $got = eval "use $module $version (); 1";
 
   if ($got) {
     return ($got, $module->VERSION);
index bceb8f5..57d0ba8 100644 (file)
@@ -10,6 +10,7 @@ use Rose::Object::MakeMethods::Generic (
 
 use File::Slurp;
 use File::Spec::Functions qw(:ALL);
+use File::Temp qw(tempfile);
 
 use SL::System::Process;
 
@@ -19,6 +20,8 @@ use constant {
   ERR_PROCESS  => -2,
 };
 
+use constant PID_BASE => "users/pid";
+
 sub status {
   my ($self) = @_;
 
@@ -61,19 +64,26 @@ sub wake_up {
 sub _read_pid {
   my ($self) = @_;
 
-  my $exe_dir       = SL::System::Process->exe_dir;
-  my $pid_file_name = join '.', splitdir($exe_dir), 'config.lx_office.conf.pid';
-  my $pid_file_path = catfile(catdir($exe_dir, 'users', 'pid'), $pid_file_name);
+  my $exe_dir = SL::System::Process->exe_dir;
+
+  foreach my $conf (qw(kivitendo.conf lx_office.conf kivitendo.conf.default)) {
+    my $pid_file_path = catfile(catdir($exe_dir, splitdir(PID_BASE())), "config.${conf}.pid");
 
-  return undef unless -f $pid_file_path;
-  return join('', read_file($pid_file_path)) * 1;
+    return join('', read_file($pid_file_path)) * 1 if -f $pid_file_path;
+  }
 }
 
 sub _run_script_command {
   my ($self, $command) = @_;
 
-  my $exe = catfile(catdir(SL::System::Process->exe_dir, 'scripts'), 'task_server.pl');
-  $self->last_command_output(`${exe} ${command}`);
+  my ($fh, $file_name) = tempfile();
+  my $exe              = catfile(catdir(SL::System::Process->exe_dir, 'scripts'), 'task_server.pl');
+
+  system "${exe} ${command} >> ${file_name} 2>&1";
+
+  $fh->close;
+
+  $self->last_command_output(read_file($file_name));
 
   return $? == 0 ? 1 : undef;
 }
index 43ac5d2..8523db9 100644 (file)
@@ -570,6 +570,18 @@ sub dump {
   return '<pre>' . Data::Dumper::Dumper(@_) . '</pre>';
 }
 
+sub truncate {
+  my ($self, $text, @slurp) = @_;
+  my %params                = _hashify(@slurp);
+
+  $params{at}             ||= 50;
+  $params{at}               =  3 if 3 > $params{at};
+  $params{at}              -= 3;
+
+  return $text if length($text) < $params{at};
+  return substr($text, 0, $params{at}) . '...';
+}
+
 1;
 
 __END__
@@ -918,6 +930,16 @@ the resulting tab will get ignored by C<tabbed>:
 
   L.tab('Awesome tab wih much info', '_much_info.html', if => SELF.wants_all)
 
+=item C<truncate $text, %params>
+
+Returns the C<$text> truncated after a certain number of
+characters.
+
+The number of characters to truncate at is determined by the parameter
+C<at> which defaults to 50. If the text is longer than C<$params{at}>
+then it will be truncated and postfixed with '...'. Otherwise it will
+be returned unmodified.
+
 =back
 
 =head1 MODULE AUTHORS
diff --git a/css/Mobile/background_jobs.css b/css/Mobile/background_jobs.css
new file mode 120000 (symlink)
index 0000000..8031c94
--- /dev/null
@@ -0,0 +1 @@
+../lx-office-erp/background_jobs.css
\ No newline at end of file
diff --git a/css/Win2000/background_jobs.css b/css/Win2000/background_jobs.css
new file mode 120000 (symlink)
index 0000000..8031c94
--- /dev/null
@@ -0,0 +1 @@
+../lx-office-erp/background_jobs.css
\ No newline at end of file
diff --git a/css/kivitendo/background_jobs.css b/css/kivitendo/background_jobs.css
new file mode 120000 (symlink)
index 0000000..8031c94
--- /dev/null
@@ -0,0 +1 @@
+../lx-office-erp/background_jobs.css
\ No newline at end of file
diff --git a/css/lx-office-erp/background_jobs.css b/css/lx-office-erp/background_jobs.css
new file mode 100644 (file)
index 0000000..e390bb5
--- /dev/null
@@ -0,0 +1,14 @@
+/* Jobverwaltung */
+.background_job_list    tbody pre,
+.background_job_details tbody pre {
+  margin: 0px;
+}
+
+.background_job_details tbody th {
+  text-align: right;
+  vertical-align: top;
+}
+
+.background_job_details tbody td {
+  vertical-align: top;
+}
index 932db27..b4b6628 100644 (file)
@@ -505,6 +505,7 @@ $self->{texts} = {
   'Current Earnings'            => 'Gewinn',
   'Current assets account'      => 'Konto für Umlaufvermögen',
   'Current profile'             => 'Aktuelles Profil',
+  'Current status'              => 'Aktueller Status',
   'Current unit'                => 'Aktuelle Einheit',
   'Current value:'              => 'Aktueller Wert:',
   'Custom Variables'            => 'Benutzerdefinierte Variablen',
@@ -819,6 +820,7 @@ $self->{texts} = {
   'Execution date from'         => 'Ausführungsdatum von',
   'Execution date to'           => 'Ausführungsdatum bis',
   'Execution schedule'          => 'Ausführungszeitplan',
+  'Execution status'            => 'Ausführungsstatus',
   'Execution type'              => 'Ausführungsart',
   'Existing Buchungsgruppen'    => 'Existierende Buchungsgruppen',
   'Existing Datasets'           => 'Existierende Datenbanken',
@@ -1077,6 +1079,7 @@ $self->{texts} = {
   'Last Service Number'         => 'Letzte Dienstleistungsnr.',
   'Last Transaction'            => 'Letzte Buchung',
   'Last Vendor Number'          => 'Letzte Lieferantennummer',
+  'Last command output'         => 'Ausgabe des letzten Befehls',
   'Last run at'                 => 'Zeitpunkt letzter Ausführung',
   'Lastcost (with X being a number)' => 'Einkaufspreis (X ist eine fortlaufende Zahl)',
   'Lead'                        => 'Kundenquelle',
@@ -1560,12 +1563,14 @@ $self->{texts} = {
   'Required by'                 => 'Lieferdatum',
   'Reset'                       => 'Zurücksetzen',
   'Restore Dataset'             => 'Datenbank wiederherstellen',
+  'Result'                      => 'Ergebnis',
   'Revenue'                     => 'Erlöskonto',
   'Revenue Account'             => 'Erlöskonto',
   'Revenues EU with UStId'      => 'Erl&ouml;se EU m. UStId',
   'Revenues EU without UStId'   => 'Erl&ouml;se EU o. UStId',
   'Review of Aging list'        => 'Altersstrukturliste',
   'Right'                       => 'Rechts',
+  'Run at'                      => 'Ausgeführt um',
   'SAVED'                       => 'Gespeichert',
   'SAVED FOR DUNNING'           => 'Gespeichert',
   'SCREENED'                    => 'Angezeigt',
@@ -1713,9 +1718,11 @@ $self->{texts} = {
   'Start Dunning Process'       => 'Mahnprozess starten',
   'Start analysis'              => 'Analyse beginnen',
   'Start date'                  => 'Startdatum',
+  'Start task server'           => 'Task-Server starten',
   'Start the correction assistant' => 'Korrekturassistenten starten',
   'Startdate_coa'               => 'Gültig ab',
   'Starting Balance'            => 'Eröffnungsbilanzwerte',
+  'Starting the task server failed.' => 'Das Starten des Task-Servers schlug fehl.',
   'Starting with version 2.6.3 the configuration files in "config" have been consolidated.' => 'Ab Version 2.6.3 wurden die Konfiguration vereinfacht und es gibt nur noch eine Konfigurationsdatei im Verzeichnis config',
   'Statement'                   => 'Sammelrechnung',
   'Statement Balance'           => 'Sammelrechnungsbilanz',
@@ -1732,6 +1739,8 @@ $self->{texts} = {
   'Stock Qty for Date'          => 'Lagerbestand am',
   'Stock value'                 => 'Bestandswert',
   'Stocked Qty'                 => 'Lagermenge',
+  'Stop task server'            => 'Task-Server beenden',
+  'Stopping the task server failed. Output:' => 'Das Beenden des Task-Servers schlug fehl.',
   'Storno'                      => 'Storno',
   'Storno (one letter abbreviation)' => 'S',
   'Storno Invoice'              => 'Stornorechnung',
@@ -1760,6 +1769,7 @@ $self->{texts} = {
   'Target bank account'         => 'Zielkonto',
   'Target table'                => 'Zieltabelle',
   'Task server control'         => 'Task-Server-Steuerung',
+  'Task server status'          => 'Task-Server-Status',
   'Tax'                         => 'Steuer',
   'Tax Consultant'              => 'Steuerberater/-in',
   'Tax Included'                => 'Steuer im Preis inbegriffen',
@@ -1825,6 +1835,7 @@ $self->{texts} = {
   'The background job has been created.' => 'Der Hintergrund-Job wurden angelegt.',
   'The background job has been deleted.' => 'Der Hintergrund-Job wurde gelöscht.',
   'The background job has been saved.' => 'Der Hintergrund-Job wurde gespeichert.',
+  'The background job was executed successfully.' => 'Der Hintergrund-Job wurde erfolgreich ausgeführt.',
   'The backup you upload here has to be a file created with &quot;pg_dump -o -Ft&quot;.' => 'Die von Ihnen hochzuladende Sicherungsdatei muss mit dem Programm und den Parametern &quot;pg_dump -o -Ft&quot; erstellt worden sein.',
   'The bank information must not be empty.' => 'Die Bankinformationen müssen vollständig ausgefüllt werden.',
   'The base unit does not exist or it is about to be deleted in row %d.' => 'Die Basiseinheit in Zeile %d existiert nicht oder soll gel&ouml;scht werden.',
@@ -1944,6 +1955,10 @@ $self->{texts} = {
   'The tables for user management and authentication do not exist. They will be created in the next step in the following database:' => 'Die Tabellen zum Speichern der Benutzerdaten und zur Benutzerauthentifizierung wurden nicht gefunden. Sie werden in der folgenden Datenbank angelegt:',
   'The tabulator character'     => 'Das Tabulator-Symbol',
   'The task server does not appear to be running.' => 'Der Task-Server scheint nicht zu laufen.',
+  'The task server is already running.' => 'Der Task-Server läuft bereits.',
+  'The task server is not running.' => 'Der Task-Server läuft nicht.',
+  'The task server was started successfully.' => 'Der Task-Server wurde erfolgreich gestartet.',
+  'The task server was stopped successfully.' => 'Der Task-Server wurde erfolgreich beendet.',
   'The third way is to download the module from the above mentioned URL and to install the module manually following the installations instructions contained in the source archive.' => 'Die dritte Variante besteht darin, das Paket von der oben genannten URL herunterzuladen und es manuell zu installieren. Beachten Sie dabei die im Paket enthaltenen Installationsanweisungen.',
   'The transaction is shown below in its current state.' => 'Nachfolgend wird angezeigt, wie die Buchung momentan aussieht.',
   'The unit has been saved.'    => 'Die Einheit wurde gespeichert.',
@@ -1964,6 +1979,7 @@ $self->{texts} = {
   'There are #1 unfinished follow-ups of which #2 are due.' => 'Es gibt #1 Wiedervorlage(n), von denen #2 fällig ist/sind.',
   'There are bookings to the account 3803 after 01.01.2007. If you didn\'t change this account manually to 19% the bookings are probably incorrect.' => 'Das Konto 3803 wurde nach dem 01.01.2007 bebucht. Falls Sie dieses Konto nicht manuell auf 19% gestellt haben sind die Buchungen wahrscheinlich mit falscher Umsatzsteuer gebucht worden.',
   'There are four tax zones.'   => 'Es gibt vier Steuerzonen.',
+  'There are no entries in the background job history.' => 'Es gibt keine Einträge im Hintergrund-Job-Verlauf.',
   'There are no items in stock.' => 'Dieser Artikel ist nicht eingelagert.',
   'There are no items on your TODO list at the moment.' => 'Ihre Aufgabenliste enth&auml;lt momentan keine Eintr&auml;ge.',
   'There are still entries in the database for which no unit has been assigned.' => 'Es gibt noch Eintr&auml;ge in der Datenbank, f&uuml;r die keine Einheit zugeordnet ist.',
@@ -1975,6 +1991,7 @@ $self->{texts} = {
   'There is not enough available of \'#1\' at warehouse \'#2\', bin \'#3\', #4, for the transfer of #5.' => 'Von \'#1\' ist in Lager \'#2\', Lagerplatz \'#3\', #4 nicht gen&uuml;gend eingelagert, um insgesamt #5 auszulagern.',
   'There is not enough left of \'#1\' in bin \'#2\' for the removal of #3.' => 'In Lagerplatz \'#2\' ist nicht genug von \'#1\' vorhanden, um #3 zu entnehmen.',
   'There is nothing to do in this step.' => 'In diesem Schritt gibt es nichts mehr zu tun.',
+  'There was an error executing the background job.' => 'Bei der Ausführung des Hintergrund-Jobs trat ein Fehler auf.',
   'Therefore the definition of "kg" with the base unit "g" and a factor of 1000 is valid while defining "g" with a base unit of "kg" and a factor of "0.001" is not.' => 'So ist die Definition von "kg" mit der Basiseinheit "g" und dem Faktor 1000 zulässig, die Definition von "g" mit der Basiseinheit "kg" und dem Faktor "0,001" hingegen nicht.',
   'Therefore there\'s no need to create the same article more than once if it is sold or bought in/from another tax zone.' => 'Deswegen muss man den gleichen Artikel nicht mehr mehrmals anlegen, wenn er in verschiedenen Steuerzonen gehandelt werden soll.',
   'These units can be based on other units so that kivitendo can convert prices when the user switches from one unit to another.' => 'Einheiten können auf anderen Einheiten basieren, sodass kivitendo Preise automatisch umrechnen kann, wenn die Benutzer zwischen solchen Einheiten umschalten.',
@@ -2121,6 +2138,9 @@ $self->{texts} = {
   'Version'                     => 'Version',
   'Version 2.4.0 introduces two new concepts: tax zones and Buchungsgruppen.' => 'Version 2.4.0 hat zwei neue Konzepte eingeführt: Steuerzonen und Buchungsgruppen.',
   'View SEPA export'            => 'SEPA-Export-Details ansehen',
+  'View background job execution result' => 'Verlauf der Hintergrund-Job-Ausführungen anzeigen',
+  'View background job history' => 'Hintergrund-Job-Verlauf anzeigen',
+  'View background jobs'        => 'Hintergrund-Jobs anzeigen',
   'View warehouse content'      => 'Lagerbestand ansehen',
   'View/edit all employees sales documents' => 'Bearbeiten/ansehen der Verkaufsdokumente aller Mitarbeiter',
   'Von Konto: '                 => 'von Konto: ',
@@ -2268,6 +2288,7 @@ $self->{texts} = {
   'emailed to'                  => 'gemailt an',
   'empty'                       => 'leer',
   'executed'                    => 'ausgeführt',
+  'failed'                      => 'fehlgeschlagen',
   'female'                      => 'weiblich',
   'follow_up_list'              => 'wiedervorlageliste',
   'for'                         => 'f&uuml;r',
@@ -2313,6 +2334,7 @@ $self->{texts} = {
   'not delivered'               => 'nicht geliefert',
   'not executed'                => 'nicht ausgeführt',
   'not logged in'               => 'nicht eingeloggt',
+  'not running'                 => 'läuft nicht',
   'not set'                     => 'nicht gesetzt',
   'not transferred in yet'      => 'noch nicht eingelagert',
   'not transferred out yet'     => 'noch nicht ausgelagert',
@@ -2354,6 +2376,7 @@ $self->{texts} = {
   'reset'                       => 'zurücksetzen',
   'return_material'             => 'Materialr&uuml;ckgabe',
   'rfq_list'                    => 'anfragenliste',
+  'running'                     => 'läuft',
   'sales tax identification number' => 'USt-IdNr.',
   'sales_delivery_order_list'   => 'lieferscheinliste_verkauf',
   'sales_order'                 => 'Kundenauftrag',
@@ -2369,6 +2392,7 @@ $self->{texts} = {
   'soldtotal'                   => 'Verkaufte Anzahl',
   'stock'                       => 'Einlagerung',
   'submit'                      => 'abschicken',
+  'succeeded'                   => 'erfolgreich',
   'tax_chartaccno'              => 'Automatikkonto',
   'tax_percent'                 => 'Prozentsatz',
   'tax_rate'                    => 'Prozent',
index 8c17ca7..eb407cb 100755 (executable)
@@ -2,17 +2,17 @@
 
 use strict;
 
-BEGIN {
-  require Cwd;
+my $exe_dir;
 
-  my $dir =  $0;
-  $dir    =  Cwd::getcwd() . '/' . $dir unless $dir =~ m|^/|;
-  $dir    =~ s|[^/]+$|..|;
+BEGIN {
+  use SL::System::Process;
+  $exe_dir = SL::System::Process::exe_dir;
 
-  chdir($dir) || die "Cannot change directory to ${dir}\n";
+  unshift @INC, "${exe_dir}/modules/override"; # Use our own versions of various modules (e.g. YAML).
+  push    @INC, "${exe_dir}/modules/fallback"; # Only use our own versions of modules if there's no system version.
+  unshift @INC, $exe_dir;
 
-  unshift @INC, "modules/override"; # Use our own versions of various modules (e.g. YAML).
-  push    @INC, "modules/fallback"; # Only use our own versions of modules if there's no system version.
+  chdir($exe_dir) || die "Cannot change directory to ${exe_dir}\n";
 }
 
 use CGI qw( -no_xhtml);
@@ -21,6 +21,8 @@ use Daemon::Generic;
 use Data::Dumper;
 use DateTime;
 use English qw(-no_match_vars);
+use File::Spec;
+use List::Util qw(first);
 use POSIX qw(setuid setgid);
 use SL::Auth;
 use SL::DB::BackgroundJob;
@@ -31,6 +33,7 @@ use SL::InstanceConfiguration;
 use SL::LXDebug;
 use SL::LxOfficeConf;
 use SL::Locale;
+use SL::System::TaskServer;
 
 our %lx_office_conf;
 
@@ -113,6 +116,8 @@ sub gd_run {
         $::locale = Locale->new($::lx_office_conf{system}->{language});
         $::form   = Form->new;
 
+        chdir $exe_dir;
+
         $job->run;
       }
 
@@ -138,15 +143,19 @@ sub gd_run {
   }
 }
 
-my $cwd     = getcwd();
-my $pidbase = "${cwd}/users/pid";
+chdir $exe_dir;
+
+mkdir SL::System::TaskServer::PID_BASE() if !-d SL::System::TaskServer::PID_BASE();
+
+my $file = first { -f } ("${exe_dir}/config/kivitendo.conf", "${exe_dir}/config/lx_office.conf", "${exe_dir}/config/kivitendo.conf.default");
+
+die "No configuration file found." unless $file;
 
-mkdir($pidbase) if !-d $pidbase;
+$file = File::Spec->abs2rel(Cwd::abs_path($file), Cwd::abs_path($exe_dir));
 
-my $file = -f "${cwd}/config/lx_office.conf" ? "${cwd}/config/lx_office.conf" : "${cwd}/config/lx_office.conf.default";
 newdaemon(configfile => $file,
           progname   => 'kivitendo-task-server',
-          pidbase    => "${pidbase}/",
+          pidbase    => SL::System::TaskServer::PID_BASE() . '/',
           );
 
 1;
index c3d784c..a4753bb 100644 (file)
@@ -8,29 +8,29 @@
 
   <table>
    <tr>
-    <td>[%- LxERP.t8('Active') %]</td>
+    <th align="right">[%- LxERP.t8('Active') %]</th>
     <td>[% L.yes_no_tag("background_job.active", SELF.background_job.active) %]</td>
    </tr>
 
    <tr>
-    <td>[%- LxERP.t8('Execution type') %]</td>
+    <th align="right">[%- LxERP.t8('Execution type') %]</th>
     <td>[% L.select_tag("background_job.type", L.options_for_select([ [ 'once', LxERP.t8('one-time execution') ], [ 'interval', LxERP.t8('repeated execution') ] ],
                                                                     'default' => SELF.background_job.type)) %]</td>
    </tr>
 
    <tr>
-    <td>[%- LxERP.t8('Package name') %]</td>
+    <th align="right">[%- LxERP.t8('Package name') %]</th>
     <td>[% L.input_tag("background_job.package_name", SELF.background_job.package_name, 'size' => 40) %]</td>
    </tr>
 
    <tr>
-    <td>[%- LxERP.t8('Execution schedule') %]</td>
+    <th align="right">[%- LxERP.t8('Execution schedule') %]</th>
     <td>[% L.input_tag("background_job.cron_spec", SELF.background_job.cron_spec, 'size' => 40) %]</td>
    </tr>
 
    <tr>
-    <td>[%- LxERP.t8('Data') %]</td>
-    <td>[% L.textarea_tag("background_job.data", SELF.background_job.data, 'cols' => 80, 'rows' => 10) %]</td>
+    <th align="right" valign="top">[%- LxERP.t8('Data') %]</th>
+    <td valign="top">[% L.textarea_tag("background_job.data", SELF.background_job.data, 'cols' => 80, 'rows' => 10) %]</td>
    </tr>
 
   </table>
index c0ef999..9f4422d 100644 (file)
   [%- ELSE %]
    <table id="background_job_list" width="100%">
     <thead>
-    <tr class="listheading">
-     <th>[%- LxERP.t8('Execution type') %]</th>
-     <th>[%- LxERP.t8('Package name') %]</th>
-     <th>[%- LxERP.t8('Active') %]</th>
-     <th>[%- LxERP.t8('Execution schedule') %]</th>
-     <th>[%- LxERP.t8('Last run at') %]</th>
-     <th>[%- LxERP.t8('Next run at') %]</th>
-    </tr>
+     <tr class="listheading">
+      <th>[%- LxERP.t8('Execution type') %]</th>
+      <th>[%- LxERP.t8('Package name') %]</th>
+      <th>[%- LxERP.t8('Active') %]</th>
+      <th>[%- LxERP.t8('Execution schedule') %]</th>
+      <th>[%- LxERP.t8('Last run at') %]</th>
+      <th>[%- LxERP.t8('Next run at') %]</th>
+     </tr>
     </thead>
 
     <tbody>
@@ -35,6 +35,7 @@
       [% ELSE %]
        [%- HTML.escape(background_job.type) %]
       [%- END %]
+     </td>
      <td>
       <a href="[% SELF.url_for(action => 'edit', id => background_job.id) %]">
        [%- HTML.escape(background_job.package_name) %]
@@ -68,6 +69,8 @@
   <p>
    <a href="[% SELF.url_for(action => 'new') %]">[%- LxERP.t8('Create new background job') %]</a>
    |
+   <a href="[% SELF.url_for(controller => 'BackgroundJobHistory', action => 'list') %]">[%- LxERP.t8('View background job history') %]</a>
+   |
    <a href="[% SELF.url_for(controller => 'TaskServer', action => 'show') %]">[%- LxERP.t8('Task server control') %]</a>
   </p>
  </form>
diff --git a/templates/webpages/background_job_history/list.html b/templates/webpages/background_job_history/list.html
new file mode 100644 (file)
index 0000000..83c8dae
--- /dev/null
@@ -0,0 +1,61 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+
+<body>
+ <div class="listtop">[% FORM.title %]</div>
+
+[%- INCLUDE 'common/flash.html' %]
+
+ <form method="post" action="controller.pl">
+  [% IF !ENTRIES.size %]
+   <p>
+    [%- LxERP.t8('There are no entries in the background job history.') %]
+   </p>
+
+  [%- ELSE %]
+   <table id="background_job_history_list" class="background_job_list" width="100%">
+    <thead>
+     <tr class="listheading">
+      <th>[%- LxERP.t8('Package name') %]</th>
+      <th>[%- LxERP.t8('Run at') %]</th>
+      <th>[%- LxERP.t8('Execution status') %]</th>
+      <th>[%- LxERP.t8('Result') %]</th>
+      <th>[%- LxERP.t8('Error') %]</th>
+     </tr>
+    </thead>
+
+    <tbody>
+    [%- FOREACH entry = ENTRIES %]
+    <tr class="listrow[% loop.count % 2 %]" id="background_job_history_id_[% entry.id %]">
+     <td>
+      <a href="[% SELF.url_for(action => 'show', id => entry.id) %]">
+       [%- HTML.escape(entry.package_name) %]
+      </a>
+     </td>
+     <td>[%- HTML.escape(entry.run_at.to_lxoffice('precision' => 'second')) %]</td>
+     <td>
+      [%- IF entry.status == 'success' %]
+       [%- LxERP.t8('succeeded') %]
+      [%- ELSIF entry.status == 'failure' %]
+       [%- LxERP.t8('failed') %]
+      [%- ELSE %]
+       [%- HTML.escape(entry.status) %]
+      [%- END %]
+     </td>
+     <td>[%- HTML.escape(entry.result) %]</td>
+     <td>[% IF entry.error_col %]<pre>[%- HTML.escape(L.truncate(entry.error_col)) %]</pre>[%- END %]</td>
+    </tr>
+    [%- END %]
+    </tbody>
+   </table>
+  [%- END %]
+
+  <hr size="3" noshade>
+
+  <p>
+   <a href="[% SELF.url_for(controller => 'BackgroundJob', action => 'list') %]">[%- LxERP.t8('View background jobs') %]</a>
+   |
+   <a href="[% SELF.url_for(controller => 'TaskServer', action => 'show') %]">[%- LxERP.t8('Task server control') %]</a>
+  </p>
+ </form>
+</body>
+</html>
diff --git a/templates/webpages/background_job_history/show.html b/templates/webpages/background_job_history/show.html
new file mode 100644 (file)
index 0000000..0a0584f
--- /dev/null
@@ -0,0 +1,54 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+<body>
+
+ <div class="listtop">[% FORM.title %]</div>
+
+[%- INCLUDE 'common/flash.html' %]
+
+ <table id="background_job_history_details" class="background_job_details">
+  <tbody>
+   <tr class="listrow0">
+    <th>[%- LxERP.t8('Package name') %]</th>
+    <td>[%- HTML.escape(SELF.history.package_name) %]</td>
+   </tr>
+
+   <tr class="listrow1">
+    <th>[%- LxERP.t8('Run at') %]</th>
+    <td>[%- HTML.escape(SELF.history.run_at.to_lxoffice('precision' => 'second')) %]</td>
+   </tr>
+
+   <tr class="listrow0">
+    <th>[%- LxERP.t8('Execution status') %]</th>
+    <td>
+     [%- IF SELF.history.status == 'success' %]
+     [%- LxERP.t8('succeeded') %]
+     [%- ELSIF SELF.history.status == 'failed' %]
+     [%- LxERP.t8('failed') %]
+     [%- ELSE %]
+     [%- HTML.escape(SELF.history.status) %]
+     [%- END %]
+    </td>
+   </tr>
+
+   <tr class="listrow1">
+    <th>[%- LxERP.t8('Result') %]</th>
+    <td>[%- HTML.escape(SELF.history.result) %]</td>
+   </tr>
+
+   <tr class="listrow0">
+    <th>[%- LxERP.t8('Error') %]</th>
+    <td>[% IF SELF.history.error_col %]<pre>[%- HTML.escape(SELF.history.error_col) %]</pre>[%- END %]</td>
+   </tr>
+
+   <tr class="listrow1">
+    <th>[%- LxERP.t8('Data') %]</th>
+    <td>[% IF SELF.history.data %]<pre>[%- HTML.escape(SELF.history.data) %]</pre>[%- END %]</td>
+   </tr>
+  </tbody>
+ </table>
+
+ <p>
+  <a href="[% back_to %]">[%- LxERP.t8('Back') %]</a>
+ </p>
+</body>
+</html>
diff --git a/templates/webpages/task_server/show.html b/templates/webpages/task_server/show.html
new file mode 100644 (file)
index 0000000..2b1c279
--- /dev/null
@@ -0,0 +1,37 @@
+[% USE HTML %][% USE L %][% USE LxERP %]
+<body>
+
+ <div class="listtop">[% FORM.title %]</div>
+
+[%- INCLUDE 'common/flash.html' %]
+
+ <table id="background_job_history_details" class="background_job_details">
+  <tbody>
+   <tr class="listrow0">
+    <th>[%- LxERP.t8('Current status') %]</th>
+    <td>[% IF SELF.task_server.is_running %][%- LxERP.t8('running') %][%- ELSE %][%- LxERP.t8('not running') %][%- END %]</td>
+   </tr>
+
+[%- IF last_command_output %]
+   <tr class="listrow1">
+    <th>[%- LxERP.t8('Last command output') %]</th>
+    <td><pre>[% HTML.escape(last_command_output) %]</pre></td>
+   </tr>
+[%- END %]
+
+  </tbody>
+ </table>
+
+ <p>
+[% IF SELF.task_server.is_running %]
+  <a href="[% SELF.url_for(action => 'stop') %]">[%- LxERP.t8('Stop task server') %]</a>
+[%- ELSE %]
+  <a href="[% SELF.url_for(action => 'start') %]">[%- LxERP.t8('Start task server') %]</a>
+[%- END %]
+  |
+  <a href="[% SELF.url_for(controller => 'BackgroundJob', action => 'list') %]">[%- LxERP.t8('View background jobs') %]</a>
+  |
+  <a href="[% SELF.url_for(controller => 'BackgroundJobHistory', action => 'list') %]">[%- LxERP.t8('View background job history') %]</a>
+ </p>
+</body>
+</html>