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 FirstVolume,"
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 + "'");
318 if (filterCopyCheckBox->checkState() == Qt::Checked) {
319 conditions.append("Job.Type != 'c'" );
321 if (filterMigrationCheckBox->checkState() == Qt::Checked) {
322 conditions.append("Job.Type != 'g'" );
325 foreach (QString condition, conditions) {
327 query += " WHERE " + condition;
330 query += " AND " + condition;
334 query += " ORDER BY Job.JobId DESC";
335 /* If Limit check box for limit records returned is checked */
336 if (limitCheckBox->checkState() == Qt::Checked) {
338 limit.setNum(limitSpinBox->value());
339 query += " LIMIT " + limit;
344 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
345 * The tree has been populated.
347 void JobList::PgSeltreeWidgetClicked()
355 * Virtual function override of pages function which is called when this page
356 * is visible on the stack
358 void JobList::currentStackItem()
360 /* if (!m_populated) populate every time user comes back to this object */
365 * Virtual Function to return the name for the medialist tree widget
367 void JobList::treeWidgetName(QString &desc)
369 if (m_mediaName != "" ) {
370 desc = tr("JobList of Volume %1").arg(m_mediaName);
371 } else if (m_clientName != "" ) {
372 desc = tr("JobList of Client %1").arg(m_clientName);
373 } else if (m_jobName != "" ) {
374 desc = tr("JobList of Job %1").arg(m_jobName);
375 } else if (m_filesetName != "" ) {
376 desc = tr("JobList of fileset %1").arg(m_filesetName);
378 desc = tr("JobList");
383 * Function to create connections for context sensitive menu for this and
386 void JobList::createConnections()
388 /* connect to the action specific to this pages class that shows up in the
389 * page selector tree */
390 connect(actionRefreshJobList, SIGNAL(triggered()), this,
391 SLOT(populateTable()));
392 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
394 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
396 graphButton->setEnabled(false);
397 graphButton->setVisible(false);
399 /* for the selectionChanged to maintain m_currentJob and a delete selection */
400 connect(mp_tableWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged()));
402 /* Do what is required for the local context sensitive menu */
405 /* setContextMenuPolicy is required */
406 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
408 connect(actionListJobid, SIGNAL(triggered()), this,
409 SLOT(consoleListJobid()));
410 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
411 SLOT(consoleListFilesOnJob()));
412 connect(actionListJobMedia, SIGNAL(triggered()), this,
413 SLOT(consoleListJobMedia()));
414 connect(actionListVolumes, SIGNAL(triggered()), this,
415 SLOT(consoleListVolumes()));
416 connect(actionDeleteJob, SIGNAL(triggered()), this,
417 SLOT(consoleDeleteJob()));
418 connect(actionPurgeFiles, SIGNAL(triggered()), this,
419 SLOT(consolePurgeFiles()));
420 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
421 SLOT(preRestoreFromJob()));
422 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
423 SLOT(preRestoreFromTime()));
424 connect(actionShowLogForJob, SIGNAL(triggered()), this,
425 SLOT(showLogForJob()));
426 connect(actionCancelJob, SIGNAL(triggered()), this,
427 SLOT(consoleCancelJob()));
428 connect(actionListJobTotals, SIGNAL(triggered()), this,
429 SLOT(consoleListJobTotals()));
431 m_contextActions.append(actionRefreshJobList);
432 m_contextActions.append(actionListJobTotals);
436 * Functions to respond to local context sensitive menu sending console commands
437 * If I could figure out how to make these one function passing a string, Yaaaaaa
439 void JobList::consoleListJobid()
441 QString cmd("list jobid=");
443 if (mainWin->m_longList) { cmd.prepend("l"); }
446 void JobList::consoleListFilesOnJob()
448 QString cmd("list files jobid=");
450 if (mainWin->m_longList) { cmd.prepend("l"); }
453 void JobList::consoleListJobMedia()
455 QString cmd("list jobmedia jobid=");
457 if (mainWin->m_longList) { cmd.prepend("l"); }
460 void JobList::consoleListVolumes()
462 QString cmd("list volumes jobid=");
464 if (mainWin->m_longList) { cmd.prepend("l"); }
467 void JobList::consoleListJobTotals()
469 QString cmd("list jobtotals");
470 if (mainWin->m_longList) { cmd.prepend("l"); }
473 void JobList::consoleDeleteJob()
475 if (QMessageBox::warning(this, "Bat",
476 tr("Are you sure you want to delete?? !!!.\n"
477 "This delete command is used to delete a Job record and all associated catalog"
478 " records that were created. This command operates only on the Catalog"
479 " database and has no effect on the actual data written to a Volume. This"
480 " command can be dangerous and we strongly recommend that you do not use"
481 " it unless you know what you are doing. The Job and all its associated"
482 " records (File and JobMedia) will be deleted from the catalog."
483 "Press OK to proceed with delete operation.?"),
484 QMessageBox::Ok | QMessageBox::Cancel)
485 == QMessageBox::Cancel) { return; }
487 QString cmd("delete job jobid=");
488 cmd += m_selectedJobs;
489 consoleCommand(cmd, false);
492 void JobList::consolePurgeFiles()
494 if (QMessageBox::warning(this, "Bat",
495 tr("Are you sure you want to purge ?? !!!.\n"
496 "The Purge command will delete associated Catalog database records from Jobs and"
497 " Volumes without considering the retention period. Purge works only on the"
498 " Catalog database and does not affect data written to Volumes. This command can"
499 " be dangerous because you can delete catalog records associated with current"
500 " backups of files, and we recommend that you do not use it unless you know what"
502 "Press OK to proceed with the purge operation?"),
503 QMessageBox::Ok | QMessageBox::Cancel)
504 == QMessageBox::Cancel) { return; }
506 foreach(QString job, m_selectedJobsList) {
507 QString cmd("purge files jobid=");
509 consoleCommand(cmd, false);
515 * Subroutine to call preRestore to restore from a select job
517 void JobList::preRestoreFromJob()
519 new prerestorePage(m_currentJob, R_JOBIDLIST);
523 * Subroutine to call preRestore to restore from a select job
525 void JobList::preRestoreFromTime()
527 new prerestorePage(m_currentJob, R_JOBDATETIME);
531 * Subroutine to call class to show the log in the database from that job
533 void JobList::showLogForJob()
535 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
536 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
540 * Cancel a running job
542 void JobList::consoleCancelJob()
544 QString cmd("cancel jobid=");
552 void JobList::graphTable()
556 pass.recordLimitCheck = limitCheckBox->checkState();
557 pass.daysLimitCheck = daysCheckBox->checkState();
558 pass.recordLimitSpin = limitSpinBox->value();
559 pass.daysLimitSpin = daysSpinBox->value();
560 pass.jobCombo = jobComboBox->currentText();
561 pass.clientCombo = clientComboBox->currentText();
562 pass.volumeCombo = volumeComboBox->currentText();
563 pass.fileSetCombo = fileSetComboBox->currentText();
564 pass.purgedCombo = purgedComboBox->currentText();
565 pass.levelCombo = levelComboBox->currentText();
566 pass.statusCombo = statusComboBox->currentText();
568 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
569 new JobPlot(pageSelectorTreeWidgetItem, pass);
574 * Save user settings associated with this page
576 void JobList::writeSettings()
578 QSettings settings(m_console->m_dir->name(), "bat");
579 settings.beginGroup(m_groupText);
580 settings.setValue(m_splitText, m_splitter->saveState());
581 settings.setValue("FilterCopyCheckState", filterCopyCheckBox->checkState());
582 settings.setValue("FilterMigrationCheckState", filterMigrationCheckBox->checkState());
587 * Read and restore user settings associated with this page
589 void JobList::readSettings()
591 m_groupText = "JobListPage";
592 m_splitText = "splitterSizes_1";
593 QSettings settings(m_console->m_dir->name(), "bat");
594 settings.beginGroup(m_groupText);
595 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
596 filterCopyCheckBox->setCheckState((Qt::CheckState)settings.value("FilterCopyCheckState").toInt());
597 filterMigrationCheckBox->setCheckState((Qt::CheckState)settings.value("FilterMigrationCheckState").toInt());
602 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
604 void JobList::selectionChanged()
607 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
608 foreach (QTableWidgetItem *sitem, sitems) {
609 int row = sitem->row();
610 if (!rowList.contains(row)) {
616 m_selectedJobsList.clear();
618 foreach(int row, rowList) {
619 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
620 if (!first) m_selectedJobs.append(",");
622 m_selectedJobs.append(sitem->text());
623 m_selectedJobsList.append(sitem->text());
625 m_selectedJobsCount = rowList.count();
626 if (m_selectedJobsCount > 1) {
627 QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
628 actionDeleteJob->setText(text);
629 text = QString( tr("Purge Files from list of %1 Jobs")).arg(m_selectedJobsCount);
630 actionPurgeFiles->setText(text);
632 actionDeleteJob->setText(tr("Delete Single Job"));
633 actionPurgeFiles->setText(tr("Purge Files from single job"));
636 /* remove all actions */
637 foreach(QAction* mediaAction, mp_tableWidget->actions()) {
638 mp_tableWidget->removeAction(mediaAction);
642 mp_tableWidget->addAction(actionRefreshJobList);
643 if (m_selectedJobsCount == 1) {
644 mp_tableWidget->addAction(actionListJobid);
645 mp_tableWidget->addAction(actionListFilesOnJob);
646 mp_tableWidget->addAction(actionListJobMedia);
647 mp_tableWidget->addAction(actionListVolumes);
648 mp_tableWidget->addAction(actionRestoreFromJob);
649 mp_tableWidget->addAction(actionRestoreFromTime);
650 mp_tableWidget->addAction(actionShowLogForJob);
652 if (m_selectedJobsCount >= 1) {
653 mp_tableWidget->addAction(actionDeleteJob);
654 mp_tableWidget->addAction(actionPurgeFiles);
657 /* Make Connections */
658 if (m_checkCurrentWidget) {
659 int row = mp_tableWidget->currentRow();
660 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
661 m_currentJob = jobitem->text();
663 /* include purged action or not */
664 jobitem = mp_tableWidget->item(row, m_purgedIndex);
665 QString purged = jobitem->text();
666 /* mp_tableWidget->removeAction(actionPurgeFiles);
667 if (purged == tr("No") ) {
668 mp_tableWidget->addAction(actionPurgeFiles);
670 /* include restore from time and job action or not */
671 jobitem = mp_tableWidget->item(row, m_typeIndex);
672 QString type = jobitem->text();
673 if (m_selectedJobsCount == 1) {
674 mp_tableWidget->removeAction(actionRestoreFromJob);
675 mp_tableWidget->removeAction(actionRestoreFromTime);
676 if (type == tr("Backup")) {
677 mp_tableWidget->addAction(actionRestoreFromJob);
678 mp_tableWidget->addAction(actionRestoreFromTime);
681 /* include cancel action or not */
682 jobitem = mp_tableWidget->item(row, m_statusIndex);
683 QString status = jobitem->text();
684 mp_tableWidget->removeAction(actionCancelJob);
685 if (status == tr("Running") || status == tr("Created, not yet running")) {
686 mp_tableWidget->addAction(actionCancelJob);