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>
39 #include "joblog/joblog.h"
41 #include "jobgraphs/jobplot.h"
43 #include "util/fmtwidgetitem.h"
44 #include "util/comboutil.h"
47 * Constructor for the class
49 JobList::JobList(const QString &mediaName, const QString &clientName,
50 const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
53 m_name = ""; /* treeWidgetName has a virtual override in this class */
54 m_mediaName = mediaName;
55 m_clientName = clientName;
57 m_filesetName = filesetName;
58 m_filesetName = filesetName;
59 pgInitialize("", parentTreeWidgetItem);
60 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
61 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
66 if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
68 m_checkCurrentWidget = true;
71 /* Set Defaults for check and spin for limits */
72 limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
73 limitSpinBox->setValue(mainWin->m_recordLimitVal);
74 daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
75 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(mp_tableWidget);
89 m_splitter->addWidget(area);
91 gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
96 * Write the m_splitter settings in the destructor
104 * The Meat of the class.
105 * This function will populate the QTableWidget, mp_tablewidget, with
106 * QTableWidgetItems representing the results of a query for what jobs exist on
107 * the media name passed from the constructor stored in m_mediaName.
109 void JobList::populateTable()
111 /* Can't do this in constructor because not neccesarily conected in constructor */
112 prepareFilterWidgets();
115 Freeze frz(*mp_tableWidget); /* disable updating*/
119 fillQueryString(query);
121 /* Set up the Header for the table */
122 QStringList headerlist = (QStringList()
123 << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime")
124 << tr("Job Type") << tr("Job Level") << tr("Job Files")
125 << tr("Job Bytes") << tr("Job Status") << tr("Purged") << tr("File Set")
126 << tr("Pool Name") << tr("First Volume") << tr("VolCount"));
128 m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
129 m_purgedIndex = headerlist.indexOf(tr("Purged"));
130 m_typeIndex = headerlist.indexOf(tr("Job Type"));
131 m_statusIndex = headerlist.indexOf(tr("Job Status"));
132 m_startIndex = headerlist.indexOf(tr("Job Starttime"));
133 m_filesIndex = headerlist.indexOf(tr("Job Files"));
134 m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
136 /* Initialize the QTableWidget */
137 m_checkCurrentWidget = false;
138 mp_tableWidget->clear();
139 m_checkCurrentWidget = true;
140 mp_tableWidget->setColumnCount(headerlist.size());
141 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
142 mp_tableWidget->horizontalHeader()->setHighlightSections(false);
143 mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
144 mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
146 if (mainWin->m_sqlDebug) {
147 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
151 if (m_console->sql_cmd(query, results)) {
152 m_resultCount = results.count();
154 QStringList fieldlist;
155 mp_tableWidget->setRowCount(results.size());
158 /* Iterate through the record returned from the query */
160 foreach (resultline, results) {
161 fieldlist = resultline.split("\t");
162 if (fieldlist.size() < 13)
163 continue; /* some fields missing, ignore row */
165 TableItemFormatter jobitem(*mp_tableWidget, row);
167 /* Iterate through fields in the record */
168 QStringListIterator fld(fieldlist);
172 jobitem.setNumericFld(col++, fld.next());
175 jobitem.setTextFld(col++, fld.next());
178 jobitem.setTextFld(col++, fld.next());
181 jobitem.setTextFld(col++, fld.next(), true);
184 jobitem.setJobTypeFld(col++, fld.next());
187 jobitem.setJobLevelFld(col++, fld.next());
190 jobitem.setNumericFld(col++, fld.next());
193 jobitem.setBytesFld(col++, fld.next());
196 jobitem.setJobStatusFld(col++, fld.next());
199 jobitem.setBoolFld(col++, fld.next());
202 jobitem.setTextFld(col++, fld.next());
205 jobitem.setTextFld(col++, fld.next());
208 jobitem.setTextFld(col++, fld.next());
211 jobitem.setNumericFld(col++, fld.next());
215 /* set default sorting */
216 mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
217 mp_tableWidget->setSortingEnabled(true);
219 /* Resize the columns */
220 mp_tableWidget->resizeColumnsToContents();
221 mp_tableWidget->resizeRowsToContents();
222 mp_tableWidget->verticalHeader()->hide();
223 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
224 /* for context sensitive searches, let the user know if there were no
226 QMessageBox::warning(this, "Bat",
227 tr("The Jobs query returned no results.\n"
228 "Press OK to continue?"), QMessageBox::Ok );
232 int rcnt = mp_tableWidget->rowCount();
233 int ccnt = mp_tableWidget->columnCount();
234 for(int r=0; r < rcnt; r++) {
235 for(int c=0; c < ccnt; c++) {
236 QTableWidgetItem* item = mp_tableWidget->item(r, c);
238 item->setFlags(Qt::ItemFlags(item->flags() & (~Qt::ItemIsEditable)));
244 void JobList::prepareFilterWidgets()
247 clientComboBox->addItem(tr("Any"));
248 clientComboBox->addItems(m_console->client_list);
249 comboSel(clientComboBox, m_clientName);
251 QStringList volumeList;
252 getVolumeList(volumeList);
253 volumeComboBox->addItem(tr("Any"));
254 volumeComboBox->addItems(volumeList);
255 comboSel(volumeComboBox, m_mediaName);
257 jobComboBox->addItem(tr("Any"));
258 jobComboBox->addItems(m_console->job_list);
259 comboSel(jobComboBox, m_jobName);
261 levelComboFill(levelComboBox);
263 boolComboFill(purgedComboBox);
265 fileSetComboBox->addItem(tr("Any"));
266 fileSetComboBox->addItems(m_console->fileset_list);
267 comboSel(fileSetComboBox, m_filesetName);
269 poolComboBox->addItem(tr("Any"));
270 poolComboBox->addItems(m_console->pool_list);
272 jobStatusComboFill(statusComboBox);
276 void JobList::fillQueryString(QString &query)
279 int volumeIndex = volumeComboBox->currentIndex();
280 if (volumeIndex != -1)
281 m_mediaName = volumeComboBox->itemText(volumeIndex);
282 QString distinct = "";
283 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
284 query += "SELECT " + distinct + "Job.JobId AS JobId, Job.Name AS JobName, "
285 " Client.Name AS Client,"
286 " Job.Starttime AS JobStart, Job.Type AS JobType,"
287 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
288 " Job.JobBytes AS Bytes, Job.JobStatus AS Status,"
289 " Job.PurgedFiles AS Purged, FileSet.FileSet,"
290 " Pool.Name AS Pool,"
291 " (SELECT Media.VolumeName FROM JobMedia JOIN Media ON JobMedia.MediaId=Media.MediaId WHERE JobMedia.JobId=Job.JobId ORDER BY JobMediaId LIMIT 1) AS FirdtVolume,"
292 " (SELECT count(DISTINCT MediaId) FROM JobMedia WHERE JobMedia.JobId=Job.JobId) AS Volumes"
294 " JOIN Client ON (Client.ClientId=Job.ClientId)"
295 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) "
296 " LEFT OUTER JOIN Pool ON Job.PoolId = Pool.PoolId ";
297 QStringList conditions;
298 if (m_mediaName != tr("Any")) {
299 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
300 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
301 conditions.append("Media.VolumeName='" + m_mediaName + "'");
304 comboCond(conditions, clientComboBox, "Client.Name");
305 comboCond(conditions, jobComboBox, "Job.Name");
306 levelComboCond(conditions, levelComboBox, "Job.Level");
307 jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
308 boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
309 comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
310 comboCond(conditions, poolComboBox, "Pool.Name");
312 /* If Limit check box For limit by days is checked */
313 if (daysCheckBox->checkState() == Qt::Checked) {
314 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
315 QString since = stamp.toString(Qt::ISODate);
316 conditions.append("Job.Starttime>'" + since + "'");
319 foreach (QString condition, conditions) {
321 query += " WHERE " + condition;
324 query += " AND " + condition;
328 query += " ORDER BY Job.JobId DESC";
329 /* If Limit check box for limit records returned is checked */
330 if (limitCheckBox->checkState() == Qt::Checked) {
332 limit.setNum(limitSpinBox->value());
333 query += " LIMIT " + limit;
338 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
339 * The tree has been populated.
341 void JobList::PgSeltreeWidgetClicked()
349 * Virtual function override of pages function which is called when this page
350 * is visible on the stack
352 void JobList::currentStackItem()
354 /* if (!m_populated) populate every time user comes back to this object */
359 * Virtual Function to return the name for the medialist tree widget
361 void JobList::treeWidgetName(QString &desc)
363 if (m_mediaName != "" ) {
364 desc = tr("JobList of Volume %1").arg(m_mediaName);
365 } else if (m_clientName != "" ) {
366 desc = tr("JobList of Client %1").arg(m_clientName);
367 } else if (m_jobName != "" ) {
368 desc = tr("JobList of Job %1").arg(m_jobName);
369 } else if (m_filesetName != "" ) {
370 desc = tr("JobList of fileset %1").arg(m_filesetName);
372 desc = tr("JobList");
377 * Function to create connections for context sensitive menu for this and
380 void JobList::createConnections()
382 /* connect to the action specific to this pages class that shows up in the
383 * page selector tree */
384 connect(actionRefreshJobList, SIGNAL(triggered()), this,
385 SLOT(populateTable()));
386 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
388 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
390 graphButton->setEnabled(false);
391 graphButton->setVisible(false);
393 /* for the selectionChanged to maintain m_currentJob and a delete selection */
394 connect(mp_tableWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
396 /* Do what is required for the local context sensitive menu */
399 /* setContextMenuPolicy is required */
400 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
402 connect(actionListJobid, SIGNAL(triggered()), this,
403 SLOT(consoleListJobid()));
404 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
405 SLOT(consoleListFilesOnJob()));
406 connect(actionListJobMedia, SIGNAL(triggered()), this,
407 SLOT(consoleListJobMedia()));
408 connect(actionListVolumes, SIGNAL(triggered()), this,
409 SLOT(consoleListVolumes()));
410 connect(actionDeleteJob, SIGNAL(triggered()), this,
411 SLOT(consoleDeleteJob()));
412 connect(actionPurgeFiles, SIGNAL(triggered()), this,
413 SLOT(consolePurgeFiles()));
414 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
415 SLOT(preRestoreFromJob()));
416 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
417 SLOT(preRestoreFromTime()));
418 connect(actionShowLogForJob, SIGNAL(triggered()), this,
419 SLOT(showLogForJob()));
420 connect(actionCancelJob, SIGNAL(triggered()), this,
421 SLOT(consoleCancelJob()));
422 connect(actionListJobTotals, SIGNAL(triggered()), this,
423 SLOT(consoleListJobTotals()));
425 m_contextActions.append(actionRefreshJobList);
426 m_contextActions.append(actionListJobTotals);
430 * Functions to respond to local context sensitive menu sending console commands
431 * If I could figure out how to make these one function passing a string, Yaaaaaa
433 void JobList::consoleListJobid()
435 QString cmd("list jobid=");
437 if (mainWin->m_longList) { cmd.prepend("l"); }
440 void JobList::consoleListFilesOnJob()
442 QString cmd("list files jobid=");
444 if (mainWin->m_longList) { cmd.prepend("l"); }
447 void JobList::consoleListJobMedia()
449 QString cmd("list jobmedia jobid=");
451 if (mainWin->m_longList) { cmd.prepend("l"); }
454 void JobList::consoleListVolumes()
456 QString cmd("list volumes jobid=");
458 if (mainWin->m_longList) { cmd.prepend("l"); }
461 void JobList::consoleListJobTotals()
463 QString cmd("list jobtotals");
464 if (mainWin->m_longList) { cmd.prepend("l"); }
467 void JobList::consoleDeleteJob()
469 if (QMessageBox::warning(this, "Bat",
470 tr("Are you sure you want to delete?? !!!.\n"
471 "This delete command is used to delete a Job record and all associated catalog"
472 " records that were created. This command operates only on the Catalog"
473 " database and has no effect on the actual data written to a Volume. This"
474 " command can be dangerous and we strongly recommend that you do not use"
475 " it unless you know what you are doing. The Job and all its associated"
476 " records (File and JobMedia) will be deleted from the catalog."
477 "Press OK to proceed with delete operation.?"),
478 QMessageBox::Ok | QMessageBox::Cancel)
479 == QMessageBox::Cancel) { return; }
481 QString cmd("delete job jobid=");
482 cmd += m_selectedJobs;
485 void JobList::consolePurgeFiles()
487 if (QMessageBox::warning(this, "Bat",
488 tr("Are you sure you want to purge ?? !!!.\n"
489 "The Purge command will delete associated Catalog database records from Jobs and"
490 " Volumes without considering the retention period. Purge works only on the"
491 " Catalog database and does not affect data written to Volumes. This command can"
492 " be dangerous because you can delete catalog records associated with current"
493 " backups of files, and we recommend that you do not use it unless you know what"
495 "Press OK to proceed with the purge operation?"),
496 QMessageBox::Ok | QMessageBox::Cancel)
497 == QMessageBox::Cancel) { return; }
499 foreach(QString job, m_selectedJobsList) {
500 QString cmd("purge files jobid=");
507 * Subroutine to call preRestore to restore from a select job
509 void JobList::preRestoreFromJob()
511 new prerestorePage(m_currentJob, R_JOBIDLIST);
515 * Subroutine to call preRestore to restore from a select job
517 void JobList::preRestoreFromTime()
519 new prerestorePage(m_currentJob, R_JOBDATETIME);
523 * Subroutine to call class to show the log in the database from that job
525 void JobList::showLogForJob()
527 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
528 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
532 * Cancel a running job
534 void JobList::consoleCancelJob()
536 QString cmd("cancel jobid=");
544 void JobList::graphTable()
548 pass.recordLimitCheck = limitCheckBox->checkState();
549 pass.daysLimitCheck = daysCheckBox->checkState();
550 pass.recordLimitSpin = limitSpinBox->value();
551 pass.daysLimitSpin = daysSpinBox->value();
552 pass.jobCombo = jobComboBox->currentText();
553 pass.clientCombo = clientComboBox->currentText();
554 pass.volumeCombo = volumeComboBox->currentText();
555 pass.fileSetCombo = fileSetComboBox->currentText();
556 pass.purgedCombo = purgedComboBox->currentText();
557 pass.levelCombo = levelComboBox->currentText();
558 pass.statusCombo = statusComboBox->currentText();
560 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
561 new JobPlot(pageSelectorTreeWidgetItem, pass);
566 * Save user settings associated with this page
568 void JobList::writeSettings()
570 QSettings settings(m_console->m_dir->name(), "bat");
571 settings.beginGroup(m_groupText);
572 settings.setValue(m_splitText, m_splitter->saveState());
577 * Read and restore user settings associated with this page
579 void JobList::readSettings()
581 m_groupText = "JobListPage";
582 m_splitText = "splitterSizes_1";
583 QSettings settings(m_console->m_dir->name(), "bat");
584 settings.beginGroup(m_groupText);
585 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
590 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
592 void JobList::selectionChanged()
595 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
596 foreach (QTableWidgetItem *sitem, sitems) {
597 int row = sitem->row();
598 if (!rowList.contains(row)) {
604 m_selectedJobsList.clear();
606 foreach(int row, rowList) {
607 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
608 if (!first) m_selectedJobs.append(",");
610 m_selectedJobs.append(sitem->text());
611 m_selectedJobsList.append(sitem->text());
613 m_selectedJobsCount = rowList.count();
614 if (m_selectedJobsCount > 1) {
615 QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
616 actionDeleteJob->setText(text);
617 text = QString( tr("Purge Files from list of %1 Jobs")).arg(m_selectedJobsCount);
618 actionPurgeFiles->setText(text);
620 actionDeleteJob->setText(tr("Delete Single Job"));
621 actionPurgeFiles->setText(tr("Purge Files from single job"));
624 /* remove all actions */
625 foreach(QAction* mediaAction, mp_tableWidget->actions()) {
626 mp_tableWidget->removeAction(mediaAction);
630 mp_tableWidget->addAction(actionRefreshJobList);
631 if (m_selectedJobsCount == 1) {
632 mp_tableWidget->addAction(actionListJobid);
633 mp_tableWidget->addAction(actionListFilesOnJob);
634 mp_tableWidget->addAction(actionListJobMedia);
635 mp_tableWidget->addAction(actionListVolumes);
636 mp_tableWidget->addAction(actionRestoreFromJob);
637 mp_tableWidget->addAction(actionRestoreFromTime);
638 mp_tableWidget->addAction(actionShowLogForJob);
640 if (m_selectedJobsCount >= 1) {
641 mp_tableWidget->addAction(actionDeleteJob);
642 mp_tableWidget->addAction(actionPurgeFiles);
645 /* Make Connections */
646 if (m_checkCurrentWidget) {
647 int row = mp_tableWidget->currentRow();
648 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
649 m_currentJob = jobitem->text();
651 /* include purged action or not */
652 jobitem = mp_tableWidget->item(row, m_purgedIndex);
653 QString purged = jobitem->text();
654 /* mp_tableWidget->removeAction(actionPurgeFiles);
655 if (purged == tr("No") ) {
656 mp_tableWidget->addAction(actionPurgeFiles);
658 /* include restore from time and job action or not */
659 jobitem = mp_tableWidget->item(row, m_typeIndex);
660 QString type = jobitem->text();
661 if (m_selectedJobsCount == 1) {
662 mp_tableWidget->removeAction(actionRestoreFromJob);
663 mp_tableWidget->removeAction(actionRestoreFromTime);
664 if (type == tr("Backup")) {
665 mp_tableWidget->addAction(actionRestoreFromJob);
666 mp_tableWidget->addAction(actionRestoreFromTime);
669 /* include cancel action or not */
670 jobitem = mp_tableWidget->item(row, m_statusIndex);
671 QString status = jobitem->text();
672 mp_tableWidget->removeAction(actionCancelJob);
673 if (status == tr("Running") || status == tr("Created, not yet running")) {
674 mp_tableWidget->addAction(actionCancelJob);