2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2009 Free Software Foundation Europe e.V.
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
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.
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
23 Bacula® is a registered trademark of Kern Sibbald.
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.
31 * Dirk Bartley, March 2007
35 #include <QAbstractEventDispatcher>
36 #include <QTableWidgetItem>
40 #include "joblog/joblog.h"
42 #include "jobgraphs/jobplot.h"
44 #include "util/fmtwidgetitem.h"
45 #include "util/comboutil.h"
48 * Constructor for the class
50 JobList::JobList(const QString &mediaName, const QString &clientName,
51 const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
54 m_name = "Jobs Run"; /* treeWidgetName has a virtual override in this class */
55 m_mediaName = mediaName;
56 m_clientName = clientName;
58 m_filesetName = filesetName;
59 m_filesetName = filesetName;
60 pgInitialize("", parentTreeWidgetItem);
61 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
62 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
67 if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != "")) {
70 m_checkCurrentWidget = true;
72 /* Set Defaults for check and spin for limits */
73 limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
74 limitSpinBox->setValue(mainWin->m_recordLimitVal);
75 daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
76 daysSpinBox->setValue(mainWin->m_daysLimitVal);
78 QGridLayout *gridLayout = new QGridLayout(this);
79 gridLayout->setSpacing(6);
80 gridLayout->setMargin(9);
81 gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
83 m_splitter = new QSplitter(Qt::Vertical, this);
84 QScrollArea *area = new QScrollArea();
85 area->setObjectName(QString::fromUtf8("area"));
86 area->setWidget(frame);
87 area->setWidgetResizable(true);
88 m_splitter->addWidget(area);
89 m_splitter->addWidget(mp_tableWidget);
91 gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
94 if (m_closeable) { dockPage(); }
98 * Write the m_splitter settings in the destructor
106 * The Meat of the class.
107 * This function will populate the QTableWidget, mp_tablewidget, with
108 * QTableWidgetItems representing the results of a query for what jobs exist on
109 * the media name passed from the constructor stored in m_mediaName.
111 void JobList::populateTable()
113 /* Can't do this in constructor because not neccesarily conected in constructor */
114 prepareFilterWidgets();
117 Freeze frz(*mp_tableWidget); /* disable updating*/
121 fillQueryString(query);
123 /* Set up the Header for the table */
124 QStringList headerlist = (QStringList()
125 << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime")
126 << tr("Job Type") << tr("Job Level") << tr("Job Files")
127 << tr("Job Bytes") << tr("Job Status") << tr("Purged") << tr("File Set")
128 << tr("Pool Name") << tr("First Volume") << tr("VolCount"));
130 m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
131 m_purgedIndex = headerlist.indexOf(tr("Purged"));
132 m_typeIndex = headerlist.indexOf(tr("Job Type"));
133 m_statusIndex = headerlist.indexOf(tr("Job Status"));
134 m_startIndex = headerlist.indexOf(tr("Job Starttime"));
135 m_filesIndex = headerlist.indexOf(tr("Job Files"));
136 m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
138 /* Initialize the QTableWidget */
139 m_checkCurrentWidget = false;
140 mp_tableWidget->clear();
141 m_checkCurrentWidget = true;
142 mp_tableWidget->setColumnCount(headerlist.size());
143 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
144 mp_tableWidget->horizontalHeader()->setHighlightSections(false);
145 mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
146 mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
148 if (mainWin->m_sqlDebug) {
149 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
153 if (m_console->sql_cmd(query, results)) {
154 m_resultCount = results.count();
156 QStringList fieldlist;
157 mp_tableWidget->setRowCount(results.size());
160 /* Iterate through the record returned from the query */
162 foreach (resultline, results) {
163 fieldlist = resultline.split("\t");
164 if (fieldlist.size() < 13)
165 continue; /* some fields missing, ignore row */
167 TableItemFormatter jobitem(*mp_tableWidget, row);
169 /* Iterate through fields in the record */
170 QStringListIterator fld(fieldlist);
174 jobitem.setNumericFld(col++, fld.next());
177 jobitem.setTextFld(col++, fld.next());
180 jobitem.setTextFld(col++, fld.next());
183 jobitem.setTextFld(col++, fld.next(), true);
186 jobitem.setJobTypeFld(col++, fld.next());
189 jobitem.setJobLevelFld(col++, fld.next());
192 jobitem.setNumericFld(col++, fld.next());
195 jobitem.setBytesFld(col++, fld.next());
198 jobitem.setJobStatusFld(col++, fld.next());
201 jobitem.setBoolFld(col++, fld.next());
204 jobitem.setTextFld(col++, fld.next());
207 jobitem.setTextFld(col++, fld.next());
210 jobitem.setTextFld(col++, fld.next());
213 jobitem.setNumericFld(col++, fld.next());
217 /* set default sorting */
218 mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
219 mp_tableWidget->setSortingEnabled(true);
221 /* Resize the columns */
222 mp_tableWidget->resizeColumnsToContents();
223 mp_tableWidget->resizeRowsToContents();
224 mp_tableWidget->verticalHeader()->hide();
225 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
226 /* for context sensitive searches, let the user know if there were no
228 QMessageBox::warning(this, "Bat",
229 tr("The Jobs query returned no results.\n"
230 "Press OK to continue?"), QMessageBox::Ok );
234 mp_tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
237 void JobList::prepareFilterWidgets()
240 clientComboBox->addItem(tr("Any"));
241 clientComboBox->addItems(m_console->client_list);
242 comboSel(clientComboBox, m_clientName);
244 QStringList volumeList;
245 getVolumeList(volumeList);
246 volumeComboBox->addItem(tr("Any"));
247 volumeComboBox->addItems(volumeList);
248 comboSel(volumeComboBox, m_mediaName);
250 jobComboBox->addItem(tr("Any"));
251 jobComboBox->addItems(m_console->job_list);
252 comboSel(jobComboBox, m_jobName);
254 levelComboFill(levelComboBox);
256 boolComboFill(purgedComboBox);
258 fileSetComboBox->addItem(tr("Any"));
259 fileSetComboBox->addItems(m_console->fileset_list);
260 comboSel(fileSetComboBox, m_filesetName);
262 poolComboBox->addItem(tr("Any"));
263 poolComboBox->addItems(m_console->pool_list);
265 jobStatusComboFill(statusComboBox);
269 void JobList::fillQueryString(QString &query)
272 int volumeIndex = volumeComboBox->currentIndex();
273 if (volumeIndex != -1)
274 m_mediaName = volumeComboBox->itemText(volumeIndex);
275 QString distinct = "";
276 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
277 query += "SELECT " + distinct + "Job.JobId AS JobId, Job.Name AS JobName, "
278 " Client.Name AS Client,"
279 " Job.Starttime AS JobStart, Job.Type AS JobType,"
280 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
281 " Job.JobBytes AS Bytes, Job.JobStatus AS Status,"
282 " Job.PurgedFiles AS Purged, FileSet.FileSet,"
283 " Pool.Name AS Pool,"
284 " (SELECT Media.VolumeName FROM JobMedia JOIN Media ON JobMedia.MediaId=Media.MediaId WHERE JobMedia.JobId=Job.JobId ORDER BY JobMediaId LIMIT 1) AS FirstVolume,"
285 " (SELECT count(DISTINCT MediaId) FROM JobMedia WHERE JobMedia.JobId=Job.JobId) AS Volumes"
287 " JOIN Client ON (Client.ClientId=Job.ClientId)"
288 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) "
289 " LEFT OUTER JOIN Pool ON Job.PoolId = Pool.PoolId ";
290 QStringList conditions;
291 if (m_mediaName != tr("Any")) {
292 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
293 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
294 conditions.append("Media.VolumeName='" + m_mediaName + "'");
297 comboCond(conditions, clientComboBox, "Client.Name");
298 comboCond(conditions, jobComboBox, "Job.Name");
299 levelComboCond(conditions, levelComboBox, "Job.Level");
300 jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
301 boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
302 comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
303 comboCond(conditions, poolComboBox, "Pool.Name");
305 /* If Limit check box For limit by days is checked */
306 if (daysCheckBox->checkState() == Qt::Checked) {
307 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
308 QString since = stamp.toString(Qt::ISODate);
309 conditions.append("Job.Starttime > '" + since + "'");
311 if (filterCopyCheckBox->checkState() == Qt::Checked) {
312 conditions.append("Job.Type != 'c'" );
314 if (filterMigrationCheckBox->checkState() == Qt::Checked) {
315 conditions.append("Job.Type != 'g'" );
318 foreach (QString condition, conditions) {
320 query += " WHERE " + condition;
323 query += " AND " + condition;
327 query += " ORDER BY Job.JobId DESC";
328 /* If Limit check box for limit records returned is checked */
329 if (limitCheckBox->checkState() == Qt::Checked) {
331 limit.setNum(limitSpinBox->value());
332 query += " LIMIT " + limit;
337 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
338 * The tree has been populated.
340 void JobList::PgSeltreeWidgetClicked()
344 /* Lets make sure the splitter is not all the way to size index 0 == 0 */
345 QList<int> sizes = m_splitter->sizes();
347 int frameMax = frame->maximumHeight();
349 foreach(int size, sizes) { sizeSum += size; }
350 int tabHeight = mainWin->tabWidget->geometry().height();
352 sizes[1] = tabHeight - frameMax;
353 m_splitter->setSizes(sizes);
356 if (!isOnceDocked()) {
362 * Virtual function override of pages function which is called when this page
363 * is visible on the stack
365 void JobList::currentStackItem()
367 /* if (!m_populated) populate every time user comes back to this object */
372 * Virtual Function to return the name for the medialist tree widget
374 void JobList::treeWidgetName(QString &desc)
376 if (m_mediaName != "" ) {
377 desc = tr("Jobs Run on Volume %1").arg(m_mediaName);
378 } else if (m_clientName != "" ) {
379 desc = tr("Jobs Run from Client %1").arg(m_clientName);
380 } else if (m_jobName != "" ) {
381 desc = tr("Jobs Run of Job %1").arg(m_jobName);
382 } else if (m_filesetName != "" ) {
383 desc = tr("Jobs Run with fileset %1").arg(m_filesetName);
385 desc = tr("Jobs Run");
390 * Function to create connections for context sensitive menu for this and
393 void JobList::createConnections()
395 /* connect to the action specific to this pages class that shows up in the
396 * page selector tree */
397 connect(actionRefreshJobList, SIGNAL(triggered()), this, SLOT(populateTable()));
398 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
400 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
402 graphButton->setEnabled(false);
403 graphButton->setVisible(false);
405 /* for the selectionChanged to maintain m_currentJob and a delete selection */
406 connect(mp_tableWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
407 connect(mp_tableWidget, SIGNAL(itemDoubleClicked(QTableWidgetItem*)), this, SLOT(showInfoForJob()));
409 /* Do what is required for the local context sensitive menu */
412 /* setContextMenuPolicy is required */
413 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
415 connect(actionListFilesOnJob, SIGNAL(triggered()), this, SLOT(consoleListFilesOnJob()));
416 connect(actionListJobMedia, SIGNAL(triggered()), this, SLOT(consoleListJobMedia()));
417 connect(actionDeleteJob, SIGNAL(triggered()), this, SLOT(consoleDeleteJob()));
418 connect(actionPurgeFiles, SIGNAL(triggered()), this, SLOT(consolePurgeFiles()));
419 connect(actionRestoreFromJob, SIGNAL(triggered()), this, SLOT(preRestoreFromJob()));
420 connect(actionRestoreFromTime, SIGNAL(triggered()), this, SLOT(preRestoreFromTime()));
421 connect(actionShowLogForJob, SIGNAL(triggered()), this, SLOT(showLogForJob()));
422 connect(actionShowInfoForJob, SIGNAL(triggered()), this, SLOT(showInfoForJob()));
423 connect(actionCancelJob, SIGNAL(triggered()), this, SLOT(consoleCancelJob()));
424 connect(actionListJobTotals, SIGNAL(triggered()), this, SLOT(consoleListJobTotals()));
425 connect(m_splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(splitterMoved(int, int)));
427 m_contextActions.append(actionRefreshJobList);
428 m_contextActions.append(actionListJobTotals);
432 * Functions to respond to local context sensitive menu sending console commands
433 * If I could figure out how to make these one function passing a string, Yaaaaaa
435 void JobList::consoleListFilesOnJob()
437 QString cmd("list files jobid=");
439 if (mainWin->m_longList) { cmd.prepend("l"); }
442 void JobList::consoleListJobMedia()
444 QString cmd("list jobmedia jobid=");
446 if (mainWin->m_longList) { cmd.prepend("l"); }
450 void JobList::consoleListJobTotals()
452 QString cmd("list jobtotals");
453 if (mainWin->m_longList) { cmd.prepend("l"); }
456 void JobList::consoleDeleteJob()
458 if (QMessageBox::warning(this, "Bat",
459 tr("Are you sure you want to delete?? !!!.\n"
460 "This delete command is used to delete a Job record and all associated catalog"
461 " records that were created. This command operates only on the Catalog"
462 " database and has no effect on the actual data written to a Volume. This"
463 " command can be dangerous and we strongly recommend that you do not use"
464 " it unless you know what you are doing. The Job and all its associated"
465 " records (File and JobMedia) will be deleted from the catalog."
466 "Press OK to proceed with delete operation.?"),
467 QMessageBox::Ok | QMessageBox::Cancel)
468 == QMessageBox::Cancel) { return; }
470 QString cmd("delete job jobid=");
471 cmd += m_selectedJobs;
472 consoleCommand(cmd, false);
475 void JobList::consolePurgeFiles()
477 if (QMessageBox::warning(this, "Bat",
478 tr("Are you sure you want to purge ?? !!!.\n"
479 "The Purge command will delete associated Catalog database records from Jobs and"
480 " Volumes without considering the retention period. Purge works only on the"
481 " Catalog database and does not affect data written to Volumes. This command can"
482 " be dangerous because you can delete catalog records associated with current"
483 " backups of files, and we recommend that you do not use it unless you know what"
485 "Press OK to proceed with the purge operation?"),
486 QMessageBox::Ok | QMessageBox::Cancel)
487 == QMessageBox::Cancel) { return; }
489 m_console->m_warningPrevent = true;
490 foreach(QString job, m_selectedJobsList) {
491 QString cmd("purge files jobid=");
493 consoleCommand(cmd, false);
495 m_console->m_warningPrevent = false;
500 * Subroutine to call preRestore to restore from a select job
502 void JobList::preRestoreFromJob()
504 new prerestorePage(m_currentJob, R_JOBIDLIST);
508 * Subroutine to call preRestore to restore from a select job
510 void JobList::preRestoreFromTime()
512 new prerestorePage(m_currentJob, R_JOBDATETIME);
516 * Subroutine to call class to show the log in the database from that job
518 void JobList::showLogForJob()
520 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
521 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
525 * Subroutine to call class to show the log in the database from that job
527 void JobList::showInfoForJob(QTableWidgetItem * /*item*/)
529 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
530 new Job(m_currentJob, pageSelectorTreeWidgetItem);
534 * Cancel a running job
536 void JobList::consoleCancelJob()
538 QString cmd("cancel jobid=");
546 void JobList::graphTable()
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();
562 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
563 new JobPlot(pageSelectorTreeWidgetItem, pass);
568 * Save user settings associated with this page
570 void JobList::writeSettings()
572 QSettings settings(m_console->m_dir->name(), "bat");
573 settings.beginGroup(m_groupText);
574 settings.setValue(m_splitText, m_splitter->saveState());
575 settings.setValue("FilterCopyCheckState", filterCopyCheckBox->checkState());
576 settings.setValue("FilterMigrationCheckState", filterMigrationCheckBox->checkState());
581 * Read and restore user settings associated with this page
583 void JobList::readSettings()
585 m_groupText = "JobListPage";
586 m_splitText = "splitterSizes_2";
587 QSettings settings(m_console->m_dir->name(), "bat");
588 settings.beginGroup(m_groupText);
589 if (settings.contains(m_splitText)) {
590 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
592 filterCopyCheckBox->setCheckState((Qt::CheckState)settings.value("FilterCopyCheckState").toInt());
593 filterMigrationCheckBox->setCheckState((Qt::CheckState)settings.value("FilterMigrationCheckState").toInt());
598 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
600 void JobList::selectionChanged()
603 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
604 foreach (QTableWidgetItem *sitem, sitems) {
605 int row = sitem->row();
606 if (!rowList.contains(row)) {
612 m_selectedJobsList.clear();
614 foreach(int row, rowList) {
615 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
616 if (!first) m_selectedJobs.append(",");
618 m_selectedJobs.append(sitem->text());
619 m_selectedJobsList.append(sitem->text());
621 m_selectedJobsCount = rowList.count();
622 if (m_selectedJobsCount > 1) {
623 QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
624 actionDeleteJob->setText(text);
625 text = QString( tr("Purge Files from list of %1 Jobs")).arg(m_selectedJobsCount);
626 actionPurgeFiles->setText(text);
628 actionDeleteJob->setText(tr("Delete Single Job"));
629 actionPurgeFiles->setText(tr("Purge Files from single job"));
632 /* remove all actions */
633 foreach(QAction* mediaAction, mp_tableWidget->actions()) {
634 mp_tableWidget->removeAction(mediaAction);
638 mp_tableWidget->addAction(actionRefreshJobList);
639 if (m_selectedJobsCount == 1) {
640 mp_tableWidget->addAction(actionListFilesOnJob);
641 mp_tableWidget->addAction(actionListJobMedia);
642 mp_tableWidget->addAction(actionRestoreFromJob);
643 mp_tableWidget->addAction(actionRestoreFromTime);
644 mp_tableWidget->addAction(actionShowLogForJob);
645 mp_tableWidget->addAction(actionShowInfoForJob);
647 if (m_selectedJobsCount >= 1) {
648 mp_tableWidget->addAction(actionDeleteJob);
649 mp_tableWidget->addAction(actionPurgeFiles);
652 /* Make Connections */
653 if (m_checkCurrentWidget) {
654 int row = mp_tableWidget->currentRow();
655 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
656 m_currentJob = jobitem->text();
658 /* include purged action or not */
659 jobitem = mp_tableWidget->item(row, m_purgedIndex);
660 QString purged = jobitem->text();
661 /* mp_tableWidget->removeAction(actionPurgeFiles);
662 if (purged == tr("No") ) {
663 mp_tableWidget->addAction(actionPurgeFiles);
665 /* include restore from time and job action or not */
666 jobitem = mp_tableWidget->item(row, m_typeIndex);
667 QString type = jobitem->text();
668 if (m_selectedJobsCount == 1) {
669 mp_tableWidget->removeAction(actionRestoreFromJob);
670 mp_tableWidget->removeAction(actionRestoreFromTime);
671 if (type == tr("Backup")) {
672 mp_tableWidget->addAction(actionRestoreFromJob);
673 mp_tableWidget->addAction(actionRestoreFromTime);
676 /* include cancel action or not */
677 jobitem = mp_tableWidget->item(row, m_statusIndex);
678 QString status = jobitem->text();
679 mp_tableWidget->removeAction(actionCancelJob);
680 if (status == tr("Running") || status == tr("Created, not yet running")) {
681 mp_tableWidget->addAction(actionCancelJob);
687 * Function to prevent the splitter from making index 0 of the size larger than it
690 void JobList::splitterMoved(int /*pos*/, int /*index*/)
692 int frameMax = frame->maximumHeight();
693 QList<int> sizes = m_splitter->sizes();
695 foreach(int size, sizes) { sizeSum += size; }
696 if (sizes[0] > frameMax) {
698 sizes[1] = sizeSum - frameMax;
699 m_splitter->setSizes(sizes);