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