]> git.sur5r.net Git - bacula/bacula/commitdiff
Add new tray-monitor files that were omitted in the backport from Enterprise
authorKern Sibbald <kern@sibbald.com>
Mon, 10 Jul 2017 15:50:29 +0000 (17:50 +0200)
committerKern Sibbald <kern@sibbald.com>
Mon, 10 Jul 2017 15:50:29 +0000 (17:50 +0200)
18 files changed:
bacula/src/qt-console/tray-monitor/bacula-tray-monitor.conf.in [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/conf.h [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/install_conf_file.in
bacula/src/qt-console/tray-monitor/main-conf.ui [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/res-conf.ui [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/run.ui [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/runjob.cpp [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/runjob.h [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/sd-monitor.ui [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/sdstatus.cpp [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/sdstatus.h [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/status.cpp [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/status.h [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/task.cpp [new file with mode: 0644]
bacula/src/qt-console/tray-monitor/tray-monitor.conf.in
bacula/src/qt-console/tray-monitor/tray-monitor.pro.in
bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw32.in
bacula/src/qt-console/tray-monitor/tray-monitor.pro.mingw64.in

diff --git a/bacula/src/qt-console/tray-monitor/bacula-tray-monitor.conf.in b/bacula/src/qt-console/tray-monitor/bacula-tray-monitor.conf.in
new file mode 100644 (file)
index 0000000..4a9eff7
--- /dev/null
@@ -0,0 +1,32 @@
+#
+# Bacula Tray Monitor Configuration File
+#
+# Copyright (C) 2000-2017 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+
+Monitor {
+  Name = @basename@-mon
+  RefreshInterval = 120 seconds
+}
+   
+Client {
+  Name = @basename@-fd
+  Password = "@mon_fd_password@"          # password for FileDaemon
+  Address = @hostname@
+  Port = @fd_port@
+}
+
+#Storage {
+#  Name = @basename@-sd
+#  Address = @hostname@
+#  Port = @sd_port@
+#  Password = "@mon_sd_password@"          # password for StorageDaemon
+#}
+#
+#Director {
+#  Name = @basename@-dir
+#  Address = @hostname@
+#  Port = @dir_port@
+#  Password = "@mon_dir_password@"         # password for the Directors   
+#}
diff --git a/bacula/src/qt-console/tray-monitor/conf.h b/bacula/src/qt-console/tray-monitor/conf.h
new file mode 100644 (file)
index 0000000..e2cb528
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#ifndef CONF_H
+#define CONF_H
+
+#include "common.h"
+#include "ui_main-conf.h"
+#include "ui_res-conf.h"
+#include "tray_conf.h"
+
+class Conf: public QDialog
+{
+   Q_OBJECT
+
+private:
+   CONFIG *config;
+   RES_HEAD **rhead;
+public:
+   int items;
+   QLineEdit::EchoMode passtype;
+   Ui::Conf UIConf;
+   Conf();
+   ~Conf();
+   bool parse_config();
+   void addResource(RESMON *res, const char *title);
+   void addRes(int type, const char *title); /* create the resource */
+public slots:
+   void accept();
+   void selectCommandDir();
+   void addDir();
+   void addStore();
+   void addClient();
+   void togglePassword();
+};
+
+class ConfTab: public QWidget
+{
+   Q_OBJECT
+
+public:
+   Ui::ResConf ui;
+   RESMON *res;
+   int  type;
+   bool new_resource;
+   ConfTab(RESMON *r): QWidget() {
+      res = r;
+      type = r->type;
+      new_resource = r->new_resource;
+      ui.setupUi(this);
+      connect(ui.bpDelete, SIGNAL(clicked()), this, SLOT(disable()));
+   };
+   ~ConfTab() {
+      if (new_resource && res) {
+         free_resource((RES*) res, res->type);
+         res = NULL;
+      }
+   };
+public slots:
+   void disable() {
+      setEnabled(false);
+   };
+   void selectCaCertificateFile();
+   void selectCaCertificateDir();
+   void selectCertificate();
+   void selectKey();
+};
+
+#endif
index 6b9d27823bd0f3c921d1fde536da130c06f04e3c..3698afb4428cecabde2275e7fa4ce308c05fba57 100755 (executable)
@@ -1,4 +1,7 @@
 #!/bin/sh
+# Copyright (C) 2000-2017 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
 
 sbindir=@sbindir@
 sysconfdir=@sysconfdir@
diff --git a/bacula/src/qt-console/tray-monitor/main-conf.ui b/bacula/src/qt-console/tray-monitor/main-conf.ui
new file mode 100644 (file)
index 0000000..4e023ab
--- /dev/null
@@ -0,0 +1,350 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Conf</class>
+ <widget class="QDialog" name="Conf">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>556</width>
+    <height>337</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Configuration</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="tabMonitor">
+      <attribute name="title">
+       <string>Monitor Configuration</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <item>
+        <layout class="QFormLayout" name="formLayout">
+         <property name="fieldGrowthPolicy">
+          <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+         </property>
+         <item row="0" column="0">
+          <widget class="QLabel" name="label">
+           <property name="toolTip">
+            <string>The Monitor name will be used during the authentication phase.</string>
+           </property>
+           <property name="text">
+            <string>Name:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QLineEdit" name="editName">
+           <property name="toolTip">
+            <string>The Monitor name will be used during the authentication phase.</string>
+           </property>
+           <property name="maxLength">
+            <number>127</number>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>Refresh Interval:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="QSpinBox" name="spinRefresh">
+           <property name="minimum">
+            <number>5</number>
+           </property>
+           <property name="maximum">
+            <number>9999</number>
+           </property>
+           <property name="value">
+            <number>120</number>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="label_3">
+           <property name="toolTip">
+            <string>Specify the &quot;Command Directory&quot; where the tray-monitor program will check regularly for jobs to run</string>
+           </property>
+           <property name="text">
+            <string>Command Directory:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <item>
+            <widget class="QLineEdit" name="editCommandDir">
+             <property name="toolTip">
+              <string>Specify the &quot;Command Directory&quot; where the tray-monitor program will check regularly for jobs to run</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QToolButton" name="bpCommandDir">
+             <property name="text">
+              <string>...</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="label_4">
+           <property name="toolTip">
+            <string>Display or Hide advanced options in the &quot;Run Job&quot; window</string>
+           </property>
+           <property name="text">
+            <string>Display Advanced Options:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <widget class="QCheckBox" name="cbDspAdvanced">
+           <property name="toolTip">
+            <string>Display or Hide advanced options in the &quot;Run Job&quot; window</string>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout_2">
+     <item>
+      <widget class="QPushButton" name="bpSave">
+       <property name="toolTip">
+        <string>Save and Apply the changes</string>
+       </property>
+       <property name="text">
+        <string>Save</string>
+       </property>
+       <property name="icon">
+        <iconset resource="../main.qrc">
+         <normaloff>:/images/label.png</normaloff>:/images/label.png</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="bpCancel">
+       <property name="text">
+        <string>Cancel</string>
+       </property>
+       <property name="icon">
+        <iconset resource="../main.qrc">
+         <normaloff>:/images/A.png</normaloff>:/images/A.png</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="verticalSpacer">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>40</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="bpStrip">
+       <property name="toolTip">
+        <string>Show/Hide Passwords</string>
+       </property>
+       <property name="text">
+        <string>Password</string>
+       </property>
+       <property name="icon">
+        <iconset resource="../main.qrc">
+         <normaloff>:/images/zoom.png</normaloff>:/images/zoom.png</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="bpAddClient">
+       <property name="toolTip">
+        <string>Add Client resource to monitor</string>
+       </property>
+       <property name="text">
+        <string>Client   </string>
+       </property>
+       <property name="icon">
+        <iconset resource="../main.qrc">
+         <normaloff>:/images/mark.png</normaloff>:/images/mark.png</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="bpAddStorage">
+       <property name="toolTip">
+        <string>Add Storage resource to monitor</string>
+       </property>
+       <property name="text">
+        <string>Storage</string>
+       </property>
+       <property name="icon">
+        <iconset resource="../main.qrc">
+         <normaloff>:/images/mark.png</normaloff>:/images/mark.png</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="bpAddDir">
+       <property name="toolTip">
+        <string>Add Director resource to monitor</string>
+       </property>
+       <property name="text">
+        <string> Director</string>
+       </property>
+       <property name="icon">
+        <iconset resource="../main.qrc">
+         <normaloff>:/images/mark.png</normaloff>:/images/mark.png</iconset>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../main.qrc"/>
+ </resources>
+ <connections>
+  <connection>
+   <sender>bpSave</sender>
+   <signal>clicked()</signal>
+   <receiver>Conf</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>511</x>
+     <y>30</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>521</x>
+     <y>46</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpCancel</sender>
+   <signal>clicked()</signal>
+   <receiver>Conf</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>511</x>
+     <y>76</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>521</x>
+     <y>159</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpStrip</sender>
+   <signal>clicked()</signal>
+   <receiver>Conf</receiver>
+   <slot>togglePassword()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>511</x>
+     <y>178</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>496</x>
+     <y>142</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpAddClient</sender>
+   <signal>clicked()</signal>
+   <receiver>Conf</receiver>
+   <slot>addClient()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>511</x>
+     <y>239</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>521</x>
+     <y>245</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpAddStorage</sender>
+   <signal>clicked()</signal>
+   <receiver>Conf</receiver>
+   <slot>addStore()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>511</x>
+     <y>272</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>521</x>
+     <y>289</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpAddDir</sender>
+   <signal>clicked()</signal>
+   <receiver>Conf</receiver>
+   <slot>addDir()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>511</x>
+     <y>313</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>521</x>
+     <y>331</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpCommandDir</sender>
+   <signal>clicked()</signal>
+   <receiver>Conf</receiver>
+   <slot>selectCommandDir()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>405</x>
+     <y>135</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>466</x>
+     <y>112</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>togglePassword()</slot>
+  <slot>addClient()</slot>
+  <slot>addStore()</slot>
+  <slot>addDir()</slot>
+  <slot>selectCommandDir()</slot>
+ </slots>
+</ui>
diff --git a/bacula/src/qt-console/tray-monitor/res-conf.ui b/bacula/src/qt-console/tray-monitor/res-conf.ui
new file mode 100644 (file)
index 0000000..7cf21a7
--- /dev/null
@@ -0,0 +1,565 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ResConf</class>
+ <widget class="QWidget" name="ResConf">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>417</width>
+    <height>541</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QGroupBox" name="groupBox_2">
+     <property name="title">
+      <string>General</string>
+     </property>
+     <layout class="QFormLayout" name="formLayout">
+      <property name="fieldGrowthPolicy">
+       <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+      </property>
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="toolTip">
+         <string>The Name will be used only in the Tray Monitor interface</string>
+        </property>
+        <property name="text">
+         <string>Name:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QLineEdit" name="editName">
+        <property name="toolTip">
+         <string>The Name will be used only in the Tray Monitor interface</string>
+        </property>
+        <property name="maxLength">
+         <number>127</number>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_9">
+        <property name="text">
+         <string>Description:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QLineEdit" name="editDescription">
+        <property name="maxLength">
+         <number>512</number>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="labelPassword">
+        <property name="text">
+         <string>Password:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QLineEdit" name="editPassword">
+        <property name="text">
+         <string/>
+        </property>
+        <property name="maxLength">
+         <number>127</number>
+        </property>
+        <property name="echoMode">
+         <enum>QLineEdit::PasswordEchoOnEdit</enum>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="0">
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Address:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="1">
+       <widget class="QLineEdit" name="editAddress">
+        <property name="maxLength">
+         <number>1024</number>
+        </property>
+       </widget>
+      </item>
+      <item row="5" column="0">
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>Port:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="5" column="1">
+       <widget class="QLineEdit" name="editPort">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="maximumSize">
+         <size>
+          <width>100</width>
+          <height>16777215</height>
+         </size>
+        </property>
+        <property name="maxLength">
+         <number>5</number>
+        </property>
+       </widget>
+      </item>
+      <item row="6" column="0">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Timeout:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="6" column="1">
+       <widget class="QLineEdit" name="editTimeout">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="maximumSize">
+         <size>
+          <width>100</width>
+          <height>16777215</height>
+         </size>
+        </property>
+        <property name="maxLength">
+         <number>5</number>
+        </property>
+       </widget>
+      </item>
+      <item row="8" column="0">
+       <widget class="QLabel" name="labelRemote">
+        <property name="toolTip">
+         <string>Use Client Initiated backup/restore feature</string>
+        </property>
+        <property name="text">
+         <string>Remote</string>
+        </property>
+       </widget>
+      </item>
+      <item row="8" column="1">
+       <widget class="QCheckBox" name="cbRemote">
+        <property name="toolTip">
+         <string>Use Client Initiated backup/restore feature</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="7" column="0">
+       <widget class="QLabel" name="label_10">
+        <property name="toolTip">
+         <string>Update the tray monitor icon with the status of this component</string>
+        </property>
+        <property name="text">
+         <string>Monitor:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="7" column="1">
+       <widget class="QCheckBox" name="cbMonitor">
+        <property name="toolTip">
+         <string>Update the tray monitor icon with the status of this component</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="9" column="1">
+       <widget class="QCheckBox" name="cbUseSetIp">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="9" column="0">
+       <widget class="QLabel" name="labelSetIp">
+        <property name="text">
+         <string>Use SetIp:</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="2" column="0">
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>TLS</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="3" column="2">
+       <widget class="QToolButton" name="bpCertificate">
+        <property name="text">
+         <string>...</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_5">
+        <property name="text">
+         <string>CA Certificate File:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0">
+       <widget class="QCheckBox" name="cbTLSEnabled">
+        <property name="text">
+         <string>Enabled</string>
+        </property>
+        <property name="checked">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="2">
+       <widget class="QToolButton" name="bpCaCertificateFile">
+        <property name="text">
+         <string>...</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="0">
+       <widget class="QLabel" name="label_8">
+        <property name="text">
+         <string>Key File:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QLineEdit" name="editCaCertificateDir"/>
+      </item>
+      <item row="2" column="2">
+       <widget class="QToolButton" name="bpCaCertificateDir">
+        <property name="text">
+         <string>...</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="0">
+       <widget class="QLabel" name="label_7">
+        <property name="text">
+         <string>Certificate File:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="1">
+       <widget class="QLineEdit" name="editKey"/>
+      </item>
+      <item row="3" column="1">
+       <widget class="QLineEdit" name="editCertificate"/>
+      </item>
+      <item row="1" column="1">
+       <widget class="QLineEdit" name="editCaCertificateFile"/>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="label_6">
+        <property name="text">
+         <string>CA Certificate Directory:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="2">
+       <widget class="QToolButton" name="bpKey">
+        <property name="text">
+         <string>...</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+     <zorder>cbTLSEnabled</zorder>
+     <zorder>editCaCertificateFile</zorder>
+     <zorder>label_5</zorder>
+     <zorder>label_6</zorder>
+     <zorder>editCaCertificateDir</zorder>
+     <zorder>label_7</zorder>
+     <zorder>editCertificate</zorder>
+     <zorder>label_8</zorder>
+     <zorder>editKey</zorder>
+     <zorder>bpCaCertificateFile</zorder>
+     <zorder>bpCaCertificateDir</zorder>
+     <zorder>bpCertificate</zorder>
+     <zorder>bpKey</zorder>
+    </widget>
+   </item>
+   <item row="3" column="2">
+    <widget class="QPushButton" name="bpDelete">
+     <property name="maximumSize">
+      <size>
+       <width>64</width>
+       <height>16777215</height>
+      </size>
+     </property>
+     <property name="baseSize">
+      <size>
+       <width>64</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="icon">
+      <iconset resource="../main.qrc">
+       <normaloff>:/images/purge.png</normaloff>:/images/purge.png</iconset>
+     </property>
+     <property name="flat">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="3" column="0">
+    <spacer name="horizontalSpacer">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>40</width>
+       <height>20</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>editName</tabstop>
+  <tabstop>editDescription</tabstop>
+  <tabstop>editPassword</tabstop>
+  <tabstop>editAddress</tabstop>
+  <tabstop>editPort</tabstop>
+  <tabstop>editTimeout</tabstop>
+  <tabstop>cbRemote</tabstop>
+  <tabstop>cbTLSEnabled</tabstop>
+  <tabstop>editCaCertificateFile</tabstop>
+  <tabstop>bpCaCertificateFile</tabstop>
+  <tabstop>editCaCertificateDir</tabstop>
+  <tabstop>bpCaCertificateDir</tabstop>
+  <tabstop>editCertificate</tabstop>
+  <tabstop>bpCertificate</tabstop>
+  <tabstop>editKey</tabstop>
+  <tabstop>bpKey</tabstop>
+ </tabstops>
+ <resources>
+  <include location="../main.qrc"/>
+ </resources>
+ <connections>
+  <connection>
+   <sender>bpCaCertificateFile</sender>
+   <signal>clicked()</signal>
+   <receiver>ResConf</receiver>
+   <slot>selectCaCertificateFile()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>461</x>
+     <y>294</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>521</x>
+     <y>247</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpCaCertificateDir</sender>
+   <signal>clicked()</signal>
+   <receiver>ResConf</receiver>
+   <slot>selectCaCertificateDir()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>452</x>
+     <y>334</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>501</x>
+     <y>355</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpCertificate</sender>
+   <signal>clicked()</signal>
+   <receiver>ResConf</receiver>
+   <slot>selectCertificate()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>459</x>
+     <y>364</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>495</x>
+     <y>384</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>bpKey</sender>
+   <signal>clicked()</signal>
+   <receiver>ResConf</receiver>
+   <slot>selectKey()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>461</x>
+     <y>395</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>481</x>
+     <y>410</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>editCaCertificateFile</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>132</x>
+     <y>271</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>249</x>
+     <y>291</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>editCaCertificateDir</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>120</x>
+     <y>274</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>203</x>
+     <y>325</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>editCertificate</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>68</x>
+     <y>271</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>220</x>
+     <y>360</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>editKey</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>51</x>
+     <y>275</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>288</x>
+     <y>392</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>bpCaCertificateFile</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>161</x>
+     <y>267</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>449</x>
+     <y>291</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>bpCaCertificateDir</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>145</x>
+     <y>271</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>455</x>
+     <y>329</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>bpCertificate</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>140</x>
+     <y>266</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>459</x>
+     <y>358</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>cbTLSEnabled</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>bpKey</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>118</x>
+     <y>272</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>458</x>
+     <y>389</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+ <slots>
+  <slot>selectCaCertificateFile()</slot>
+  <slot>selectCaCertificateDir()</slot>
+  <slot>selectCertificate()</slot>
+  <slot>selectKey()</slot>
+ </slots>
+</ui>
diff --git a/bacula/src/qt-console/tray-monitor/run.ui b/bacula/src/qt-console/tray-monitor/run.ui
new file mode 100644 (file)
index 0000000..4ed29af
--- /dev/null
@@ -0,0 +1,379 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>runForm</class>
+ <widget class="QWidget" name="runForm">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>568</width>
+    <height>407</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Run job</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_3">
+   <item>
+    <widget class="QLabel" name="run">
+     <property name="maximumSize">
+      <size>
+       <width>16777215</width>
+       <height>30</height>
+      </size>
+     </property>
+     <property name="font">
+      <font>
+       <pointsize>11</pointsize>
+      </font>
+     </property>
+     <property name="text">
+      <string>&lt;h3&gt;Run a Job&lt;/h3&gt;</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>5</height>
+      </size>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label_12">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="pixmap">
+        <pixmap resource="../main.qrc">:/images/runit.png</pixmap>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTabWidget" name="tabWidget">
+       <property name="currentIndex">
+        <number>0</number>
+       </property>
+       <widget class="QWidget" name="tab1">
+        <attribute name="title">
+         <string>Properties</string>
+        </attribute>
+        <layout class="QGridLayout" name="gridLayout">
+         <item row="0" column="0">
+          <widget class="QGroupBox" name="groupBox">
+           <property name="title">
+            <string/>
+           </property>
+           <layout class="QFormLayout" name="formLayout">
+            <property name="fieldGrowthPolicy">
+             <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+            </property>
+            <item row="0" column="0">
+             <widget class="QLabel" name="label_6">
+              <property name="text">
+               <string>Job:</string>
+              </property>
+              <property name="buddy">
+               <cstring>jobCombo</cstring>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QComboBox" name="jobCombo">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="sizeAdjustPolicy">
+               <enum>QComboBox::AdjustToContents</enum>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QLabel" name="label">
+              <property name="text">
+               <string>When:</string>
+              </property>
+              <property name="buddy">
+               <cstring>dateTimeEdit</cstring>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="1">
+             <widget class="QDateTimeEdit" name="dateTimeEdit">
+              <property name="currentSection">
+               <enum>QDateTimeEdit::YearSection</enum>
+              </property>
+              <property name="displayFormat">
+               <string>yyyy-MM-dd hh:mm:ss</string>
+              </property>
+              <property name="calendarPopup">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QGroupBox" name="boxEstimate">
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Job statistics computed from the Catalog with previous jobs.&lt;/p&gt;&lt;p&gt;For accurate information, it is possible to use the bconsole &amp;quot;estimate&amp;quot; command.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+           <property name="title">
+            <string>Estimate:</string>
+           </property>
+           <layout class="QFormLayout" name="formLayout_3">
+            <item row="0" column="0">
+             <widget class="QLabel" name="labelJobBytes_2">
+              <property name="text">
+               <string>Job Bytes:</string>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QLabel" name="labelJobBytes">
+              <property name="text">
+               <string/>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0">
+             <widget class="QLabel" name="labelJobFiles_2">
+              <property name="text">
+               <string>Job Files:</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="1">
+             <widget class="QLabel" name="labelJobFiles">
+              <property name="text">
+               <string/>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="0">
+             <widget class="QLabel" name="label_13">
+              <property name="text">
+               <string>Level:</string>
+              </property>
+             </widget>
+            </item>
+            <item row="2" column="1">
+             <widget class="QLabel" name="labelJobLevel">
+              <property name="text">
+               <string/>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="tab2">
+        <attribute name="title">
+         <string>Advanced</string>
+        </attribute>
+        <layout class="QFormLayout" name="formLayout_2">
+         <item row="0" column="0">
+          <widget class="QLabel" name="label_11">
+           <property name="text">
+            <string>Level:</string>
+           </property>
+           <property name="buddy">
+            <cstring>levelCombo</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <widget class="QComboBox" name="levelCombo"/>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="label_8">
+           <property name="text">
+            <string>Client:</string>
+           </property>
+           <property name="buddy">
+            <cstring>clientCombo</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="1">
+          <widget class="QComboBox" name="clientCombo"/>
+         </item>
+         <item row="2" column="0">
+          <widget class="QLabel" name="label_9">
+           <property name="text">
+            <string>FileSet:</string>
+           </property>
+           <property name="buddy">
+            <cstring>filesetCombo</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="1">
+          <widget class="QComboBox" name="filesetCombo"/>
+         </item>
+         <item row="3" column="0">
+          <widget class="QLabel" name="label_3">
+           <property name="text">
+            <string>Pool:</string>
+           </property>
+           <property name="buddy">
+            <cstring>poolCombo</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="3" column="1">
+          <widget class="QComboBox" name="poolCombo"/>
+         </item>
+         <item row="4" column="0">
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>Storage:</string>
+           </property>
+           <property name="buddy">
+            <cstring>storageCombo</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="4" column="1">
+          <widget class="QComboBox" name="storageCombo"/>
+         </item>
+         <item row="5" column="0">
+          <widget class="QLabel" name="label_7">
+           <property name="text">
+            <string>Catalog:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="5" column="1">
+          <widget class="QComboBox" name="catalogCombo"/>
+         </item>
+         <item row="6" column="0">
+          <widget class="QLabel" name="label_4">
+           <property name="text">
+            <string>Priority:</string>
+           </property>
+           <property name="buddy">
+            <cstring>prioritySpin</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="6" column="1">
+          <widget class="QSpinBox" name="prioritySpin">
+           <property name="minimumSize">
+            <size>
+             <width>60</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>60</width>
+             <height>16777215</height>
+            </size>
+           </property>
+           <property name="baseSize">
+            <size>
+             <width>60</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="minimum">
+            <number>1</number>
+           </property>
+           <property name="maximum">
+            <number>10000</number>
+           </property>
+           <property name="value">
+            <number>10</number>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="Line" name="line_2">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>5</height>
+      </size>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout">
+     <item>
+      <spacer>
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="okButton">
+       <property name="text">
+        <string>OK</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cancelButton">
+       <property name="text">
+        <string>Cancel</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../main.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/bacula/src/qt-console/tray-monitor/runjob.cpp b/bacula/src/qt-console/tray-monitor/runjob.cpp
new file mode 100644 (file)
index 0000000..e6204ee
--- /dev/null
@@ -0,0 +1,515 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#include "runjob.h"
+#include <QMessageBox>
+
+static void fillcombo(QComboBox *cb, alist *lst, bool addempty=true)
+{
+   if (lst && lst->size() > 0) {
+      QStringList list;
+      char *str;
+      if (addempty) {
+         list << QString("");
+      }
+      foreach_alist(str, lst) {
+         list << QString(str);
+      }
+      cb->addItems(list);
+   } else {
+      cb->setEnabled(false);
+   }
+}
+
+RunJob::RunJob(RESMON *r): QDialog(), res(r), tabAdvanced(NULL)
+{
+   int nbjob;
+   if (res->jobs->size() == 0) {
+      QMessageBox msgBox;
+      msgBox.setText(_("This restricted console does not have access to Backup jobs"));
+      msgBox.setIcon(QMessageBox::Warning);
+      msgBox.exec();
+      deleteLater();
+      return;
+
+   }
+
+   ui.setupUi(this);
+   setModal(true);
+   connect(ui.cancelButton, SIGNAL(clicked()), this, SLOT(close_cb()));
+   connect(ui.okButton, SIGNAL(clicked()), this, SLOT(runjob()));
+   connect(ui.jobCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(jobChanged(int)));
+   connect(ui.levelCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(levelChanged(int)));
+   ui.dateTimeEdit->setMinimumDate(QDate::currentDate());
+   ui.dateTimeEdit->setMaximumDate(QDate::currentDate().addDays(7));
+   ui.dateTimeEdit->setDate(QDate::currentDate());
+   ui.dateTimeEdit->setTime(QTime::currentTime());
+   ui.boxEstimate->setVisible(false);
+
+   res->mutex->lock();
+   nbjob = res->jobs->size();
+   fillcombo(ui.jobCombo,    res->jobs, (nbjob > 1));
+   fillcombo(ui.clientCombo, res->clients);
+   fillcombo(ui.filesetCombo,res->filesets);
+   fillcombo(ui.poolCombo,   res->pools);
+   fillcombo(ui.storageCombo,res->storages);
+   fillcombo(ui.catalogCombo,res->catalogs);
+   res->mutex->unlock();
+   connect(ui.tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChange(int)));
+   QStringList levels;
+   levels << "" << "Incremental" << "Differential"  << "Full";
+   ui.levelCombo->addItems(levels);
+
+   MONITOR *m = (MONITOR*) GetNextRes(R_MONITOR, NULL);
+   if (!m->display_advanced_options) {
+      tabAdvanced = ui.tabWidget->widget(1);
+      ui.tabWidget->removeTab(1);
+   }
+
+   show();
+};
+
+void RunJob::tabChange(int idx)
+{
+   QString q = ui.tabWidget->tabText(idx);
+   if (q.contains("Advanced")) {
+      if (ui.jobCombo->currentText().compare("") == 0) {
+         pm_strcpy(curjob, "");
+         ui.tab2->setEnabled(false);
+
+      } else if (ui.jobCombo->currentText().compare(curjob.c_str()) != 0) {
+         task *t = new task();
+         char *job = bstrdup(ui.jobCombo->currentText().toUtf8().data());
+         pm_strcpy(curjob, job); // Keep the job name to not refresh the Advanced tab the next time
+
+         Dmsg1(10, "get defaults for %s\n", job);
+         res->mutex->lock();
+         bfree_and_null(res->defaults.job);
+         res->defaults.job = job;
+         res->mutex->unlock();
+
+         ui.tab2->setEnabled(false);
+         connect(t, SIGNAL(done(task *)), this, SLOT(fill_defaults(task *)), Qt::QueuedConnection);
+         t->init(res, TASK_DEFAULTS);
+         res->wrk->queue(t);
+      }
+   }
+}
+
+void RunJob::runjob()
+{
+   POOL_MEM tmp;
+   char *p;
+
+   p = ui.jobCombo->currentText().toUtf8().data();
+   if (!p || !*p) {
+      QMessageBox msgBox;
+      msgBox.setText(_("Nothing selected"));
+      msgBox.setIcon(QMessageBox::Warning);
+      msgBox.exec();
+      return;
+   }
+
+   Mmsg(command, "run job=\"%s\" yes", p);
+
+   if (strcmp(p, NPRTB(res->defaults.job)) == 0 || strcmp("", NPRTB(res->defaults.job)) == 0) {
+      p = ui.storageCombo->currentText().toUtf8().data();
+      if (p && *p && strcmp(p, NPRTB(res->defaults.storage)) != 0) {
+         Mmsg(tmp, " storage=\"%s\"", p);
+         pm_strcat(command, tmp.c_str());
+      }
+
+      p = ui.clientCombo->currentText().toUtf8().data();
+      if (p && *p && strcmp(p, NPRTB(res->defaults.client)) != 0) {
+         Mmsg(tmp, " client=\"%s\"", p);
+         pm_strcat(command, tmp.c_str());
+      }
+
+      p = ui.levelCombo->currentText().toUtf8().data();
+      if (p && *p && strcmp(p, NPRTB(res->defaults.level)) != 0) {
+         Mmsg(tmp, " level=\"%s\"", p);
+         pm_strcat(command, tmp.c_str());
+      }
+
+      p = ui.poolCombo->currentText().toUtf8().data();
+      if (p && *p && strcmp(p, NPRTB(res->defaults.pool)) != 0) {
+         Mmsg(tmp, " pool=\"%s\"", p);
+         pm_strcat(command, tmp.c_str());
+      }
+
+      p = ui.filesetCombo->currentText().toUtf8().data();
+      if (p && *p && strcmp(p, NPRTB(res->defaults.fileset)) != 0) {
+         Mmsg(tmp, " fileset=\"%s\"", p);
+         pm_strcat(command, tmp.c_str());
+      }
+
+      if (res->defaults.priority && res->defaults.priority != ui.prioritySpin->value()) {
+         Mmsg(tmp, " priority=\"%d\"", res->defaults.priority);
+         pm_strcat(command, tmp.c_str());
+      }
+   }
+
+   QDate dnow = QDate::currentDate();
+   QTime tnow = QTime::currentTime();
+   QDate dval = ui.dateTimeEdit->date();
+   QTime tval = ui.dateTimeEdit->time();
+
+   if (dval > dnow || (dval == dnow && tval > tnow)) {
+      Mmsg(tmp, " when=\"%s %s\"", dval.toString("yyyy-MM-dd").toUtf8().data(), tval.toString("hh:mm:00").toUtf8().data());
+      pm_strcat(command, tmp.c_str());
+   }
+
+   if (res->type == R_CLIENT) {
+      pm_strcat(command, " fdcalled=1");
+   }
+   
+   // Build the command and run it!
+   task *t = new task();
+   connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection);
+   t->arg = command.c_str();
+   t->init(res, TASK_RUN);
+   res->wrk->queue(t);
+}
+
+void RunJob::jobStarted(task *t)
+{
+   Dmsg1(10, "%s\n", command.c_str());
+   Dmsg1(10, "-> jobid=%d\n", t->result.i);
+   deleteLater();
+   delete t;
+}
+
+void RunJob::close_cb(task *t)
+{
+   deleteLater();
+   delete t;
+}
+
+void RunJob::close_cb()
+{
+   task *t = new task();
+   connect(t, SIGNAL(done(task *)), this, SLOT(close_cb(task *)), Qt::QueuedConnection);
+   t->init(res, TASK_DISCONNECT);
+   res->wrk->queue(t);
+}
+
+void RunJob::jobChanged(int)
+{
+   char *p;
+   ui.levelCombo->setCurrentIndex(0);
+   ui.storageCombo->setCurrentIndex(0);
+   ui.filesetCombo->setCurrentIndex(0);
+   ui.clientCombo->setCurrentIndex(0);
+   ui.storageCombo->setCurrentIndex(0);
+   ui.poolCombo->setCurrentIndex(0);
+   ui.catalogCombo->setCurrentIndex(0);
+
+   p = ui.jobCombo->currentText().toUtf8().data();
+   if (p && *p) {
+      task *t = new task();
+      pm_strcpy(info, p);
+      connect(t, SIGNAL(done(task *)), this, SLOT(jobInfo(task *)), Qt::QueuedConnection);
+      t->arg = info.c_str();    // Jobname
+      t->arg2 = NULL;           // Level
+      t->init(res, TASK_INFO);
+      res->wrk->queue(t);
+   }
+}
+
+void RunJob::levelChanged(int)
+{
+   char *p;
+   p = ui.jobCombo->currentText().toUtf8().data();
+   if (p && *p) {
+      pm_strcpy(info, p);      
+      p = ui.levelCombo->currentText().toUtf8().data();
+      if (p && *p) {
+         task *t = new task();
+         pm_strcpy(level, p);
+         connect(t, SIGNAL(done(task *)), this, SLOT(jobInfo(task *)), Qt::QueuedConnection);
+         t->arg = info.c_str();    // Jobname
+         t->arg2 = level.c_str();  // Level
+         t->init(res, TASK_INFO);
+         res->wrk->queue(t);
+      }
+   }
+}
+
+void RunJob::jobInfo(task *t)
+{
+   char ed1[50];
+   res->mutex->lock();
+   if (res->infos.CorrNbJob == 0) {
+      ui.boxEstimate->setVisible(false);
+   } else {
+      QString t;
+      edit_uint64_with_suffix(res->infos.JobBytes, ed1);
+      strncat(ed1, "B", sizeof(ed1));
+      ui.labelJobBytes->setText(QString(ed1));
+      ui.labelJobFiles->setText(QString(edit_uint64_with_commas(res->infos.JobFiles, ed1)));
+      ui.labelJobLevel->setText(QString(job_level_to_str(res->infos.JobLevel)));
+      t = tr("Computed over %1 job%2, the correlation is %3/100.").arg(res->infos.CorrNbJob).arg(res->infos.CorrNbJob>1?"s":"").arg(res->infos.CorrJobBytes);
+      ui.labelJobBytes_2->setToolTip(t);
+      t = tr("Computed over %1 job%2, The correlation is %3/100.").arg(res->infos.CorrNbJob).arg(res->infos.CorrNbJob>1?"s":"").arg(res->infos.CorrJobFiles);
+      ui.labelJobFiles_2->setToolTip(t);
+      ui.boxEstimate->setVisible(true);
+   }
+   res->mutex->unlock();
+   t->deleteLater();
+}
+
+static void set_combo(QComboBox *dest, char *str)
+{
+   if (str) {
+      int idx = dest->findText(QString(str), Qt::MatchExactly);
+      if (idx >= 0) {
+         dest->setCurrentIndex(idx);
+      }
+   }
+}
+
+void RunJob::fill_defaults(task *t)
+{
+   if (t->status == true) {
+      res->mutex->lock();
+      set_combo(ui.levelCombo, res->defaults.level);
+      set_combo(ui.filesetCombo, res->defaults.fileset);
+      set_combo(ui.clientCombo, res->defaults.client);
+      set_combo(ui.storageCombo, res->defaults.storage);
+      set_combo(ui.poolCombo, res->defaults.pool);
+      set_combo(ui.catalogCombo, res->defaults.catalog);
+      res->mutex->unlock();
+   }
+
+   ui.tab2->setEnabled(true);
+   t->deleteLater();
+}
+
+RunJob::~RunJob()
+{
+   Dmsg0(10, "~RunJob()\n");
+   if (tabAdvanced) {
+      delete tabAdvanced;
+   }
+}
+
+void TSched::init(const char *cmd_dir)
+{
+   bool started = (timer >= 0);
+   if (started) {
+      stop();
+   }
+
+   bfree_and_null(command_dir);
+   command_dir = bstrdup(cmd_dir);
+
+   if (started) {
+      start();
+   }
+}
+
+TSched::TSched() {
+   timer = -1;
+   command_dir = NULL;
+}
+
+TSched::~TSched() {
+   if (timer >= 0) {
+      stop();
+   }
+   bfree_and_null(command_dir);
+}
+
+#ifndef HAVE_READDIR_R
+int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
+#else
+#include <dirent.h>
+#endif
+
+bool TSched::read_command_file(const char *file, alist *lst, btime_t mtime)
+{
+   POOLMEM *line;
+   bool ret=false;
+   char *p;
+   TSchedJob *s;
+   Dmsg1(50, "open command file %s\n", file);
+   FILE *fp = fopen(file, "r");
+   if (!fp) {
+      return false;
+   }
+   line = get_pool_memory(PM_FNAME);
+
+   /* Get the first line, client/component:command */
+   while (bfgets(line, fp) != NULL) {
+      strip_trailing_junk(line);
+      Dmsg1(50, "%s\n", line);
+      if (line[0] == '#') {
+         continue;
+      }
+
+      if ((p = strchr(line, ':')) != NULL) {
+         *p=0;
+         s = new TSchedJob(line, p+1, mtime);
+         lst->append(s);
+         ret = true;
+      }
+   }
+
+   free_pool_memory(line);
+   fclose(fp);
+   return ret;
+}
+
+#include "lib/plugins.h"
+#include "lib/cmd_parser.h"
+
+void TSched::timerEvent(QTimerEvent *event)
+{
+   POOL_MEM tmp, command;
+   TSchedJob *j;
+   alist lst(10, not_owned_by_alist);
+   arg_parser parser;
+   int i;
+   task *t;
+   RESMON *res;
+   scan_for_commands(&lst);
+
+   foreach_alist(j, (&lst)) {
+      if (parser.parse_cmd(j->command) == bRC_OK) {
+         if ((i = parser.find_arg_with_value("job")) > 0) {
+            QMessageBox msgbox;
+            foreach_res(res, R_CLIENT) {
+               if (strcmp(res->hdr.name, j->component) == 0) {
+                  break;
+               }
+            }
+            if (!res) {
+               foreach_res(res, R_DIRECTOR) {
+                  if (strcmp(res->hdr.name, j->component) == 0) {
+                     break;
+                  }
+               }
+            }
+            if (!res) {
+               msgbox.setIcon(QMessageBox::Information);
+               msgbox.setText(QString("Unable to find the component \"%1\" to run the job \"%2\".").arg(j->component, j->command));
+               msgbox.setStandardButtons(QMessageBox::Ignore);
+            } else {
+
+               msgbox.setIcon(QMessageBox::Information);
+               msgbox.setText(QString("The job \"%1\" will start automatically in few seconds...").arg(parser.argv[i]));
+               msgbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Ignore);
+               msgbox.setDefaultButton(QMessageBox::Ok);
+               msgbox.button(QMessageBox::Ok)->animateClick(6000);
+            }
+            switch(msgbox.exec()) {
+               case QMessageBox::Ok:
+                  Mmsg(command, "%s yes", j->command);
+
+                  if (res->type == R_CLIENT) {
+                     pm_strcat(command, " fdcalled=1");
+                  }
+   
+                  // Build the command and run it!
+                  t = new task();
+                  connect(t, SIGNAL(done(task *)), this, SLOT(jobStarted(task *)), Qt::QueuedConnection);
+                  t->arg = command.c_str();
+                  t->init(res, TASK_RUN);
+                  res->wrk->queue(t);
+
+                  break;
+               case QMessageBox::Cancel:
+               case QMessageBox::Ignore:
+                  break;
+            }
+         }
+      }
+      delete j;
+   }
+}
+
+void TSched::jobStarted(task *t)
+{
+   Dmsg1(10, "-> jobid=%d\n", t->result.i);
+   t->deleteLater();
+}
+
+
+bool TSched::scan_for_commands(alist *commands)
+{
+   int name_max, len;
+   DIR* dp = NULL;
+   POOL_MEM fname(PM_FNAME), fname2(PM_FNAME);
+   bool ret=false, found=false;
+   struct dirent *entry = NULL, *result;
+   struct stat statp;
+
+   name_max = pathconf(".", _PC_NAME_MAX);
+   if (name_max < 1024) {
+      name_max = 1024;
+   }
+
+   if (!(dp = opendir(command_dir))) {
+      berrno be;
+      Dmsg2(0, "Failed to open directory %s: ERR=%s\n",
+            command_dir, be.bstrerror());
+      goto bail_out;
+   }
+
+   entry = (struct dirent *)malloc(sizeof(struct dirent) + name_max + 1000);
+   for ( ;; ) {
+      if ((readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
+         if (!found) {
+            goto bail_out;
+         }
+         break;
+      }
+      if (strcmp(result->d_name, ".") == 0 ||
+          strcmp(result->d_name, "..") == 0) {
+         continue;
+      }
+      len = strlen(result->d_name);
+      if (len <= 5) {
+         continue;
+      }
+      if (strcmp(result->d_name + len - 5, ".bcmd") != 0) {
+         continue;
+      }
+
+      Mmsg(fname, "%s/%s", command_dir, result->d_name);
+
+      if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) {
+         continue;                 /* ignore directories & special files */
+      }
+
+      if (read_command_file(fname.c_str(), commands, statp.st_mtime)) {
+         Mmsg(fname2, "%s.ok", fname.c_str());
+         unlink(fname2.c_str());
+         rename(fname.c_str(), fname2.c_str()); // TODO: We should probably unlink the file
+      }
+   }
+bail_out:
+   if (entry) {
+      free(entry);
+   }
+   if (dp) {
+      closedir(dp);
+   }
+   return ret;
+}
diff --git a/bacula/src/qt-console/tray-monitor/runjob.h b/bacula/src/qt-console/tray-monitor/runjob.h
new file mode 100644 (file)
index 0000000..6333865
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#ifndef RUN_H
+#define RUN_H
+
+#include "common.h"
+#include "ui_run.h"
+#include "tray_conf.h"
+#include "task.h"
+
+class RunJob: public QDialog
+{
+   Q_OBJECT
+
+public:
+   RESMON *res;
+   QWidget *tabAdvanced;
+   POOL_MEM command;
+   POOL_MEM info;
+   POOL_MEM level;
+   POOL_MEM curjob;
+   Ui::runForm ui;
+   RunJob(RESMON *r);
+   ~RunJob();
+
+public slots:
+   void jobChanged(int);
+   void levelChanged(int);
+   void jobStarted(task *);
+   void jobInfo(task *);
+   void fill_defaults(task *);
+   void tabChange(int idx);
+   void runjob();
+   /* close the window properly */
+   void close_cb(task *t);
+   void close_cb();
+};
+
+/* Object that can scan a directory to find jobs */
+class TSched: public QObject
+{
+   Q_OBJECT
+private:
+   char *command_dir;
+   bool read_command_file(const char *file, alist *lst, btime_t mtime);
+   int   timer;
+
+public:
+   TSched();
+   ~TSched();
+   void init(const char *cmd_dir);
+   bool scan_for_commands(alist *lst);
+   void start() {
+      timer = startTimer(60000);  // 1-minute timer
+   };
+   void stop() {
+      if (timer >= 0) {
+         killTimer(timer);
+         timer = -1;
+      }
+   };
+public slots:
+   void jobStarted(task *t);
+protected:
+   void timerEvent(QTimerEvent *event);
+
+};
+
+
+/* Job found in the command directory */
+class TSchedJob: public QObject
+{
+   Q_OBJECT
+
+public:
+   char *component;             // Name of the daemon
+   char *command;               // job command
+   btime_t create_date;         // When the command file was created
+   TSchedJob() : component(NULL), command(NULL) {};
+
+   TSchedJob(const char *comp, const char *cmd, btime_t cd) {
+         component = bstrdup(comp);
+         command = bstrdup(cmd);
+         create_date = cd;
+   };
+
+   ~TSchedJob() {
+      clear();
+   };
+   void clear() {
+      if (component) {
+         bfree_and_null(component);
+      }
+      if (command) {
+         bfree_and_null(command);
+      }
+      create_date = 0;
+   };
+};
+
+#endif
diff --git a/bacula/src/qt-console/tray-monitor/sd-monitor.ui b/bacula/src/qt-console/tray-monitor/sd-monitor.ui
new file mode 100644 (file)
index 0000000..3c00e6c
--- /dev/null
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>sdStatus</class>
+ <widget class="QWidget" name="sdStatus">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>518</width>
+    <height>435</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout">
+     <item>
+      <widget class="QGroupBox" name="groupBox">
+       <property name="title">
+        <string>Storage Daemon Status</string>
+       </property>
+       <layout class="QGridLayout" name="gridLayout">
+        <item row="1" column="1">
+         <widget class="QLabel" name="labelName">
+          <property name="text">
+           <string/>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QLabel" name="label">
+          <property name="text">
+           <string>Name:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="3">
+         <widget class="QLabel" name="labelPlugins">
+          <property name="text">
+           <string/>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="2">
+         <widget class="QLabel" name="label_3">
+          <property name="text">
+           <string>Started:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="3">
+         <widget class="QLabel" name="labelStarted">
+          <property name="text">
+           <string/>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="1">
+         <widget class="QLabel" name="labelVersion">
+          <property name="text">
+           <string/>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="0">
+         <widget class="QLabel" name="label_2">
+          <property name="text">
+           <string>Version:</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="2">
+         <widget class="QLabel" name="label_4">
+          <property name="text">
+           <string>Plugins:</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox_2">
+       <property name="title">
+        <string>Running Jobs</string>
+       </property>
+       <layout class="QHBoxLayout" name="horizontalLayout">
+        <item>
+         <widget class="QTableWidget" name="tableRunning">
+          <property name="selectionMode">
+           <enum>QAbstractItemView::SingleSelection</enum>
+          </property>
+          <attribute name="verticalHeaderVisible">
+           <bool>false</bool>
+          </attribute>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="groupBox_3">
+       <property name="title">
+        <string>Terminated Jobs</string>
+       </property>
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <item>
+         <widget class="QTableWidget" name="tableTerminated">
+          <property name="selectionMode">
+           <enum>QAbstractItemView::SingleSelection</enum>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QLabel" name="statusBar">
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="pushButton">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="icon">
+        <iconset resource="../main.qrc">
+         <normaloff>:/images/view-refresh.png</normaloff>:/images/view-refresh.png</iconset>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources>
+  <include location="../main.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/bacula/src/qt-console/tray-monitor/sdstatus.cpp b/bacula/src/qt-console/tray-monitor/sdstatus.cpp
new file mode 100644 (file)
index 0000000..741305a
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#include "sdstatus.h"
+#include "../util/fmtwidgetitem.h"
+#include "jcr.h"
+
+void SDStatus::doUpdate()
+{
+   if (count == 0) {
+      count++;
+      task *t = new task();
+      status.pushButton->setEnabled(false);
+      connect(t, SIGNAL(done(task *)), this, SLOT(taskDone(task *)), Qt::QueuedConnection);
+      t->init(res, TASK_STATUS);
+      res->wrk->queue(t);
+      status.statusBar->setText(QString("Trying to connect to Storage..."));
+      Dmsg1(50, "doUpdate(%p)\n", res);
+   }
+}
+
+void SDStatus::taskDone(task *t)
+{
+   count--;
+   if (!t->status) {
+      status.statusBar->setText(QString(t->errmsg));
+
+   } else {
+      status.statusBar->clear();
+      if (t->type == TASK_STATUS) {
+         char ed1[50];
+         struct s_last_job *ljob;
+         struct s_running_job *rjob;
+         res->mutex->lock();
+         status.labelName->setText(QString(res->name));
+         status.labelVersion->setText(QString(res->version));
+         status.labelStarted->setText(QString(res->started));
+         status.labelPlugins->setText(QString(res->plugins));
+         /* Clear the table first */
+         Freeze(*status.tableRunning);
+         Freeze(*status.tableTerminated);
+         QStringList headerlistR = (QStringList() << tr("JobId")
+                                    << tr("Job")  << tr("Level") << tr("Client")
+                                    << tr("Storage")
+                                    << tr("Files") << tr("Bytes") << tr("Errors"));
+         status.tableRunning->clear();
+         status.tableRunning->setRowCount(0);
+         status.tableRunning->setColumnCount(headerlistR.count());
+         status.tableRunning->setHorizontalHeaderLabels(headerlistR);
+         status.tableRunning->setEditTriggers(QAbstractItemView::NoEditTriggers);
+         status.tableRunning->verticalHeader()->hide();
+         status.tableRunning->setSortingEnabled(true);
+
+         if (res->running_jobs) {
+            status.tableRunning->setRowCount(res->running_jobs->size());
+            int row=0;
+            foreach_alist(rjob, res->running_jobs) {
+               int col=0;
+               TableItemFormatter item(*status.tableRunning, row++);
+               item.setNumericFld(col++, QString(edit_uint64(rjob->JobId, ed1)));
+               item.setTextFld(col++, QString(rjob->Job));
+               item.setJobLevelFld(col++, QString(rjob->JobLevel));
+               item.setTextFld(col++, QString(rjob->Client));
+               item.setTextFld(col++, QString(rjob->Storage));
+               item.setNumericFld(col++, QString(edit_uint64(rjob->JobFiles, ed1)));
+               item.setBytesFld(col++, QString(edit_uint64(rjob->JobBytes, ed1)));
+               item.setNumericFld(col++, QString(edit_uint64(rjob->Errors, ed1)));
+            }
+         } else {
+            Dmsg0(0, "Strange, the list is NULL\n");
+         }
+
+         QStringList headerlistT = (QStringList() << tr("JobId")
+                                    << tr("Job")  << tr("Level")
+                                    << tr("Status") << tr("Files") << tr("Bytes")
+                                    << tr("Errors"));
+
+         status.tableTerminated->clear();
+         status.tableTerminated->setRowCount(0);
+         status.tableTerminated->setColumnCount(headerlistT.count());
+         status.tableTerminated->setHorizontalHeaderLabels(headerlistT);
+         status.tableTerminated->setEditTriggers(QAbstractItemView::NoEditTriggers);
+         status.tableTerminated->verticalHeader()->hide();
+         status.tableTerminated->setSortingEnabled(true);
+
+         if (res->terminated_jobs) {
+            status.tableTerminated->setRowCount(res->terminated_jobs->size());
+            int row=0;
+            foreach_dlist(ljob, res->terminated_jobs) {
+               int col=0;
+               TableItemFormatter item(*status.tableTerminated, row++);
+               item.setNumericFld(col++, QString(edit_uint64(ljob->JobId, ed1)));
+               item.setTextFld(col++, QString(ljob->Job));
+               item.setJobLevelFld(col++, QString(ljob->JobLevel));
+               item.setJobStatusFld(col++, QString(ljob->JobStatus));
+               item.setNumericFld(col++, QString(edit_uint64(ljob->JobFiles, ed1)));
+               item.setBytesFld(col++, QString(edit_uint64(ljob->JobBytes, ed1)));
+               item.setNumericFld(col++, QString(edit_uint64(ljob->Errors, ed1)));
+            }
+         } else {
+            Dmsg0(0, "Strange, the list is NULL\n");
+         }
+         res->mutex->unlock();
+      }
+      Dmsg1(50, "  Task %p OK\n", t);
+   }
+   t->deleteLater();
+   status.pushButton->setEnabled(true);   
+}
diff --git a/bacula/src/qt-console/tray-monitor/sdstatus.h b/bacula/src/qt-console/tray-monitor/sdstatus.h
new file mode 100644 (file)
index 0000000..17f0a3f
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#include "common.h"
+#include "ui_sd-monitor.h"
+#include "task.h"
+#include "status.h"
+
+class SDStatus: public ResStatus
+{
+   Q_OBJECT
+
+public:
+   Ui::sdStatus status;
+
+   SDStatus(RESMON *d):  ResStatus(d)
+   {
+      status.setupUi(this);
+      QObject::connect(status.pushButton, SIGNAL(clicked()), this, SLOT(doUpdate()),  Qt::QueuedConnection);
+   };
+   ~SDStatus() {
+   };
+public slots:
+   void doUpdate();
+   void taskDone(task *);
+};
diff --git a/bacula/src/qt-console/tray-monitor/status.cpp b/bacula/src/qt-console/tray-monitor/status.cpp
new file mode 100644 (file)
index 0000000..8320c5b
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#include "status.h"
+#include "lib/worker.h"
+
+void ResStatus::doUpdate()
+{
+   if (count == 0) {
+      task *t = new task();
+      connect(t, SIGNAL(done(task *)), this, SLOT(taskDone(task *)), Qt::QueuedConnection);
+      t->init(res, TASK_STATUS);
+      res->wrk->queue(t);
+      Dmsg0(0, "doUpdate()\n");
+      count++;
+   }
+}
+
+void ResStatus::taskDone(task *t)
+{
+   if (!t->status) {
+      Dmsg2(0, "  Task %p failed => %s\n", t, t->errmsg);
+   }
+   delete t;
+   count--;
+}
diff --git a/bacula/src/qt-console/tray-monitor/status.h b/bacula/src/qt-console/tray-monitor/status.h
new file mode 100644 (file)
index 0000000..0e0f4ea
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#ifndef STATUS_H
+#define STATUS_H
+
+#include "common.h"
+#include <QWidget>
+#include "tray_conf.h"
+#include "task.h"
+
+class ResStatus: public QWidget
+{
+   Q_OBJECT
+
+public:
+   int count;
+   RESMON *res;
+   ResStatus(RESMON *c): count(0), res(c) {
+   };
+   virtual ~ResStatus() {
+   };
+public slots:
+   virtual void doUpdate();
+   virtual void taskDone(task *t);
+};
+
+#endif
diff --git a/bacula/src/qt-console/tray-monitor/task.cpp b/bacula/src/qt-console/tray-monitor/task.cpp
new file mode 100644 (file)
index 0000000..911ba2f
--- /dev/null
@@ -0,0 +1,951 @@
+/*
+   Bacula(R) - The Network Backup Solution
+
+   Copyright (C) 2000-2017 Kern Sibbald
+
+   The original author of Bacula is Kern Sibbald, with contributions
+   from many others, a complete list can be found in the file AUTHORS.
+
+   You may use this file and others of this release according to the
+   license defined in the LICENSE file, which includes the Affero General
+   Public License, v3.0 ("AGPLv3") and some additional permissions and
+   terms pursuant to its AGPLv3 Section 7.
+
+   This notice must be preserved when any source code is
+   conveyed and/or propagated.
+
+   Bacula(R) is a registered trademark of Kern Sibbald.
+*/
+
+#include "task.h"
+#include "jcr.h"
+#define dbglvl 10
+int authenticate_daemon(JCR *jcr, MONITOR *monitor, RESMON *res);
+
+static void *handle_task(void *data)
+{
+   task   *t;
+   worker *wrk = (worker *)data;
+   lmgr_init_thread();
+   
+   wrk->set_running();
+   Dmsg0(dbglvl, "Worker started\n");
+
+   while (!wrk->is_quit_state()) {
+      if (wrk->is_wait_state()) {
+         wrk->wait();
+         continue;
+      }   
+      t = (task *)wrk->dequeue();
+      if (!t) {
+         continue;
+      }
+      /* Do the work */
+      switch(t->type) {
+      case TASK_STATUS:
+         t->do_status();
+         break;
+      case TASK_RESOURCES:
+         t->get_resources();
+         break;
+      case TASK_DEFAULTS:
+         t->get_job_defaults();
+         break;
+      case TASK_RUN:
+         t->run_job();
+         break;
+      case TASK_BWLIMIT:
+         t->set_bandwidth();
+         break;
+      case TASK_INFO:
+         t->get_job_info(t->arg2);
+         break;
+      case TASK_DISCONNECT:
+         t->disconnect_bacula();
+         t->mark_as_done();
+         break;
+      default:
+         Mmsg(t->errmsg, "Unknown task");
+         t->mark_as_failed();
+         break;
+      }
+   }
+   Dmsg0(dbglvl, "Worker stoped\n");
+   lmgr_cleanup_thread();
+   return NULL;
+}
+
+bool task::set_bandwidth()
+{
+   bool ret = false;
+   btimer_t *tid = NULL;
+   if (res->type != R_CLIENT) {
+      mark_as_failed();
+      Mmsg(errmsg, _("Bandwidth can set only set on Client"));
+      return false;
+   }
+   if (!arg || !*arg) {
+      mark_as_failed();
+      Mmsg(errmsg, _("Bandwidth parameter is invalid"));
+      return false;
+   }
+
+   if (res->proxy_sent) {
+      free_bsock(res->bs);
+   }
+
+   if (!res->bs || !res->bs->is_open() || res->bs->is_error()) {
+      if (!connect_bacula()) { 
+         mark_as_failed();
+         return false;
+      }
+   }
+
+   tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120);
+   res->bs->fsend("setbandwidth limit=%s\n", NPRTB(arg));
+   while (get_next_line(res)) {
+      Dmsg1(dbglvl, "-> %s\n", curline);
+   }
+
+   if (tid) {
+      stop_thread_timer(tid);
+   }
+
+   /* Do not reuse the same socket */
+   disconnect_bacula();
+
+   if (ret) {
+      mark_as_done();
+   } else {
+      mark_as_failed();
+   }
+   return ret;
+}
+
+RESMON *task::get_res()
+{
+   return res;
+}
+
+void task::lock_res()
+{
+   res->mutex->lock();
+}
+
+void task::unlock_res()
+{
+   res->mutex->unlock();
+}
+
+bool task::disconnect_bacula()
+{
+   free_bsock(res->bs);
+   return true;
+}
+
+bool task::connect_bacula()
+{
+   JCR jcr;
+   bool ret = false;
+   memset(&jcr, 0, sizeof(jcr));
+   curend = curline = NULL;
+
+   RESMON *r = get_res();
+   MONITOR *monitor = (MONITOR*)GetNextRes(R_MONITOR, NULL);
+
+   if (r->type == R_CLIENT) {
+      r->proxy_sent = false;
+      if (r->bs && (r->bs->is_error() || !r->bs->is_open())) {
+         free_bsock(r->bs);
+      }
+      if (!r->bs) {
+         r->bs = new_bsock();
+         Dmsg0(dbglvl, "Trying to connect to FD\n");
+         if (r->bs->connect(NULL, r->connect_timeout, 0, 0, _("Client daemon"),
+                            r->address, NULL, r->port, 0))
+         {
+            Dmsg0(dbglvl, "Connect done!\n");
+            jcr.file_bsock = r->bs;
+            if (!authenticate_daemon(&jcr, monitor, r)) {
+               Dmsg0(dbglvl, "Unable to authenticate\n");
+               Mmsg(errmsg, "Unable to authenticate with the FileDaemon");
+               free_bsock(r->bs);
+               return false;
+            }
+            Dmsg0(dbglvl, "Authenticate OK\n");
+            ret = true;
+         } else {
+            Mmsg(errmsg, "Unable to connect to the FileDaemon");
+            Dmsg0(dbglvl, "Connect error!\n");
+         }
+      } else {
+         ret = true;
+      }
+   }
+   if (r->type == R_STORAGE) {
+      if (r->bs && (r->bs->is_error() || !r->bs->is_open())) {
+         free_bsock(r->bs);
+      }
+      if (!r->bs) {
+         r->bs = new_bsock();
+         Dmsg0(dbglvl, "Trying to connect to FD\n");
+         if (r->bs->connect(NULL, r->connect_timeout, 0, 0, _("Storage daemon"),
+                            r->address, NULL, r->port, 0))
+         {
+            Dmsg0(dbglvl, "Connect done!\n");
+            jcr.store_bsock = r->bs;
+            if (!authenticate_daemon(&jcr, monitor, r)) {
+               Dmsg0(dbglvl, "Unable to authenticate\n");
+               Mmsg(errmsg, "Unable to authenticate with the Storage Daemon");
+               free_bsock(r->bs);
+               return false;
+            }
+            Dmsg0(dbglvl, "Authenticate OK\n");
+            ret = true;
+         } else {
+            Mmsg(errmsg, "Unable to connect to the Storage Daemon");
+            Dmsg0(dbglvl, "Connect error!\n");
+         }
+      } else {
+         ret = true;
+      }
+   }
+   if (r->type == R_DIRECTOR) {
+      if (r->bs && (r->bs->is_error() || !r->bs->is_open())) {
+         free_bsock(r->bs);
+      }
+      if (!r->bs) {
+         r->bs = new_bsock();
+         Dmsg0(dbglvl, "Trying to connect to DIR\n");
+         if (r->bs->connect(NULL, r->connect_timeout, 0, 0, _("Director daemon"),
+                            r->address, NULL, r->port, 0))
+         {
+            Dmsg0(dbglvl, "Connect done!\n");
+            jcr.dir_bsock = r->bs;
+            if (!authenticate_daemon(&jcr, monitor, r)) {
+               Dmsg0(dbglvl, "Unable to authenticate\n");
+               Mmsg(errmsg, "Unable to authenticate with the Director");
+               free_bsock(r->bs);
+               return false;
+            }
+            Dmsg0(dbglvl, "Authenticate OK\n");
+            ret = true;
+         } else {
+            Mmsg(errmsg, "Unable to connect to the Director");
+            Dmsg0(dbglvl, "Connect error!\n");
+         }
+      } else {
+         ret = true;
+      }
+   }
+   return ret;
+}
+
+bool task::read_status_running(RESMON *r)
+{
+   bool ret = false;
+   char *start, *end;
+   struct s_running_job *item = NULL;
+   alist *running_jobs = New(alist(10, owned_by_alist));
+   
+   while (r->bs->recv() >= -1) {
+      if (r->bs->msglen < 0 &&
+          r->bs->msglen != BNET_CMD_BEGIN &&
+          r->bs->msglen != BNET_CMD_OK)
+      {
+         Dmsg1(dbglvl, "Got Signal %s\n", bnet_sig_to_ascii(r->bs->msglen));
+         break;
+      }
+      Dmsg2(dbglvl, "RECV -> %s:%d\n", r->bs->msg, r->bs->msglen);
+      start = r->bs->msg;
+
+      while ((end = strchr(start, '\n')) != NULL) {
+         *end = 0;
+         Dmsg1(dbglvl, "line=[%s]\n", start);
+         if (strncasecmp(start, "jobid=", 6) == 0) {
+            if (item) {
+               Dmsg1(dbglvl, "Append item %ld\n", item->JobId);
+               running_jobs->append(item);
+            }
+            item = (struct s_running_job *)malloc(sizeof(struct s_running_job));
+            memset(item, 0, sizeof(struct s_running_job));
+            item->JobId = str_to_uint64(start + 6);
+
+         } else if (!item) {
+            Dmsg0(dbglvl, "discard line\n");
+
+         } else if (strncasecmp(start, "level=", 6) == 0) {
+            item->JobLevel = start[6];
+
+         } else if (strncasecmp(start, "type=", 5) == 0) {
+            item->JobType = start[5];
+
+         } else if (strncasecmp(start, "status=", 7) == 0) {
+            item->JobStatus = start[7];
+
+         } else if (strncasecmp(start, "jobbytes=", 9) == 0) {
+            item->JobBytes = str_to_uint64(start + 9);
+
+         } else if (strncasecmp(start, "jobfiles=", 9) == 0) {
+            item->JobFiles = str_to_uint64(start + 9);
+
+         } else if (strncasecmp(start, "job=", 4) == 0) {
+            bstrncpy(item->Job, start + 4, sizeof(item->Job));
+
+         } else if (strncasecmp(start, "starttime_epoch=", 16) == 0) {
+            item->start_time = str_to_uint64(start + 16);
+
+         } else if (strncasecmp(start, "schedtime_epoch=", 16) == 0) {
+            item->sched_time = str_to_uint64(start + 16);
+
+         } else if (strncasecmp(start, "bytes/sec=", 10) == 0) {
+            item->bytespersec = str_to_uint64(start + 10);
+
+         } else if (strncasecmp(start, "avebytes_sec=", 13) == 0) {
+            item->bytespersec = str_to_uint64(start + 13);
+
+         } else if (strncasecmp(start, "errors=", 7) == 0) {
+            item->Errors = str_to_uint64(start + 7);
+
+         } else if (strncasecmp(start, "readbytes=", 10) == 0) {
+            item->ReadBytes = str_to_uint64(start + 10);
+
+         } else if (strncasecmp(start, "processing file=", 16) == 0) {
+            bstrncpy(item->CurrentFile, start + 16, sizeof(item->CurrentFile));
+
+         } else if (strncasecmp(start, "clientname=", 11) == 0) {
+            bstrncpy(item->Client, start + 11, sizeof(item->Client));
+
+         } else if (strncasecmp(start, "fileset=", 8) == 0) {
+            bstrncpy(item->FileSet, start + 8, sizeof(item->FileSet));
+
+         } else if (strncasecmp(start, "storage=", 8) == 0) {
+            bstrncpy(item->Storage, start + 8, sizeof(item->Storage));
+
+         } else if (strncasecmp(start, "rstorage=", 8) == 0) {
+            bstrncpy(item->RStorage, start + 8, sizeof(item->Storage));
+
+         } else if (strncasecmp(start, "sdtls=", 6) == 0) {
+            item->SDtls = str_to_uint64(start + 6);
+         }
+         start = end+1;
+      }
+      r->last_update = time(NULL);
+
+      if (r->bs->is_error()) {
+         Mmsg(errmsg, "Got error on the socket communication line");
+         goto bail_out;
+      } 
+   }
+   if (item) {
+      Dmsg1(dbglvl, "Append item %ld\n", item->JobId);
+      running_jobs->append(item);
+   }
+   ret = true;
+
+bail_out:
+   r->mutex->lock();
+   if (r->running_jobs) {
+      delete r->running_jobs;
+   }
+   r->running_jobs = running_jobs;
+   r->mutex->unlock();
+
+   return ret;
+}
+
+bool task::read_status_terminated(RESMON *r)
+{
+   bool ret = false;
+   char *start, *end;
+   struct s_last_job *item = NULL;
+
+   r->mutex->lock();
+   if (r->terminated_jobs) {
+      delete r->terminated_jobs;
+   }
+   r->terminated_jobs = New(dlist(item, &item->link));
+   r->mutex->unlock();
+
+   while (r->bs->recv() >= -1) {
+      if (r->bs->msglen < 0 &&
+          r->bs->msglen != BNET_CMD_BEGIN &&
+          r->bs->msglen != BNET_CMD_OK)
+      {
+         Dmsg1(dbglvl, "Got Signal %s\n", bnet_sig_to_ascii(r->bs->msglen));
+         break;
+      }
+
+      Dmsg2(dbglvl, "RECV -> %s:%d\n", r->bs->msg, r->bs->msglen);
+      r->mutex->lock();
+      start = r->bs->msg;
+
+      while ((end = strchr(start, '\n')) != NULL) {
+         *end = 0;
+         Dmsg1(dbglvl, "line=[%s]\n", start);
+         if (strncasecmp(start, "jobid=", 6) == 0) {
+            if (item) {
+               Dmsg1(dbglvl, "Append item %ld\n", item->JobId);
+               r->terminated_jobs->append(item);
+            }
+            item = (struct s_last_job *)malloc(sizeof(struct s_last_job));
+            memset(item, 0, sizeof(struct s_last_job));
+            item->JobId = str_to_uint64(start + 6);
+
+         } else if (!item) {
+            Dmsg0(dbglvl, "discard line\n");
+
+         } else if (strncasecmp(start, "level=", 6) == 0) {
+            item->JobLevel = start[6];
+
+         } else if (strncasecmp(start, "type=", 5) == 0) {
+            item->JobType = start[5];
+
+         } else if (strncasecmp(start, "status=", 7) == 0) {
+            item->JobStatus = start[7];
+
+         } else if (strncasecmp(start, "jobbytes=", 9) == 0) {
+            item->JobBytes = str_to_uint64(start + 9);
+
+         } else if (strncasecmp(start, "jobfiles=", 9) == 0) {
+            item->JobFiles = str_to_uint64(start + 9);
+
+         } else if (strncasecmp(start, "job=", 4) == 0) {
+            bstrncpy(item->Job, start + 4, sizeof(item->Job));
+
+         } else if (strncasecmp(start, "starttime_epoch=", 16) == 0) {
+            item->start_time = str_to_uint64(start + 16);
+
+         } else if (strncasecmp(start, "endtime_epoch=", 14) == 0) {
+            item->end_time = str_to_uint64(start + 14);
+
+         } else if (strncasecmp(start, "errors=", 7) == 0) {
+            item->Errors = str_to_uint64(start + 7);
+         }
+         start = end+1;
+      }
+      r->last_update = time(NULL);
+      r->mutex->unlock();
+
+      if (r->bs->is_error()) {
+         Mmsg(errmsg, "Got error on the socket communication line");
+         goto bail_out;
+      }
+   }
+   if (item) {
+      r->mutex->lock();
+      Dmsg1(dbglvl, "Append item %ld\n", item->JobId);
+      r->terminated_jobs->append(item);
+      r->mutex->unlock();
+   }
+   ret = true;
+
+bail_out:
+   return ret;
+}
+
+bool task::read_status_header(RESMON *r)
+{
+   bool ret = false;
+   char *start, *end;
+
+   while (r->bs->recv() >= -1) {
+      if (r->bs->msglen < 0 &&
+          r->bs->msglen != BNET_CMD_BEGIN &&
+          r->bs->msglen != BNET_CMD_OK)
+      {
+         Dmsg1(dbglvl, "Got Signal %d\n", r->bs->msglen);
+         break;
+      }
+
+      Dmsg2(dbglvl, "RECV -> %s:%d\n", r->bs->msg, r->bs->msglen);
+      r->mutex->lock();
+      start = r->bs->msg;
+
+      while ((end = strchr(start, '\n')) != NULL) {
+         *end = 0;
+         Dmsg1(dbglvl, "line=[%s]\n", start);
+         if (strncasecmp(start, "name=", 5) == 0) {
+            bstrncpy(r->name, start + 5, sizeof(r->name));
+
+         } else if (strncasecmp(start, "version=", 8) == 0) {
+            bstrncpy(r->version, start + 8, sizeof(r->version));
+
+         } else if (strncasecmp(start, "plugins=", 8) == 0) {
+            bstrncpy(r->plugins, start + 8, sizeof(r->plugins));
+
+         } else if (strncasecmp(start, "bwlimit=", 8) == 0) {
+            r->bwlimit = str_to_uint64(start + 8);
+
+         } else if (strncasecmp(start, "started=", 8) == 0) {
+            bstrncpy(r->started, start + 8, sizeof(r->started));
+
+         } else if (strncasecmp(start, "reloaded=", 9) == 0) {
+            bstrncpy(r->reloaded, start + 9, sizeof(r->reloaded));
+         }
+         start = end+1;
+      }
+
+      if (r->bs->is_error()) {
+         r->mutex->unlock();
+         Mmsg(errmsg, "Got error on the socket communication line");
+         goto bail_out;
+
+      } 
+      r->last_update = time(NULL);
+      r->mutex->unlock();
+   }
+   ret = true;
+bail_out:
+   return ret;
+}
+
+
+bool task::do_status()
+{
+   bool ret = false;
+   btimer_t *tid = NULL;
+
+   /* We don't want to use a proxy session */
+   if (res->type == R_CLIENT && res->proxy_sent) {
+      free_bsock(res->bs);
+   }
+   if (!res->bs || !res->bs->is_open() || res->bs->is_error()) {
+      if (!connect_bacula()) {
+         goto bail_out;
+      }
+   }
+   /* TODO: */
+   tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120);
+   if (res->type == R_CLIENT || res->type == R_STORAGE) {
+      Dmsg0(dbglvl, "Send status command header\n");
+      res->bs->fsend(".status header api=2\n");
+      // TODO: Update a local set of variables and commit everything when it's done
+      ret = read_status_header(res);
+
+      if (ret) {
+         res->bs->fsend(".status terminated api=2\n");
+         ret = read_status_terminated(res);
+      }
+      if (ret) {
+         res->bs->fsend(".status running api=2\n");
+         ret = read_status_running(res);
+      }
+   }
+   if (res->type == R_DIRECTOR) {
+      Dmsg0(dbglvl, "-> .api 2\n");
+      res->bs->fsend(".api 2\n");
+      while (get_next_line(res)) {
+         Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+      }
+      Dmsg0(dbglvl, "Send status command header\n");
+      res->bs->fsend(".status dir header\n");
+      // TODO: Update a local set of variables and commit everything when it's done
+      ret = read_status_header(res);
+
+      if (ret) {
+         Dmsg0(dbglvl, "Send status command terminated\n");
+         res->bs->fsend(".status dir terminated\n");
+         ret = read_status_terminated(res);
+      }
+      if (ret) {
+         Dmsg0(dbglvl, "Send status command running\n");
+         res->bs->fsend(".status dir running\n");
+         ret = read_status_running(res);
+      }
+   }
+bail_out:
+   if (tid) {
+      stop_thread_timer(tid);
+   }
+   /* Use a new socket the next time */
+   disconnect_bacula();
+   if (ret) {
+      mark_as_done();
+   } else {
+      mark_as_failed();
+   }
+   return ret;
+}
+
+bool task::get_next_line(RESMON *r)
+{
+   /* We are currently reading a line */
+   if (curline && curend && r->bs->msglen > 0 && curend < (r->bs->msg + r->bs->msglen - 1)) {
+      curline = curend + 1;    /* skip \0 */
+      if ((curend = strchr(curline, '\n')) != NULL) {
+         *curend = '\0';
+      }
+      return true;
+   }
+   curline = curend = NULL;
+   do {
+      r->bs->recv();
+
+      if (r->bs->msglen < 0) {
+         Dmsg1(dbglvl, "<- %s\n", bnet_sig_to_ascii(r->bs->msglen));
+         switch(r->bs->msglen) {
+         case BNET_ERROR_MSG:
+            r->bs->recv();
+            strip_trailing_junk(r->bs->msg);
+            Dmsg1(0, "ERROR: %s\n", r->bs->msg);
+            break;
+         case BNET_MAIN_PROMPT:       // stop
+            return false;
+         case BNET_CMD_OK:
+         case BNET_CMD_BEGIN:
+         case BNET_MSGS_PENDING:
+            break;
+         case BNET_TERMINATE:
+            return false;
+         default:                     // error or question?
+            return false;
+         }
+
+      } else if (r->bs->msglen == 0) { // strange
+         return false;
+
+      } else {
+         Dmsg1(10, "<- %s\n", r->bs->msg);
+         curline = r->bs->msg;
+         curend = strchr(curline, '\n');
+         if (curend) {
+            *curend = 0;
+         }
+         return true;           // something to read
+      }
+   } while (!r->bs->is_error());
+   return false;
+}
+
+bool task::get_job_defaults()
+{
+   bool ret = false;
+   btimer_t *tid = NULL;
+   char *p;
+
+   if (!res->bs || !res->bs->is_open() || res->bs->is_error()) {
+      if (!connect_bacula()) { 
+         goto bail_out;
+      }
+   }
+
+   res->mutex->lock();
+   bfree_and_null(res->defaults.client);
+   bfree_and_null(res->defaults.pool);
+   bfree_and_null(res->defaults.storage);
+   bfree_and_null(res->defaults.level);
+   bfree_and_null(res->defaults.type);
+   bfree_and_null(res->defaults.fileset);
+   bfree_and_null(res->defaults.catalog);
+
+   tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120);
+   if (res->type == R_CLIENT && !res->proxy_sent) {
+      res->proxy_sent = true;
+      res->bs->fsend("proxy\n");
+      while (get_next_line(res)) {
+         if (strncmp(curline, "2000", 4) != 0) {
+            pm_strcpy(errmsg, curline);
+            goto bail_out;
+         }
+         Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+      }
+   }
+
+   res->bs->fsend(".api 2\n");
+   while (get_next_line(res)) {
+      Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+   }
+   res->bs->fsend(".defaults job=\"%s\"\n", res->defaults.job);
+   while (get_next_line(res)) {
+      Dmsg1(dbglvl, "line = [%s]\n", curline);
+      if ((p = strchr(curline, '=')) == NULL) {
+         continue;
+      }
+      *p++ = 0;
+      if (strcasecmp(curline, "client") == 0) {
+         res->defaults.client = bstrdup(p);
+
+      } else if (strcasecmp(curline, "pool") == 0) {
+         res->defaults.pool = bstrdup(p);
+
+      } else if (strcasecmp(curline, "storage") == 0) {
+         res->defaults.storage = bstrdup(p);
+
+      } else if (strcasecmp(curline, "level") == 0) {
+         res->defaults.level = bstrdup(p);
+
+      } else if (strcasecmp(curline, "type") == 0) {
+         res->defaults.type = bstrdup(p);
+
+      } else if (strcasecmp(curline, "fileset") == 0) {
+         res->defaults.fileset = bstrdup(p);
+
+      } else if (strcasecmp(curline, "catalog") == 0) {
+         res->defaults.catalog = bstrdup(p);
+
+      } else if (strcasecmp(curline, "priority") == 0) {
+         res->defaults.priority = str_to_uint64(p);
+      }
+   }
+   ret = true;
+bail_out:
+   if (tid) {
+      stop_thread_timer(tid);
+   }
+   if (ret) {
+      mark_as_done();
+   } else {
+      mark_as_failed();
+   }
+   res->mutex->unlock();   
+   return ret;
+}
+
+bool task::get_job_info(const char *level)
+{
+   bool ret = false;
+   btimer_t *tid = NULL;
+   char *p;
+
+   if (!res->bs || !res->bs->is_open() || res->bs->is_error()) {
+      if (!connect_bacula()) { 
+         goto bail_out;
+      }
+   }
+   res->mutex->lock();
+   memset(&res->infos, 0, sizeof(res->infos));
+
+   tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120);
+   if (res->type == R_CLIENT && !res->proxy_sent) {
+      res->proxy_sent = true;
+      res->bs->fsend("proxy\n");
+      while (get_next_line(res)) {
+         if (strncmp(curline, "2000", 4) != 0) {
+            pm_strcpy(errmsg, curline);
+            goto bail_out;
+         }
+         Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+      }
+   }
+
+   res->bs->fsend(".api 2\n");
+   while (get_next_line(res)) {
+      Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+   }
+   if (level) {
+      res->bs->fsend(".estimate job=\"%s\" level=%s\n", arg, level);
+   } else {
+      res->bs->fsend(".estimate job=\"%s\"\n", arg);
+   }
+   while (get_next_line(res)) {
+      Dmsg1(dbglvl, "line = [%s]\n", curline);
+      if ((p = strchr(curline, '=')) == NULL) {
+         continue;
+      }
+      *p++ = 0;
+      if (strcasecmp(curline, "level") == 0) {
+         res->infos.JobLevel = p[0];
+
+      } else if (strcasecmp(curline, "jobbytes") == 0) {
+         res->infos.JobBytes = str_to_uint64(p);
+
+      } else if (strcasecmp(curline, "jobfiles") == 0) {
+         res->infos.JobFiles = str_to_uint64(p);
+
+      } else if (strcasecmp(curline, "corrbytes") == 0) {
+         res->infos.CorrJobBytes = str_to_uint64(p);
+
+      } else if (strcasecmp(curline, "corrfiles") == 0) {
+         res->infos.CorrJobFiles = str_to_uint64(p);
+
+      } else if (strcasecmp(curline, "nbjob") == 0) {
+         res->infos.CorrNbJob = str_to_uint64(p);
+      }
+   }
+   ret = true;
+bail_out:
+   res->mutex->unlock();
+   if (tid) {
+      stop_thread_timer(tid);
+   }
+   if (ret) {
+      mark_as_done();
+   } else {
+      mark_as_failed();
+   }
+   return ret;
+}
+
+bool task::run_job()
+{
+   bool ret = false;
+   char *p;
+   btimer_t *tid = NULL;
+
+   if (!res->bs || !res->bs->is_open() || res->bs->is_error()) {
+      if (!connect_bacula()) { 
+         goto bail_out;
+      }
+   }
+
+   tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120);
+   if (res->type == R_CLIENT && !res->proxy_sent) {
+      res->proxy_sent = true;
+      res->bs->fsend("proxy\n");
+      while (get_next_line(res)) {
+         if (strncmp(curline, "2000", 4) != 0) {
+            pm_strcpy(errmsg, curline);
+            goto bail_out;
+         }
+         Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+      }
+   }
+
+   res->bs->fsend(".api 2\n");
+   while (get_next_line(res)) {
+      Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+   }
+   if (res->type == R_DIRECTOR && res->use_setip) {
+      res->bs->fsend("setip\n");
+      while (get_next_line(res)) {
+         Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+      }
+   }
+   res->bs->fsend("%s\n", arg);
+   while (get_next_line(res)) {
+      if ((p = strstr(curline, "JobId=")) != NULL && sscanf(p, "JobId=%d\n", &result.i) == 1) {
+         ret = true;
+      }
+   }
+   // Close the socket, it's over or we don't want to reuse it
+   disconnect_bacula();
+   
+bail_out:
+
+   if (tid) {
+      stop_thread_timer(tid);
+   }
+   if (ret) {
+      mark_as_done();
+   } else {
+      mark_as_failed();
+   }
+   return ret;
+}
+
+/* Get resources to run a job */
+bool task::get_resources()
+{
+   bool ret = false;
+   btimer_t *tid = NULL;
+
+   if (!res->bs || !res->bs->is_open() || res->bs->is_error()) {
+      if (!connect_bacula()) { 
+         goto bail_out;
+      }
+   }
+
+   res->mutex->lock();
+   if (res->jobs) {
+      delete res->jobs;
+   }
+   res->jobs = New(alist(10, owned_by_alist));
+   if (res->clients) {
+      delete res->clients;
+   }
+   res->clients = New(alist(10, owned_by_alist));
+   if (res->filesets) {
+      delete res->filesets;
+   }
+   res->filesets = New(alist(10, owned_by_alist));
+   if (res->pools) {
+      delete res->pools;
+   }
+   res->pools = New(alist(10, owned_by_alist));
+   if (res->storages) {
+      delete res->storages;
+   }
+   res->storages = New(alist(10, owned_by_alist));
+   if (res->catalogs) {
+      delete res->catalogs;
+   }
+   res->catalogs = New(alist(10, owned_by_alist));
+
+   tid = start_thread_timer(NULL, pthread_self(), (uint32_t)120);
+   if (res->type == R_CLIENT && !res->proxy_sent) {
+      res->proxy_sent = true;
+      res->bs->fsend("proxy\n");
+      while (get_next_line(res)) {
+         if (strncmp(curline, "2000", 4) != 0) {
+            pm_strcpy(errmsg, curline);
+            goto bail_out;
+         }
+         Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+      }
+      Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+   }
+
+   res->bs->fsend(".api 2\n");
+   while (get_next_line(res)) {
+      Dmsg2(dbglvl, "<- %d %s\n", res->bs->msglen, curline);
+   }
+
+   res->bs->fsend(".jobs type=B\n");
+   while (get_next_line(res)) {
+      res->jobs->append(bstrdup(curline));
+   }
+
+   res->bs->fsend(".pools\n");
+   while (get_next_line(res)) {
+      res->pools->append(bstrdup(curline));
+   }
+
+   res->bs->fsend(".clients\n");
+   while (get_next_line(res)) {
+      res->clients->append(bstrdup(curline));
+   }
+
+   res->bs->fsend(".filesets\n");
+   while (get_next_line(res)) {
+      res->filesets->append(bstrdup(curline));
+   }
+
+   res->bs->fsend(".storage\n");
+   while (get_next_line(res)) {
+      res->storages->append(bstrdup(curline));
+   }
+
+   res->bs->fsend(".catalog\n");
+   while (get_next_line(res)) {
+      res->catalogs->append(bstrdup(curline));
+   }
+
+bail_out:
+   res->mutex->unlock();
+
+   if (tid) {
+      stop_thread_timer(tid);
+   }
+   if (ret) {
+      mark_as_done();
+   } else {
+      mark_as_failed();
+   }
+   return ret;
+}
+
+worker *worker_start()
+{
+   worker *w = New(worker());
+   w->start(handle_task, w);
+   return w;
+}
+
+void worker_stop(worker *w)
+{
+   if (w) {
+      w->stop();
+      delete w;
+   }
+}
index 30aea7db37edb43ffc190244bfa33c9de0424e5c..3f72921b2da599d7ad8c9ac848688c2a225688b6 100644 (file)
@@ -1,6 +1,9 @@
 #
 # Bacula Tray Monitor Configuration File
 #
+# Copyright (C) 2000-2017 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
 
 Monitor {
   Name = @basename@-mon
index 92247c0ca701254edc557b9acd2f85b47d1c27ff..8c414a05ad42fa61b68bf3213c25c642f2e0c8b8 100644 (file)
@@ -6,6 +6,9 @@
 #
 #  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 #    
+# Copyright (C) 2000-2017 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
 # CONFIG options for Windows are pulled from win32/qmake.conf
 CONFIG += qt
 #CONFIG += qt debug 
index 763c03244afca5d7c68961c787458e369000d271..362fff1b30d42ee84e5b02025a819698c5941d1f 100644 (file)
@@ -7,11 +7,16 @@
 #  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 #    
 # CONFIG options for Windows are pulled from win32/qmake.conf
-CONFIG += qt
+#
+# Copyright (C) 2000-2017 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+
+CONFIG += qt cross-win32
 #CONFIG += qt debug 
 
 cross-win32 {
-  LIBS       +=  -mwindows -L../../win32/release32 -lbacula
+  LIBS       +=  -mwindows -L../../win32/release32 -lbacula -lpthread
   INCLUDEPATH += ../../win32/compat
 }
 !cross-win32 {
index 39957e67f9ffbaf24d0aeb4c21409fc93b9ddc25..20719ba89078da56fb040a36ac3314a1c159d864 100644 (file)
@@ -7,11 +7,16 @@
 #  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 #    
 # CONFIG options for Windows are pulled from win32/qmake.conf
-CONFIG += qt
+#
+# Copyright (C) 2000-2017 Kern Sibbald
+# License: BSD 2-Clause; see file LICENSE-FOSS
+#
+
+CONFIG += qt cross-win32
 #CONFIG += qt debug 
 
 cross-win32 {
-  LIBS       +=  -mwindows -L../../win32/release64 -lbacula
+  LIBS       +=  -mwindows -L../../win32/release64 -lbacula -lpthread
   INCLUDEPATH += ../../win32/compat
 }
 !cross-win32 {