2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2008 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 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.
31 * Dirk Bartley, March 2007
34 #include <QAbstractEventDispatcher>
35 #include <QTableWidgetItem>
39 #include "joblog/joblog.h"
41 #include "jobgraphs/jobplot.h"
43 #include "util/fmtwidgetitem.h"
46 * Constructor for the class
48 JobList::JobList(const QString &mediaName, const QString &clientName,
49 const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
52 m_name = ""; /* treeWidgetName has a virtual override in this class */
53 m_mediaName = mediaName;
54 m_clientName = clientName;
56 m_filesetName = filesetName;
57 m_filesetName = filesetName;
58 pgInitialize(parentTreeWidgetItem);
59 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
60 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
65 if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
67 m_checkCurrentWidget = true;
70 /* Set Defaults for check and spin for limits */
71 limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
72 limitSpinBox->setValue(mainWin->m_recordLimitVal);
73 daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
74 daysSpinBox->setValue(mainWin->m_daysLimitVal);
77 QGridLayout *gridLayout = new QGridLayout(this);
78 gridLayout->setSpacing(6);
79 gridLayout->setMargin(9);
80 gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
82 m_splitter = new QSplitter(Qt::Vertical, this);
83 QScrollArea *area = new QScrollArea();
84 area->setObjectName(QString::fromUtf8("area"));
85 area->setWidget(frame);
86 area->setWidgetResizable(true);
87 m_splitter->addWidget(mp_tableWidget);
88 m_splitter->addWidget(area);
90 gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
95 * Write the m_splitter settings in the destructor
103 * The Meat of the class.
104 * This function will populate the QTableWidget, mp_tablewidget, with
105 * QTableWidgetItems representing the results of a query for what jobs exist on
106 * the media name passed from the constructor stored in m_mediaName.
108 void JobList::populateTable()
110 if (!m_console->preventInUseConnect())
113 /* Can't do this in constructor because not neccesarily conected in constructor */
114 prepareFilterWidgets();
118 fillQueryString(query);
120 /* Set up the Header for the table */
121 QStringList headerlist = (QStringList()
122 << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime")
123 << tr("Job Type") << tr("Job Level") << tr("Job Files")
124 << tr("Job Bytes") << tr("Job Status") << tr("Purged") << tr("File Set"));
126 m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
127 m_purgedIndex = headerlist.indexOf(tr("Purged"));
128 m_typeIndex = headerlist.indexOf(tr("Job Type"));
129 m_statusIndex = headerlist.indexOf(tr("Job Status"));
130 m_startIndex = headerlist.indexOf(tr("Job Starttime"));
131 m_filesIndex = headerlist.indexOf(tr("Job Files"));
132 m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
134 /* Initialize the QTableWidget */
135 m_checkCurrentWidget = false;
136 mp_tableWidget->clear();
137 m_checkCurrentWidget = true;
138 mp_tableWidget->setColumnCount(headerlist.size());
139 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
140 mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
141 mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
143 if (mainWin->m_sqlDebug) {
144 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
148 if (m_console->sql_cmd(query, results)) {
149 m_resultCount = results.count();
151 QStringList fieldlist;
152 mp_tableWidget->setRowCount(results.size());
155 /* Iterate through the record returned from the query */
157 foreach (resultline, results) {
158 fieldlist = resultline.split("\t");
159 if (fieldlist.size() < 12)
160 continue; /* some fields missing, ignore row */
162 TableItemFormatter jobitem(*mp_tableWidget, row);
164 /* Iterate through fields in the record */
165 QStringListIterator fld(fieldlist);
169 jobitem.setNumericFld(col++, fld.next());
172 jobitem.setTextFld(col++, fld.next());
175 jobitem.setTextFld(col++, fld.next());
178 jobitem.setTextFld(col++, fld.next(), true);
181 jobitem.setJobTypeFld(col++, fld.next());
184 jobitem.setJobLevelFld(col++, fld.next());
187 jobitem.setNumericFld(col++, fld.next());
190 jobitem.setBytesFld(col++, fld.next());
193 QString shortstatus(fld.next());
194 QString longstatus(fld.next());
195 jobitem.setJobStatusFld(col++, shortstatus, longstatus);
198 if (fld.next().toInt())
199 jobitem.setTextFld(col++, tr("IS"), true);
201 jobitem.setTextFld(col++, tr("NOT"), true);
204 jobitem.setTextFld(col++, fld.next());
209 /* set default sorting */
210 mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
211 mp_tableWidget->setSortingEnabled(true);
213 /* Resize the columns */
214 mp_tableWidget->resizeColumnsToContents();
215 mp_tableWidget->resizeRowsToContents();
216 mp_tableWidget->verticalHeader()->hide();
217 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
218 /* for context sensitive searches, let the user know if there were no
220 QMessageBox::warning(this, "Bat",
221 tr("The Jobs query returned no results.\n"
222 "Press OK to continue?"), QMessageBox::Ok );
226 void JobList::prepareFilterWidgets()
229 clientComboBox->addItem(tr("Any"));
230 clientComboBox->addItems(m_console->client_list);
231 int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly);
232 if (clientIndex != -1)
233 clientComboBox->setCurrentIndex(clientIndex);
235 QStringList volumeList;
236 m_console->getVolumeList(volumeList);
237 volumeComboBox->addItem(tr("Any"));
238 volumeComboBox->addItems(volumeList);
239 int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
240 if (volumeIndex != -1) {
241 volumeComboBox->setCurrentIndex(volumeIndex);
243 jobComboBox->addItem(tr("Any"));
244 jobComboBox->addItems(m_console->job_list);
245 int jobIndex = jobComboBox->findText(m_jobName, Qt::MatchExactly);
246 if (jobIndex != -1) {
247 jobComboBox->setCurrentIndex(jobIndex);
249 levelComboBox->addItem(tr("Any"));
250 levelComboBox->addItems( QStringList() << "F" << "D" << "I");
251 purgedComboBox->addItem(tr("Any"));
252 purgedComboBox->addItems( QStringList() << "0" << "1");
253 fileSetComboBox->addItem(tr("Any"));
254 fileSetComboBox->addItems(m_console->fileset_list);
255 int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly);
256 if (filesetIndex != -1) {
257 fileSetComboBox->setCurrentIndex(filesetIndex);
259 QStringList statusLongList;
260 m_console->getStatusList(statusLongList);
261 statusComboBox->addItem(tr("Any"));
262 statusComboBox->addItems(statusLongList);
266 void JobList::fillQueryString(QString &query)
269 int volumeIndex = volumeComboBox->currentIndex();
270 if (volumeIndex != -1)
271 m_mediaName = volumeComboBox->itemText(volumeIndex);
272 QString distinct = "";
273 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
274 query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, "
275 " Client.Name AS Client,"
276 " Job.Starttime AS JobStart, Job.Type AS JobType,"
277 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
278 " Job.JobBytes AS Bytes,"
279 " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
280 " Job.PurgedFiles AS Purged, FileSet.FileSet"
282 " JOIN Client ON (Client.ClientId=Job.ClientId)"
283 " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
284 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
285 QStringList conditions;
286 if (m_mediaName != tr("Any")) {
287 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
288 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
289 conditions.append("Media.VolumeName='" + m_mediaName + "'");
291 int clientIndex = clientComboBox->currentIndex();
292 if (clientIndex != -1)
293 m_clientName = clientComboBox->itemText(clientIndex);
294 if (m_clientName != tr("Any")) {
295 conditions.append("Client.Name='" + m_clientName + "'");
297 int jobIndex = jobComboBox->currentIndex();
299 m_jobName = jobComboBox->itemText(jobIndex);
300 if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != tr("Any"))) {
301 conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
303 int levelIndex = levelComboBox->currentIndex();
304 if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != tr("Any"))) {
305 conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
307 int statusIndex = statusComboBox->currentIndex();
308 if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != tr("Any"))) {
309 conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
311 int purgedIndex = purgedComboBox->currentIndex();
312 if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != tr("Any"))) {
313 conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
315 int fileSetIndex = fileSetComboBox->currentIndex();
316 if (fileSetIndex != -1)
317 m_filesetName = fileSetComboBox->itemText(fileSetIndex);
318 if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != tr("Any"))) {
319 conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
321 /* If Limit check box For limit by days is checked */
322 if (daysCheckBox->checkState() == Qt::Checked) {
323 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
324 QString since = stamp.toString(Qt::ISODate);
325 conditions.append("Job.Starttime>'" + since + "'");
328 foreach (QString condition, conditions) {
330 query += " WHERE " + condition;
333 query += " AND " + condition;
337 query += " ORDER BY Job.JobId DESC";
338 /* If Limit check box for limit records returned is checked */
339 if (limitCheckBox->checkState() == Qt::Checked) {
341 limit.setNum(limitSpinBox->value());
342 query += " LIMIT " + limit;
347 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
348 * The tree has been populated.
350 void JobList::PgSeltreeWidgetClicked()
359 * Virtual function override of pages function which is called when this page
360 * is visible on the stack
362 void JobList::currentStackItem()
371 * Virtual Function to return the name for the medialist tree widget
373 void JobList::treeWidgetName(QString &desc)
375 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
379 if (m_mediaName != "" ) {
380 desc += "of Volume " + m_mediaName;
382 if (m_clientName != "" ) {
383 desc += "of Client " + m_clientName;
385 if (m_jobName != "" ) {
386 desc += "of Job " + m_jobName;
388 if (m_filesetName != "" ) {
389 desc += "of fileset " + m_filesetName;
395 * This functions much line tableItemChanged for trees like the page selector,
396 * but I will do much less here
398 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
400 if (m_checkCurrentWidget) {
401 int row = currentItem->row();
402 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
403 m_currentJob = jobitem->text();
405 /* include purged action or not */
406 jobitem = mp_tableWidget->item(row, m_purgedIndex);
407 QString purged = jobitem->text();
408 mp_tableWidget->removeAction(actionPurgeFiles);
409 if (purged == "NOT") {
410 mp_tableWidget->addAction(actionPurgeFiles);
412 /* include restore from time and job action or not */
413 jobitem = mp_tableWidget->item(row, m_typeIndex);
414 QString type = jobitem->text();
415 mp_tableWidget->removeAction(actionRestoreFromJob);
416 mp_tableWidget->removeAction(actionRestoreFromTime);
417 if (type == "Backup") {
418 mp_tableWidget->addAction(actionRestoreFromJob);
419 mp_tableWidget->addAction(actionRestoreFromTime);
421 /* include cancel action or not */
422 jobitem = mp_tableWidget->item(row, m_statusIndex);
423 QString status = jobitem->text();
424 mp_tableWidget->removeAction(actionCancelJob);
425 if (status == "Running") {
426 mp_tableWidget->addAction(actionCancelJob);
432 * Function to create connections for context sensitive menu for this and
435 void JobList::createConnections()
437 /* connect to the action specific to this pages class that shows up in the
438 * page selector tree */
439 connect(actionRefreshJobList, SIGNAL(triggered()), this,
440 SLOT(populateTable()));
441 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
443 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
445 graphButton->setEnabled(false);
447 /* for the tableItemChanged to maintain m_currentJob */
448 connect(mp_tableWidget, SIGNAL(
449 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
450 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
452 /* for the tableItemChanged to maintain a delete selection */
453 connect(mp_tableWidget, SIGNAL( itemSelectionChanged()),
454 this, SLOT(selectedJobsGet()) );
456 /* Do what is required for the local context sensitive menu */
459 /* setContextMenuPolicy is required */
460 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
463 mp_tableWidget->addAction(actionRefreshJobList);
464 mp_tableWidget->addAction(actionListJobid);
465 mp_tableWidget->addAction(actionListFilesOnJob);
466 mp_tableWidget->addAction(actionListJobMedia);
467 mp_tableWidget->addAction(actionListVolumes);
468 mp_tableWidget->addAction(actionDeleteJob);
469 mp_tableWidget->addAction(actionPurgeFiles);
470 mp_tableWidget->addAction(actionRestoreFromJob);
471 mp_tableWidget->addAction(actionRestoreFromTime);
472 mp_tableWidget->addAction(actionShowLogForJob);
474 /* Make Connections */
475 connect(actionListJobid, SIGNAL(triggered()), this,
476 SLOT(consoleListJobid()));
477 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
478 SLOT(consoleListFilesOnJob()));
479 connect(actionListJobMedia, SIGNAL(triggered()), this,
480 SLOT(consoleListJobMedia()));
481 connect(actionListVolumes, SIGNAL(triggered()), this,
482 SLOT(consoleListVolumes()));
483 connect(actionDeleteJob, SIGNAL(triggered()), this,
484 SLOT(consoleDeleteJob()));
485 connect(actionPurgeFiles, SIGNAL(triggered()), this,
486 SLOT(consolePurgeFiles()));
487 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
488 SLOT(preRestoreFromJob()));
489 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
490 SLOT(preRestoreFromTime()));
491 connect(actionShowLogForJob, SIGNAL(triggered()), this,
492 SLOT(showLogForJob()));
493 connect(actionCancelJob, SIGNAL(triggered()), this,
494 SLOT(consoleCancelJob()));
495 connect(actionListJobTotals, SIGNAL(triggered()), this,
496 SLOT(consoleListJobTotals()));
498 m_contextActions.append(actionRefreshJobList);
499 m_contextActions.append(actionListJobTotals);
503 * Functions to respond to local context sensitive menu sending console commands
504 * If I could figure out how to make these one function passing a string, Yaaaaaa
506 void JobList::consoleListJobid()
508 QString cmd("list jobid=");
510 if (mainWin->m_longList) { cmd.prepend("l"); }
513 void JobList::consoleListFilesOnJob()
515 QString cmd("list files jobid=");
517 if (mainWin->m_longList) { cmd.prepend("l"); }
520 void JobList::consoleListJobMedia()
522 QString cmd("list jobmedia jobid=");
524 if (mainWin->m_longList) { cmd.prepend("l"); }
527 void JobList::consoleListVolumes()
529 QString cmd("list volumes jobid=");
531 if (mainWin->m_longList) { cmd.prepend("l"); }
534 void JobList::consoleListJobTotals()
536 QString cmd("list jobtotals");
537 if (mainWin->m_longList) { cmd.prepend("l"); }
540 void JobList::consoleDeleteJob()
542 if (QMessageBox::warning(this, "Bat",
543 tr("Are you sure you want to delete?? !!!.\n"
544 "This delete command is used to delete a Job record and all associated catalog"
545 " records that were created. This command operates only on the Catalog"
546 " database and has no effect on the actual data written to a Volume. This"
547 " command can be dangerous and we strongly recommend that you do not use"
548 " it unless you know what you are doing. The Job and all its associated"
549 " records (File and JobMedia) will be deleted from the catalog."
550 "Press OK to proceed with delete operation.?"),
551 QMessageBox::Ok | QMessageBox::Cancel)
552 == QMessageBox::Cancel) { return; }
554 QString cmd("delete job jobid=");
555 cmd += m_selectedJobs;
558 void JobList::consolePurgeFiles()
560 if (QMessageBox::warning(this, "Bat",
561 tr("Are you sure you want to purge ?? !!!.\n"
562 "The Purge command will delete associated Catalog database records from Jobs and"
563 " Volumes without considering the retention period. Purge works only on the"
564 " Catalog database and does not affect data written to Volumes. This command can"
565 " be dangerous because you can delete catalog records associated with current"
566 " backups of files, and we recommend that you do not use it unless you know what"
568 "Press OK to proceed with the purge operation?"),
569 QMessageBox::Ok | QMessageBox::Cancel)
570 == QMessageBox::Cancel) { return; }
572 QString cmd("purge files jobid=");
578 * Subroutine to call preRestore to restore from a select job
580 void JobList::preRestoreFromJob()
582 new prerestorePage(m_currentJob, R_JOBIDLIST);
586 * Subroutine to call preRestore to restore from a select job
588 void JobList::preRestoreFromTime()
590 new prerestorePage(m_currentJob, R_JOBDATETIME);
594 * Subroutine to call class to show the log in the database from that job
596 void JobList::showLogForJob()
598 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
599 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
603 * Cancel a running job
605 void JobList::consoleCancelJob()
607 QString cmd("cancel jobid=");
616 void JobList::graphTable()
619 pass.recordLimitCheck = limitCheckBox->checkState();
620 pass.daysLimitCheck = daysCheckBox->checkState();
621 pass.recordLimitSpin = limitSpinBox->value();
622 pass.daysLimitSpin = daysSpinBox->value();
623 pass.jobCombo = jobComboBox->currentText();
624 pass.clientCombo = clientComboBox->currentText();
625 pass.volumeCombo = volumeComboBox->currentText();
626 pass.fileSetCombo = fileSetComboBox->currentText();
627 pass.purgedCombo = purgedComboBox->currentText();
628 pass.levelCombo = levelComboBox->currentText();
629 pass.statusCombo = statusComboBox->currentText();
631 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
632 new JobPlot(pageSelectorTreeWidgetItem, pass);
637 * Save user settings associated with this page
639 void JobList::writeSettings()
641 QSettings settings(m_console->m_dir->name(), "bat");
642 settings.beginGroup(m_groupText);
643 settings.setValue(m_splitText, m_splitter->saveState());
648 * Read and restore user settings associated with this page
650 void JobList::readSettings()
652 m_groupText = "JobListPage";
653 m_splitText = "splitterSizes_1";
654 QSettings settings(m_console->m_dir->name(), "bat");
655 settings.beginGroup(m_groupText);
656 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
661 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
663 void JobList::selectedJobsGet()
666 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
667 foreach (QTableWidgetItem *sitem, sitems) {
668 int row = sitem->row();
669 if (!rowList.contains(row)) {
676 foreach(int row, rowList) {
677 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
678 if (!first) m_selectedJobs.append(",");
680 m_selectedJobs.append(sitem->text());
682 m_selectedJobsCount = rowList.count();
683 if (m_selectedJobsCount > 1) {
684 QString text = QString("Delete list of %1 Jobs").arg(m_selectedJobsCount);
685 actionDeleteJob->setText(text);
687 actionDeleteJob->setText("Delete Single Job");