]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/qt-console/joblist/joblist.cpp
a2d2747d0ea825a06760c96f2dfeed5f1390c915
[bacula/bacula] / bacula / src / qt-console / joblist / joblist.cpp
1 /*
2    Bacula® - The Network Backup Solution
3
4    Copyright (C) 2000-2007 Free Software Foundation Europe e.V.
5
6    The main author of Bacula is Kern Sibbald, with contributions from
7    many others, a complete list can be found in the file AUTHORS.
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version two of the GNU General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22
23    Bacula® is a registered trademark of John Walker.
24    The licensor of Bacula is the Free Software Foundation Europe
25    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26    Switzerland, email:ftf@fsfeurope.org.
27 */
28 /*
29  *   Version $Id: joblist.h 4230 2007-02-21 20:07:37Z kerns $
30  *
31  *   Dirk Bartley, March 2007
32  */
33  
34 #include <QAbstractEventDispatcher>
35 #include <QTableWidgetItem>
36 #include "bat.h"
37 #include "joblist.h"
38 #include "restore.h"
39 #include "joblog/joblog.h"
40 #include "jobgraphs/jobplot.h"
41
42 /*
43  * Constructor for the class
44  */
45 JobList::JobList(const QString &mediaName, const QString &clientName,
46           const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
47 {
48    setupUi(this);
49    m_name = ""; /* treeWidgetName has a virtual override in this class */
50    m_mediaName = mediaName;
51    m_clientName = clientName;
52    m_jobName = jobName;
53    m_filesetName = filesetName;
54    m_filesetName = filesetName;
55    pgInitialize(parentTreeWidgetItem);
56    QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
57    thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.svg")));
58
59    m_resultCount = 0;
60    m_populated = false;
61    m_closeable = false;
62    if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
63       m_closeable=true;
64    m_checkCurrentWidget = true;
65    createConnections();
66
67    /* Set Defaults for check and spin for limits */
68    limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
69    limitSpinBox->setValue(mainWin->m_recordLimitVal);
70    daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
71    daysSpinBox->setValue(mainWin->m_daysLimitVal);
72    dockPage();
73 }
74
75 /*
76  * The Meat of the class.
77  * This function will populate the QTableWidget, mp_tablewidget, with
78  * QTableWidgetItems representing the results of a query for what jobs exist on
79  * the media name passed from the constructor stored in m_mediaName.
80  */
81 void JobList::populateTable()
82 {
83    QStringList results;
84    QString resultline;
85    QBrush blackBrush(Qt::black);
86
87    if (!m_console->preventInUseConnect())
88        return;
89
90    /* Can't do this in constructor because not neccesarily conected in constructor */
91    if (!m_populated) {
92       clientComboBox->addItem("Any");
93       clientComboBox->addItems(m_console->client_list);
94       int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly);
95       if (clientIndex != -1)
96          clientComboBox->setCurrentIndex(clientIndex);
97
98       QStringList volumeList;
99       m_console->getVolumeList(volumeList);
100       volumeComboBox->addItem("Any");
101       volumeComboBox->addItems(volumeList);
102       int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
103       if (volumeIndex != -1) {
104          volumeComboBox->setCurrentIndex(volumeIndex);
105       }
106       jobComboBox->addItem("Any");
107       jobComboBox->addItems(m_console->job_list);
108       int jobIndex = jobComboBox->findText(m_jobName, Qt::MatchExactly);
109       if (jobIndex != -1) {
110          jobComboBox->setCurrentIndex(jobIndex);
111       }
112       levelComboBox->addItem("Any");
113       levelComboBox->addItems( QStringList() << "F" << "D" << "I");
114       purgedComboBox->addItem("Any");
115       purgedComboBox->addItems( QStringList() << "0" << "1");
116       fileSetComboBox->addItem("Any");
117       fileSetComboBox->addItems(m_console->fileset_list);
118       int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly);
119       if (filesetIndex != -1) {
120          fileSetComboBox->setCurrentIndex(filesetIndex);
121       }
122       QStringList statusLongList;
123       m_console->getStatusList(statusLongList);
124       statusComboBox->addItem("Any");
125       statusComboBox->addItems(statusLongList);
126    }
127
128    /* Set up query */
129    QString query("");
130    int volumeIndex = volumeComboBox->currentIndex();
131    if (volumeIndex != -1)
132       m_mediaName = volumeComboBox->itemText(volumeIndex);
133    query += "SELECT DISTINCT Job.Jobid AS Id, Job.Name AS JobName, Client.Name AS Client,"
134             " Job.Starttime AS JobStart, Job.Type AS JobType,"
135             " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
136             " Job.JobBytes AS Bytes,"
137             " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
138             " Job.PurgedFiles AS Purged, FileSet.FileSet"
139             " FROM Job"
140             " LEFT OUTER JOIN Client ON (Client.ClientId=Job.ClientId)"
141             " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)"
142             " LEFT OUTER JOIN Status ON (Job.JobStatus=Status.JobStatus)"
143             " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)"
144             " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)";
145    QStringList conditions;
146    if (m_mediaName != "Any") {
147       conditions.append("Media.VolumeName='" + m_mediaName + "'");
148    }
149    int clientIndex = clientComboBox->currentIndex();
150    if (clientIndex != -1)
151       m_clientName = clientComboBox->itemText(clientIndex);
152    if (m_clientName != "Any") {
153       conditions.append("Client.Name='" + m_clientName + "'");
154    }
155    int jobIndex = jobComboBox->currentIndex();
156    if (jobIndex != -1)
157       m_jobName = jobComboBox->itemText(jobIndex);
158    if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
159       conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
160    }
161    int levelIndex = levelComboBox->currentIndex();
162    if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
163       conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
164    }
165    int statusIndex = statusComboBox->currentIndex();
166    if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
167       conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
168    }
169    int purgedIndex = purgedComboBox->currentIndex();
170    if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
171       conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
172    }
173    int fileSetIndex = fileSetComboBox->currentIndex();
174    if (fileSetIndex != -1)
175       m_filesetName = fileSetComboBox->itemText(fileSetIndex);
176    if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
177       conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
178    }
179    /* If Limit check box For limit by days is checked  */
180    if (daysCheckBox->checkState() == Qt::Checked) {
181       QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
182       QString since = stamp.toString(Qt::ISODate);
183       conditions.append("Job.Starttime>'" + since + "'");
184    }
185    bool first = true;
186    foreach (QString condition, conditions) {
187       if (first) {
188          query += " WHERE " + condition;
189          first = false;
190       } else {
191          query += " AND " + condition;
192       }
193    }
194    /* Descending */
195    query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
196    /* If Limit check box for limit records returned is checked  */
197    if (limitCheckBox->checkState() == Qt::Checked) {
198       QString limit;
199       limit.setNum(limitSpinBox->value());
200       query += " LIMIT " + limit;
201    }
202
203    /* Set up the Header for the table */
204    QStringList headerlist = (QStringList()
205       << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type" 
206       << "Job Level" << "Job Files" << "Job Bytes" << "Job Status"  << "Purged" << "File Set" );
207    m_purgedIndex = headerlist.indexOf("Purged");
208    m_typeIndex = headerlist.indexOf("Job Type");
209    m_statusIndex = headerlist.indexOf("Job Status");
210    m_startIndex = headerlist.indexOf("Job Starttime");
211    m_filesIndex = headerlist.indexOf("Job Files");
212    m_bytesIndex = headerlist.indexOf("Job Bytes");
213
214    /* Initialize the QTableWidget */
215    m_checkCurrentWidget = false;
216    mp_tableWidget->clear();
217    m_checkCurrentWidget = true;
218    mp_tableWidget->setColumnCount(headerlist.size());
219    mp_tableWidget->setHorizontalHeaderLabels(headerlist);
220
221    if (mainWin->m_sqlDebug) {
222       Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
223    }
224    if (m_console->sql_cmd(query, results)) {
225       m_resultCount = results.count();
226
227       QTableWidgetItem* p_tableitem;
228       QString field;
229       QStringList fieldlist;
230       mp_tableWidget->setRowCount(results.size());
231
232       int row = 0;
233       /* Iterate through the record returned from the query */
234       foreach (resultline, results) {
235          fieldlist = resultline.split("\t");
236          int column = 0;
237          bool m_statusIndexDone = false;
238          QString statusCode("");
239          /* Iterate through fields in the record */
240          foreach (field, fieldlist) {
241             field = field.trimmed();  /* strip leading & trailing spaces */
242             if ((column == m_statusIndex) && (!m_statusIndexDone)){
243                m_statusIndexDone = true;
244                statusCode = field;
245             } else {
246                p_tableitem = new QTableWidgetItem(field,1);
247                p_tableitem->setFlags(0);
248                p_tableitem->setForeground(blackBrush);
249                mp_tableWidget->setItem(row, column, p_tableitem);
250                if (column == m_statusIndex)
251                   setStatusColor(p_tableitem, statusCode);
252                column++;
253             }
254          }
255          row++;
256       }
257    } 
258    /* Resize the columns */
259    mp_tableWidget->resizeColumnsToContents();
260    mp_tableWidget->resizeRowsToContents();
261    mp_tableWidget->verticalHeader()->hide();
262    if ((m_mediaName != "Any") && (m_resultCount == 0)){
263       /* for context sensitive searches, let the user know if there were no
264        * results */
265       QMessageBox::warning(this, tr("Bat"),
266           tr("The Jobs query returned no results.\n"
267          "Press OK to continue?"), QMessageBox::Ok );
268    }
269 }
270
271 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
272 {
273    QString greenchars("TCR");
274    QString redchars("BEf");
275    QString yellowchars("eDAFSMmsjdctp");
276    if (greenchars.contains(field, Qt::CaseSensitive)) {
277       item->setBackground(Qt::green);
278    } else if (redchars.contains(field, Qt::CaseSensitive)) {
279       item->setBackground(Qt::red);
280    } else if (yellowchars.contains(field, Qt::CaseSensitive)){ 
281       item->setBackground(Qt::yellow);
282    }
283 }
284
285 /*
286  * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
287  * The tree has been populated.
288  */
289 void JobList::PgSeltreeWidgetClicked()
290 {
291    if (!m_populated) {
292       populateTable();
293       m_populated=true;
294    }
295 }
296
297 /*
298  *  Virtual function override of pages function which is called when this page
299  *  is visible on the stack
300  */
301 void JobList::currentStackItem()
302 {
303    populateTable();
304    if (!m_populated) {
305       m_populated=true;
306    }
307 }
308
309 /*
310  * Virtual Function to return the name for the medialist tree widget
311  */
312 void JobList::treeWidgetName(QString &desc)
313 {
314    if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
315       desc = "JobList";
316    } else {
317       desc = "JobList ";
318       if (m_mediaName != "" ) {
319          desc += "of Volume " + m_mediaName;
320       }
321       if (m_clientName != "" ) {
322          desc += "of Client " + m_clientName;
323       }
324       if (m_jobName != "" ) {
325          desc += "of Job " + m_jobName;
326       }
327       if (m_filesetName != "" ) {
328          desc += "of fileset " + m_filesetName;
329       }
330    }
331 }
332
333 /*
334  * This functions much line tableItemChanged for trees like the page selector,
335  * but I will do much less here
336  */
337 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
338 {
339    if (m_checkCurrentWidget) {
340       int row = currentItem->row();
341       QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
342       m_currentJob = jobitem->text();
343
344       /* include purged action or not */
345       jobitem = mp_tableWidget->item(row, m_purgedIndex);
346       QString purged = jobitem->text();
347       mp_tableWidget->removeAction(actionPurgeFiles);
348       if (purged == "0") {
349          mp_tableWidget->addAction(actionPurgeFiles);
350       }
351       /* include restore from time and job action or not */
352       jobitem = mp_tableWidget->item(row, m_typeIndex);
353       QString type = jobitem->text();
354       mp_tableWidget->removeAction(actionRestoreFromJob);
355       mp_tableWidget->removeAction(actionRestoreFromTime);
356       if (type == "B") {
357          mp_tableWidget->addAction(actionRestoreFromJob);
358          mp_tableWidget->addAction(actionRestoreFromTime);
359       }
360       /* include cancel action or not */
361       jobitem = mp_tableWidget->item(row, m_statusIndex);
362       QString status = jobitem->text();
363       mp_tableWidget->removeAction(actionCancelJob);
364       if (status == "Running") {
365          mp_tableWidget->addAction(actionCancelJob);
366       }
367    }
368 }
369
370 /*
371  * Function to create connections for context sensitive menu for this and
372  * the page selector
373  */
374 void JobList::createConnections()
375 {
376    /* connect to the action specific to this pages class that shows up in the 
377     * page selector tree */
378    connect(actionRefreshJobList, SIGNAL(triggered()), this,
379                 SLOT(populateTable()));
380    connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
381    connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
382    /* for the tableItemChanged to maintain m_currentJob */
383    connect(mp_tableWidget, SIGNAL(
384            currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
385            this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
386
387    /* Do what is required for the local context sensitive menu */
388
389
390    /* setContextMenuPolicy is required */
391    mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
392
393    /* Add Actions */
394    mp_tableWidget->addAction(actionRefreshJobList);
395    mp_tableWidget->addAction(actionListJobid);
396    mp_tableWidget->addAction(actionListFilesOnJob);
397    mp_tableWidget->addAction(actionListJobMedia);
398    mp_tableWidget->addAction(actionListVolumes);
399    mp_tableWidget->addAction(actionDeleteJob);
400    mp_tableWidget->addAction(actionPurgeFiles);
401    mp_tableWidget->addAction(actionRestoreFromJob);
402    mp_tableWidget->addAction(actionRestoreFromTime);
403    mp_tableWidget->addAction(actionShowLogForJob);
404
405    /* Make Connections */
406    connect(actionListJobid, SIGNAL(triggered()), this,
407                 SLOT(consoleListJobid()));
408    connect(actionListFilesOnJob, SIGNAL(triggered()), this,
409                 SLOT(consoleListFilesOnJob()));
410    connect(actionListJobMedia, SIGNAL(triggered()), this,
411                 SLOT(consoleListJobMedia()));
412    connect(actionListVolumes, SIGNAL(triggered()), this,
413                 SLOT(consoleListVolumes()));
414    connect(actionDeleteJob, SIGNAL(triggered()), this,
415                 SLOT(consoleDeleteJob()));
416    connect(actionPurgeFiles, SIGNAL(triggered()), this,
417                 SLOT(consolePurgeFiles()));
418    connect(actionRestoreFromJob, SIGNAL(triggered()), this,
419                 SLOT(preRestoreFromJob()));
420    connect(actionRestoreFromTime, SIGNAL(triggered()), this,
421                 SLOT(preRestoreFromTime()));
422    connect(actionShowLogForJob, SIGNAL(triggered()), this,
423                 SLOT(showLogForJob()));
424    connect(actionCancelJob, SIGNAL(triggered()), this,
425                 SLOT(consoleCancelJob()));
426    connect(actionListJobTotals, SIGNAL(triggered()), this,
427                 SLOT(consoleListJobTotals()));
428
429    m_contextActions.append(actionRefreshJobList);
430    m_contextActions.append(actionListJobTotals);
431 }
432
433 /*
434  * Functions to respond to local context sensitive menu sending console commands
435  * If I could figure out how to make these one function passing a string, Yaaaaaa
436  */
437 void JobList::consoleListJobid()
438 {
439    QString cmd("list jobid=");
440    cmd += m_currentJob;
441    if (mainWin->m_longList) { cmd.prepend("l"); }
442    consoleCommand(cmd);
443 }
444 void JobList::consoleListFilesOnJob()
445 {
446    QString cmd("list files jobid=");
447    cmd += m_currentJob;
448    if (mainWin->m_longList) { cmd.prepend("l"); }
449    consoleCommand(cmd);
450 }
451 void JobList::consoleListJobMedia()
452 {
453    QString cmd("list jobmedia jobid=");
454    cmd += m_currentJob;
455    if (mainWin->m_longList) { cmd.prepend("l"); }
456    consoleCommand(cmd);
457 }
458 void JobList::consoleListVolumes()
459 {
460    QString cmd("list volumes jobid=");
461    cmd += m_currentJob;
462    if (mainWin->m_longList) { cmd.prepend("l"); }
463    consoleCommand(cmd);
464 }
465 void JobList::consoleListJobTotals()
466 {
467    QString cmd("list jobtotals");
468    cmd += m_currentJob;
469    if (mainWin->m_longList) { cmd.prepend("l"); }
470    consoleCommand(cmd);
471 }
472 void JobList::consoleDeleteJob()
473 {
474    if (QMessageBox::warning(this, tr("Bat"),
475       tr("Are you sure you want to delete??  !!!.\n"
476 "This delete command is used to delete a Job record and all associated catalog"
477 " records that were created. This command operates only on the Catalog"
478 " database and has no effect on the actual data written to a Volume. This"
479 " command can be dangerous and we strongly recommend that you do not use"
480 " it unless you know what you are doing.  The Job and all its associated"
481 " records (File and JobMedia) will be deleted from the catalog."
482       "Press OK to proceed with delete operation.?"),
483       QMessageBox::Ok | QMessageBox::Cancel)
484       == QMessageBox::Cancel) { return; }
485
486    QString cmd("delete job jobid=");
487    cmd += m_currentJob;
488    consoleCommand(cmd);
489 }
490 void JobList::consolePurgeFiles()
491 {
492    if (QMessageBox::warning(this, tr("Bat"),
493       tr("Are you sure you want to purge ??  !!!.\n"
494 "The Purge command will delete associated Catalog database records from Jobs and"
495 " Volumes without considering the retention period. Purge  works only on the"
496 " Catalog database and does not affect data written to Volumes. This command can"
497 " be dangerous because you can delete catalog records associated with current"
498 " backups of files, and we recommend that you do not use it unless you know what"
499 " you are doing.\n"
500       "Press OK to proceed with the purge operation?"),
501       QMessageBox::Ok | QMessageBox::Cancel)
502       == QMessageBox::Cancel) { return; }
503
504    QString cmd("purge files jobid=");
505    cmd += m_currentJob;
506    consoleCommand(cmd);
507 }
508
509 /*
510  * Subroutine to call preRestore to restore from a select job
511  */
512 void JobList::preRestoreFromJob()
513 {
514    new prerestorePage(m_currentJob, R_JOBIDLIST);
515 }
516
517 /*
518  * Subroutine to call preRestore to restore from a select job
519  */
520 void JobList::preRestoreFromTime()
521 {
522    new prerestorePage(m_currentJob, R_JOBDATETIME);
523 }
524
525 /*
526  * Subroutine to call class to show the log in the database from that job
527  */
528 void JobList::showLogForJob()
529 {
530    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
531    new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
532 }
533
534 /*
535  * Cancel a running job
536  */
537 void JobList::consoleCancelJob()
538 {
539    QString cmd("cancel jobid=");
540    cmd += m_currentJob;
541    consoleCommand(cmd);
542 }
543
544 /*
545  * Graph this table
546  */
547 void JobList::graphTable()
548 {
549    JobPlotPass pass;
550    pass.recordLimitCheck = limitCheckBox->checkState();
551    pass.daysLimitCheck = daysCheckBox->checkState();
552    pass.recordLimitSpin = limitSpinBox->value();
553    pass.daysLimitSpin = daysSpinBox->value();
554    pass.jobCombo = jobComboBox->currentText();
555    pass.clientCombo = clientComboBox->currentText();
556    pass.volumeCombo = volumeComboBox->currentText();
557    pass.fileSetCombo = fileSetComboBox->currentText();
558    pass.purgedCombo = purgedComboBox->currentText();
559    pass.levelCombo = levelComboBox->currentText();
560    pass.statusCombo = statusComboBox->currentText();
561    pass.use = true;
562    QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
563    new JobPlot(pageSelectorTreeWidgetItem, pass);
564 }