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->setSelectionMode(QAbstractItemView::SingleSelection);
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 /* Resize the columns */
210 mp_tableWidget->resizeColumnsToContents();
211 mp_tableWidget->resizeRowsToContents();
212 mp_tableWidget->verticalHeader()->hide();
213 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
214 /* for context sensitive searches, let the user know if there were no
216 QMessageBox::warning(this, "Bat",
217 tr("The Jobs query returned no results.\n"
218 "Press OK to continue?"), QMessageBox::Ok );
222 void JobList::prepareFilterWidgets()
225 clientComboBox->addItem(tr("Any"));
226 clientComboBox->addItems(m_console->client_list);
227 int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly);
228 if (clientIndex != -1)
229 clientComboBox->setCurrentIndex(clientIndex);
231 QStringList volumeList;
232 m_console->getVolumeList(volumeList);
233 volumeComboBox->addItem(tr("Any"));
234 volumeComboBox->addItems(volumeList);
235 int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
236 if (volumeIndex != -1) {
237 volumeComboBox->setCurrentIndex(volumeIndex);
239 jobComboBox->addItem(tr("Any"));
240 jobComboBox->addItems(m_console->job_list);
241 int jobIndex = jobComboBox->findText(m_jobName, Qt::MatchExactly);
242 if (jobIndex != -1) {
243 jobComboBox->setCurrentIndex(jobIndex);
245 levelComboBox->addItem(tr("Any"));
246 levelComboBox->addItems( QStringList() << "F" << "D" << "I");
247 purgedComboBox->addItem(tr("Any"));
248 purgedComboBox->addItems( QStringList() << "0" << "1");
249 fileSetComboBox->addItem(tr("Any"));
250 fileSetComboBox->addItems(m_console->fileset_list);
251 int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly);
252 if (filesetIndex != -1) {
253 fileSetComboBox->setCurrentIndex(filesetIndex);
255 QStringList statusLongList;
256 m_console->getStatusList(statusLongList);
257 statusComboBox->addItem(tr("Any"));
258 statusComboBox->addItems(statusLongList);
262 void JobList::fillQueryString(QString &query)
265 int volumeIndex = volumeComboBox->currentIndex();
266 if (volumeIndex != -1)
267 m_mediaName = volumeComboBox->itemText(volumeIndex);
268 QString distinct = "";
269 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
270 query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, "
271 " Client.Name AS Client,"
272 " Job.Starttime AS JobStart, Job.Type AS JobType,"
273 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
274 " Job.JobBytes AS Bytes,"
275 " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
276 " Job.PurgedFiles AS Purged, FileSet.FileSet"
278 " JOIN Client ON (Client.ClientId=Job.ClientId)"
279 " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
280 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
281 QStringList conditions;
282 if (m_mediaName != tr("Any")) {
283 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
284 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
285 conditions.append("Media.VolumeName='" + m_mediaName + "'");
287 int clientIndex = clientComboBox->currentIndex();
288 if (clientIndex != -1)
289 m_clientName = clientComboBox->itemText(clientIndex);
290 if (m_clientName != tr("Any")) {
291 conditions.append("Client.Name='" + m_clientName + "'");
293 int jobIndex = jobComboBox->currentIndex();
295 m_jobName = jobComboBox->itemText(jobIndex);
296 if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != tr("Any"))) {
297 conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
299 int levelIndex = levelComboBox->currentIndex();
300 if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != tr("Any"))) {
301 conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
303 int statusIndex = statusComboBox->currentIndex();
304 if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != tr("Any"))) {
305 conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
307 int purgedIndex = purgedComboBox->currentIndex();
308 if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != tr("Any"))) {
309 conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
311 int fileSetIndex = fileSetComboBox->currentIndex();
312 if (fileSetIndex != -1)
313 m_filesetName = fileSetComboBox->itemText(fileSetIndex);
314 if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != tr("Any"))) {
315 conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
317 /* If Limit check box For limit by days is checked */
318 if (daysCheckBox->checkState() == Qt::Checked) {
319 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
320 QString since = stamp.toString(Qt::ISODate);
321 conditions.append("Job.Starttime>'" + since + "'");
324 foreach (QString condition, conditions) {
326 query += " WHERE " + condition;
329 query += " AND " + condition;
333 query += " ORDER BY Job.Starttime=0 DESC, Job.Starttime DESC, Job.JobId DESC";
334 /* If Limit check box for limit records returned is checked */
335 if (limitCheckBox->checkState() == Qt::Checked) {
337 limit.setNum(limitSpinBox->value());
338 query += " LIMIT " + limit;
343 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
344 * The tree has been populated.
346 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()
367 * Virtual Function to return the name for the medialist tree widget
369 void JobList::treeWidgetName(QString &desc)
371 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
375 if (m_mediaName != "" ) {
376 desc += "of Volume " + m_mediaName;
378 if (m_clientName != "" ) {
379 desc += "of Client " + m_clientName;
381 if (m_jobName != "" ) {
382 desc += "of Job " + m_jobName;
384 if (m_filesetName != "" ) {
385 desc += "of fileset " + m_filesetName;
391 * This functions much line tableItemChanged for trees like the page selector,
392 * but I will do much less here
394 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
396 if (m_checkCurrentWidget) {
397 int row = currentItem->row();
398 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
399 m_currentJob = jobitem->text();
402 /* include purged action or not */
403 jobitem = mp_tableWidget->item(row, m_purgedIndex);
404 QString purged = jobitem->text();
405 mp_tableWidget->removeAction(actionPurgeFiles);
406 if (purged == "NOT") {
407 mp_tableWidget->addAction(actionPurgeFiles);
409 /* include restore from time and job action or not */
410 jobitem = mp_tableWidget->item(row, m_typeIndex);
411 QString type = jobitem->text();
412 mp_tableWidget->removeAction(actionRestoreFromJob);
413 mp_tableWidget->removeAction(actionRestoreFromTime);
414 if (type == "Backup") {
415 mp_tableWidget->addAction(actionRestoreFromJob);
416 mp_tableWidget->addAction(actionRestoreFromTime);
418 /* include cancel action or not */
419 jobitem = mp_tableWidget->item(row, m_statusIndex);
420 QString status = jobitem->text();
421 mp_tableWidget->removeAction(actionCancelJob);
422 if (status == "Running") {
423 mp_tableWidget->addAction(actionCancelJob);
429 * Function to create connections for context sensitive menu for this and
432 void JobList::createConnections()
434 /* connect to the action specific to this pages class that shows up in the
435 * page selector tree */
436 connect(actionRefreshJobList, SIGNAL(triggered()), this,
437 SLOT(populateTable()));
438 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
440 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
442 graphButton->setEnabled(false);
444 /* for the tableItemChanged to maintain m_currentJob */
445 connect(mp_tableWidget, SIGNAL(
446 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
447 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
449 /* Do what is required for the local context sensitive menu */
452 /* setContextMenuPolicy is required */
453 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
456 mp_tableWidget->addAction(actionRefreshJobList);
457 mp_tableWidget->addAction(actionListJobid);
458 mp_tableWidget->addAction(actionListFilesOnJob);
459 mp_tableWidget->addAction(actionListJobMedia);
460 mp_tableWidget->addAction(actionListVolumes);
461 mp_tableWidget->addAction(actionDeleteJob);
462 mp_tableWidget->addAction(actionPurgeFiles);
463 mp_tableWidget->addAction(actionRestoreFromJob);
464 mp_tableWidget->addAction(actionRestoreFromTime);
465 mp_tableWidget->addAction(actionShowLogForJob);
467 /* Make Connections */
468 connect(actionListJobid, SIGNAL(triggered()), this,
469 SLOT(consoleListJobid()));
470 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
471 SLOT(consoleListFilesOnJob()));
472 connect(actionListJobMedia, SIGNAL(triggered()), this,
473 SLOT(consoleListJobMedia()));
474 connect(actionListVolumes, SIGNAL(triggered()), this,
475 SLOT(consoleListVolumes()));
476 connect(actionDeleteJob, SIGNAL(triggered()), this,
477 SLOT(consoleDeleteJob()));
478 connect(actionPurgeFiles, SIGNAL(triggered()), this,
479 SLOT(consolePurgeFiles()));
480 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
481 SLOT(preRestoreFromJob()));
482 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
483 SLOT(preRestoreFromTime()));
484 connect(actionShowLogForJob, SIGNAL(triggered()), this,
485 SLOT(showLogForJob()));
486 connect(actionCancelJob, SIGNAL(triggered()), this,
487 SLOT(consoleCancelJob()));
488 connect(actionListJobTotals, SIGNAL(triggered()), this,
489 SLOT(consoleListJobTotals()));
491 m_contextActions.append(actionRefreshJobList);
492 m_contextActions.append(actionListJobTotals);
496 * Functions to respond to local context sensitive menu sending console commands
497 * If I could figure out how to make these one function passing a string, Yaaaaaa
499 void JobList::consoleListJobid()
501 QString cmd("list jobid=");
503 if (mainWin->m_longList) { cmd.prepend("l"); }
506 void JobList::consoleListFilesOnJob()
508 QString cmd("list files jobid=");
510 if (mainWin->m_longList) { cmd.prepend("l"); }
513 void JobList::consoleListJobMedia()
515 QString cmd("list jobmedia jobid=");
517 if (mainWin->m_longList) { cmd.prepend("l"); }
520 void JobList::consoleListVolumes()
522 QString cmd("list volumes jobid=");
524 if (mainWin->m_longList) { cmd.prepend("l"); }
527 void JobList::consoleListJobTotals()
529 QString cmd("list jobtotals");
530 if (mainWin->m_longList) { cmd.prepend("l"); }
533 void JobList::consoleDeleteJob()
535 if (QMessageBox::warning(this, "Bat",
536 tr("Are you sure you want to delete?? !!!.\n"
537 "This delete command is used to delete a Job record and all associated catalog"
538 " records that were created. This command operates only on the Catalog"
539 " database and has no effect on the actual data written to a Volume. This"
540 " command can be dangerous and we strongly recommend that you do not use"
541 " it unless you know what you are doing. The Job and all its associated"
542 " records (File and JobMedia) will be deleted from the catalog."
543 "Press OK to proceed with delete operation.?"),
544 QMessageBox::Ok | QMessageBox::Cancel)
545 == QMessageBox::Cancel) { return; }
547 QString cmd("delete job jobid=");
548 cmd += m_selectedJobs;
551 void JobList::consolePurgeFiles()
553 if (QMessageBox::warning(this, "Bat",
554 tr("Are you sure you want to purge ?? !!!.\n"
555 "The Purge command will delete associated Catalog database records from Jobs and"
556 " Volumes without considering the retention period. Purge works only on the"
557 " Catalog database and does not affect data written to Volumes. This command can"
558 " be dangerous because you can delete catalog records associated with current"
559 " backups of files, and we recommend that you do not use it unless you know what"
561 "Press OK to proceed with the purge operation?"),
562 QMessageBox::Ok | QMessageBox::Cancel)
563 == QMessageBox::Cancel) { return; }
565 QString cmd("purge files jobid=");
571 * Subroutine to call preRestore to restore from a select job
573 void JobList::preRestoreFromJob()
575 new prerestorePage(m_currentJob, R_JOBIDLIST);
579 * Subroutine to call preRestore to restore from a select job
581 void JobList::preRestoreFromTime()
583 new prerestorePage(m_currentJob, R_JOBDATETIME);
587 * Subroutine to call class to show the log in the database from that job
589 void JobList::showLogForJob()
591 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
592 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
596 * Cancel a running job
598 void JobList::consoleCancelJob()
600 QString cmd("cancel jobid=");
609 void JobList::graphTable()
612 pass.recordLimitCheck = limitCheckBox->checkState();
613 pass.daysLimitCheck = daysCheckBox->checkState();
614 pass.recordLimitSpin = limitSpinBox->value();
615 pass.daysLimitSpin = daysSpinBox->value();
616 pass.jobCombo = jobComboBox->currentText();
617 pass.clientCombo = clientComboBox->currentText();
618 pass.volumeCombo = volumeComboBox->currentText();
619 pass.fileSetCombo = fileSetComboBox->currentText();
620 pass.purgedCombo = purgedComboBox->currentText();
621 pass.levelCombo = levelComboBox->currentText();
622 pass.statusCombo = statusComboBox->currentText();
624 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
625 new JobPlot(pageSelectorTreeWidgetItem, pass);
630 * Save user settings associated with this page
632 void JobList::writeSettings()
634 QSettings settings(m_console->m_dir->name(), "bat");
635 settings.beginGroup(m_groupText);
636 settings.setValue(m_splitText, m_splitter->saveState());
641 * Read and restore user settings associated with this page
643 void JobList::readSettings()
645 m_groupText = "JobListPage";
646 m_splitText = "splitterSizes_1";
647 QSettings settings(m_console->m_dir->name(), "bat");
648 settings.beginGroup(m_groupText);
649 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
654 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
656 void JobList::selectedJobsGet()
659 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
660 foreach (QTableWidgetItem *sitem, sitems) {
661 int row = sitem->row();
662 if (!rowList.contains(row)) {
669 foreach(int row, rowList) {
670 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
671 if (!first) m_selectedJobs.append(",");
673 m_selectedJobs.append(sitem->text());
675 m_selectedJobsCount = rowList.count();
676 if (m_selectedJobsCount > 1) {
677 QString text = QString("Delete list of %1 Jobs").arg(m_selectedJobsCount);
678 actionDeleteJob->setText(text);
680 actionDeleteJob->setText("Delete Single Job");