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"
45 * Constructor for the class
47 JobList::JobList(const QString &mediaName, const QString &clientName,
48 const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
51 m_name = ""; /* treeWidgetName has a virtual override in this class */
52 m_mediaName = mediaName;
53 m_clientName = clientName;
55 m_filesetName = filesetName;
56 m_filesetName = filesetName;
57 pgInitialize(parentTreeWidgetItem);
58 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
59 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
64 if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
66 m_checkCurrentWidget = true;
69 /* Set Defaults for check and spin for limits */
70 limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
71 limitSpinBox->setValue(mainWin->m_recordLimitVal);
72 daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
73 daysSpinBox->setValue(mainWin->m_daysLimitVal);
76 QGridLayout *gridLayout = new QGridLayout(this);
77 gridLayout->setSpacing(6);
78 gridLayout->setMargin(9);
79 gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
81 m_splitter = new QSplitter(Qt::Vertical, this);
82 QScrollArea *area = new QScrollArea();
83 area->setObjectName(QString::fromUtf8("area"));
84 area->setWidget(frame);
85 area->setWidgetResizable(true);
86 m_splitter->addWidget(mp_tableWidget);
87 m_splitter->addWidget(area);
89 gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
94 * Write the m_splitter settings in the destructor
102 * The Meat of the class.
103 * This function will populate the QTableWidget, mp_tablewidget, with
104 * QTableWidgetItems representing the results of a query for what jobs exist on
105 * the media name passed from the constructor stored in m_mediaName.
107 void JobList::populateTable()
111 QBrush blackBrush(Qt::black);
113 if (!m_console->preventInUseConnect())
116 /* Can't do this in constructor because not neccesarily conected in constructor */
118 clientComboBox->addItem(tr("Any"));
119 clientComboBox->addItems(m_console->client_list);
120 int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly);
121 if (clientIndex != -1)
122 clientComboBox->setCurrentIndex(clientIndex);
124 QStringList volumeList;
125 m_console->getVolumeList(volumeList);
126 volumeComboBox->addItem(tr("Any"));
127 volumeComboBox->addItems(volumeList);
128 int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
129 if (volumeIndex != -1) {
130 volumeComboBox->setCurrentIndex(volumeIndex);
132 jobComboBox->addItem(tr("Any"));
133 jobComboBox->addItems(m_console->job_list);
134 int jobIndex = jobComboBox->findText(m_jobName, Qt::MatchExactly);
135 if (jobIndex != -1) {
136 jobComboBox->setCurrentIndex(jobIndex);
138 levelComboBox->addItem(tr("Any"));
139 levelComboBox->addItems( QStringList() << "F" << "D" << "I");
140 purgedComboBox->addItem(tr("Any"));
141 purgedComboBox->addItems( QStringList() << "0" << "1");
142 fileSetComboBox->addItem(tr("Any"));
143 fileSetComboBox->addItems(m_console->fileset_list);
144 int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly);
145 if (filesetIndex != -1) {
146 fileSetComboBox->setCurrentIndex(filesetIndex);
148 QStringList statusLongList;
149 m_console->getStatusList(statusLongList);
150 statusComboBox->addItem(tr("Any"));
151 statusComboBox->addItems(statusLongList);
156 int volumeIndex = volumeComboBox->currentIndex();
157 if (volumeIndex != -1)
158 m_mediaName = volumeComboBox->itemText(volumeIndex);
159 QString distinct = "";
160 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
161 query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, "
162 " Client.Name AS Client,"
163 " Job.Starttime AS JobStart, Job.Type AS JobType,"
164 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
165 " Job.JobBytes AS Bytes,"
166 " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
167 " Job.PurgedFiles AS Purged, FileSet.FileSet"
169 " JOIN Client ON (Client.ClientId=Job.ClientId)"
170 " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
171 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
172 QStringList conditions;
173 if (m_mediaName != tr("Any")) {
174 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
175 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
176 conditions.append("Media.VolumeName='" + m_mediaName + "'");
178 int clientIndex = clientComboBox->currentIndex();
179 if (clientIndex != -1)
180 m_clientName = clientComboBox->itemText(clientIndex);
181 if (m_clientName != tr("Any")) {
182 conditions.append("Client.Name='" + m_clientName + "'");
184 int jobIndex = jobComboBox->currentIndex();
186 m_jobName = jobComboBox->itemText(jobIndex);
187 if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != tr("Any"))) {
188 conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
190 int levelIndex = levelComboBox->currentIndex();
191 if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != tr("Any"))) {
192 conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
194 int statusIndex = statusComboBox->currentIndex();
195 if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != tr("Any"))) {
196 conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
198 int purgedIndex = purgedComboBox->currentIndex();
199 if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != tr("Any"))) {
200 conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
202 int fileSetIndex = fileSetComboBox->currentIndex();
203 if (fileSetIndex != -1)
204 m_filesetName = fileSetComboBox->itemText(fileSetIndex);
205 if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != tr("Any"))) {
206 conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
208 /* If Limit check box For limit by days is checked */
209 if (daysCheckBox->checkState() == Qt::Checked) {
210 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
211 QString since = stamp.toString(Qt::ISODate);
212 conditions.append("Job.Starttime>'" + since + "'");
215 foreach (QString condition, conditions) {
217 query += " WHERE " + condition;
220 query += " AND " + condition;
224 query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
225 /* If Limit check box for limit records returned is checked */
226 if (limitCheckBox->checkState() == Qt::Checked) {
228 limit.setNum(limitSpinBox->value());
229 query += " LIMIT " + limit;
232 /* Set up the Header for the table */
233 QStringList headerlist = (QStringList()
234 << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime")
235 << tr("Job Type") << tr("Job Level") << tr("Job Files")
236 << tr("Job Bytes") << tr("Job Status") << tr("Purged") << tr("File Set"));
237 m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
238 m_purgedIndex = headerlist.indexOf(tr("Purged"));
239 m_typeIndex = headerlist.indexOf(tr("Job Type"));
240 m_statusIndex = headerlist.indexOf(tr("Job Status"));
241 m_startIndex = headerlist.indexOf(tr("Job Starttime"));
242 m_filesIndex = headerlist.indexOf(tr("Job Files"));
243 m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
244 int jobLevelIndex = headerlist.indexOf(tr("Job Level"));
246 /* Initialize the QTableWidget */
247 m_checkCurrentWidget = false;
248 mp_tableWidget->clear();
249 m_checkCurrentWidget = true;
250 mp_tableWidget->setColumnCount(headerlist.size());
251 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
253 if (mainWin->m_sqlDebug) {
254 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
256 if (m_console->sql_cmd(query, results)) {
257 m_resultCount = results.count();
259 QTableWidgetItem* p_tableitem;
261 QStringList fieldlist;
262 mp_tableWidget->setRowCount(results.size());
265 /* Iterate through the record returned from the query */
266 foreach (resultline, results) {
267 fieldlist = resultline.split("\t");
269 bool m_statusIndexDone = false;
270 QString statusCode("");
271 /* Iterate through fields in the record */
272 foreach (field, fieldlist) {
273 field = field.trimmed(); /* strip leading & trailing spaces */
274 if ((column == m_statusIndex) && (!m_statusIndexDone)){
275 m_statusIndexDone = true;
278 p_tableitem = new QTableWidgetItem(field, 1);
279 p_tableitem->setFlags(Qt::ItemIsSelectable);
280 p_tableitem->setForeground(blackBrush);
281 mp_tableWidget->setItem(row, column, p_tableitem);
282 if (column == m_statusIndex)
283 setStatusColor(p_tableitem, statusCode);
284 if (column == m_bytesIndex) {
287 qlonglong bytes = field.toULongLong(&okay);
289 QString test = QString("%1").arg(bytes);
290 mainWin->hrConvert(text, bytes);
291 p_tableitem->setText(text);
292 } else { Pmsg1(000, "conversion error %s\n", field.toUtf8().data()); }
293 } else if (column == m_purgedIndex) {
295 int isPurged = field.toInt(&okay);
297 if (isPurged) { p_tableitem->setText(tr("IS"));
298 } else { p_tableitem->setText(tr("NOT")); }
300 } else if (column == m_typeIndex) {
301 if (field == "B") { p_tableitem->setText(tr("Backup")); }
302 else if (field == "R") { p_tableitem->setText(tr("Restore")); }
303 } else if (column == jobLevelIndex) {
304 if (field == "F") { p_tableitem->setText("Full"); }
305 else if (field == "D") { p_tableitem->setText("Diff"); }
306 else if (field == "I") { p_tableitem->setText("Incr"); }
308 if ((column == m_bytesIndex) || (column == m_filesIndex)){
309 p_tableitem->setTextAlignment(Qt::AlignRight);
317 /* Resize the columns */
318 mp_tableWidget->resizeColumnsToContents();
319 mp_tableWidget->resizeRowsToContents();
320 mp_tableWidget->verticalHeader()->hide();
321 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
322 /* for context sensitive searches, let the user know if there were no
324 QMessageBox::warning(this, "Bat",
325 tr("The Jobs query returned no results.\n"
326 "Press OK to continue?"), QMessageBox::Ok );
330 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
332 QString greenchars("TCR");
333 QString redchars("BEf");
334 QString yellowchars("eDAFSMmsjdctp");
335 if (greenchars.contains(field, Qt::CaseSensitive)) {
336 item->setBackground(Qt::green);
337 } else if (redchars.contains(field, Qt::CaseSensitive)) {
338 item->setBackground(Qt::red);
339 } else if (yellowchars.contains(field, Qt::CaseSensitive)){
340 item->setBackground(Qt::yellow);
345 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
346 * The tree has been populated.
348 void JobList::PgSeltreeWidgetClicked()
357 * Virtual function override of pages function which is called when this page
358 * is visible on the stack
360 void JobList::currentStackItem()
369 * Virtual Function to return the name for the medialist tree widget
371 void JobList::treeWidgetName(QString &desc)
373 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
377 if (m_mediaName != "" ) {
378 desc += "of Volume " + m_mediaName;
380 if (m_clientName != "" ) {
381 desc += "of Client " + m_clientName;
383 if (m_jobName != "" ) {
384 desc += "of Job " + m_jobName;
386 if (m_filesetName != "" ) {
387 desc += "of fileset " + m_filesetName;
393 * This functions much line tableItemChanged for trees like the page selector,
394 * but I will do much less here
396 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
398 if (m_checkCurrentWidget) {
399 int row = currentItem->row();
400 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
401 m_currentJob = jobitem->text();
404 /* include purged action or not */
405 jobitem = mp_tableWidget->item(row, m_purgedIndex);
406 QString purged = jobitem->text();
407 mp_tableWidget->removeAction(actionPurgeFiles);
408 if (purged == "NOT") {
409 mp_tableWidget->addAction(actionPurgeFiles);
411 /* include restore from time and job action or not */
412 jobitem = mp_tableWidget->item(row, m_typeIndex);
413 QString type = jobitem->text();
414 mp_tableWidget->removeAction(actionRestoreFromJob);
415 mp_tableWidget->removeAction(actionRestoreFromTime);
416 if (type == "Backup") {
417 mp_tableWidget->addAction(actionRestoreFromJob);
418 mp_tableWidget->addAction(actionRestoreFromTime);
420 /* include cancel action or not */
421 jobitem = mp_tableWidget->item(row, m_statusIndex);
422 QString status = jobitem->text();
423 mp_tableWidget->removeAction(actionCancelJob);
424 if (status == "Running") {
425 mp_tableWidget->addAction(actionCancelJob);
431 * Function to create connections for context sensitive menu for this and
434 void JobList::createConnections()
436 /* connect to the action specific to this pages class that shows up in the
437 * page selector tree */
438 connect(actionRefreshJobList, SIGNAL(triggered()), this,
439 SLOT(populateTable()));
440 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
442 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
444 graphButton->setEnabled(false);
446 /* for the tableItemChanged to maintain m_currentJob */
447 connect(mp_tableWidget, SIGNAL(
448 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
449 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
451 /* Do what is required for the local context sensitive menu */
454 /* setContextMenuPolicy is required */
455 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
458 mp_tableWidget->addAction(actionRefreshJobList);
459 mp_tableWidget->addAction(actionListJobid);
460 mp_tableWidget->addAction(actionListFilesOnJob);
461 mp_tableWidget->addAction(actionListJobMedia);
462 mp_tableWidget->addAction(actionListVolumes);
463 mp_tableWidget->addAction(actionDeleteJob);
464 mp_tableWidget->addAction(actionPurgeFiles);
465 mp_tableWidget->addAction(actionRestoreFromJob);
466 mp_tableWidget->addAction(actionRestoreFromTime);
467 mp_tableWidget->addAction(actionShowLogForJob);
469 /* Make Connections */
470 connect(actionListJobid, SIGNAL(triggered()), this,
471 SLOT(consoleListJobid()));
472 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
473 SLOT(consoleListFilesOnJob()));
474 connect(actionListJobMedia, SIGNAL(triggered()), this,
475 SLOT(consoleListJobMedia()));
476 connect(actionListVolumes, SIGNAL(triggered()), this,
477 SLOT(consoleListVolumes()));
478 connect(actionDeleteJob, SIGNAL(triggered()), this,
479 SLOT(consoleDeleteJob()));
480 connect(actionPurgeFiles, SIGNAL(triggered()), this,
481 SLOT(consolePurgeFiles()));
482 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
483 SLOT(preRestoreFromJob()));
484 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
485 SLOT(preRestoreFromTime()));
486 connect(actionShowLogForJob, SIGNAL(triggered()), this,
487 SLOT(showLogForJob()));
488 connect(actionCancelJob, SIGNAL(triggered()), this,
489 SLOT(consoleCancelJob()));
490 connect(actionListJobTotals, SIGNAL(triggered()), this,
491 SLOT(consoleListJobTotals()));
493 m_contextActions.append(actionRefreshJobList);
494 m_contextActions.append(actionListJobTotals);
498 * Functions to respond to local context sensitive menu sending console commands
499 * If I could figure out how to make these one function passing a string, Yaaaaaa
501 void JobList::consoleListJobid()
503 QString cmd("list jobid=");
505 if (mainWin->m_longList) { cmd.prepend("l"); }
508 void JobList::consoleListFilesOnJob()
510 QString cmd("list files jobid=");
512 if (mainWin->m_longList) { cmd.prepend("l"); }
515 void JobList::consoleListJobMedia()
517 QString cmd("list jobmedia jobid=");
519 if (mainWin->m_longList) { cmd.prepend("l"); }
522 void JobList::consoleListVolumes()
524 QString cmd("list volumes jobid=");
526 if (mainWin->m_longList) { cmd.prepend("l"); }
529 void JobList::consoleListJobTotals()
531 QString cmd("list jobtotals");
532 if (mainWin->m_longList) { cmd.prepend("l"); }
535 void JobList::consoleDeleteJob()
537 if (QMessageBox::warning(this, "Bat",
538 tr("Are you sure you want to delete?? !!!.\n"
539 "This delete command is used to delete a Job record and all associated catalog"
540 " records that were created. This command operates only on the Catalog"
541 " database and has no effect on the actual data written to a Volume. This"
542 " command can be dangerous and we strongly recommend that you do not use"
543 " it unless you know what you are doing. The Job and all its associated"
544 " records (File and JobMedia) will be deleted from the catalog."
545 "Press OK to proceed with delete operation.?"),
546 QMessageBox::Ok | QMessageBox::Cancel)
547 == QMessageBox::Cancel) { return; }
549 QString cmd("delete job jobid=");
550 cmd += m_selectedJobs;
553 void JobList::consolePurgeFiles()
555 if (QMessageBox::warning(this, "Bat",
556 tr("Are you sure you want to purge ?? !!!.\n"
557 "The Purge command will delete associated Catalog database records from Jobs and"
558 " Volumes without considering the retention period. Purge works only on the"
559 " Catalog database and does not affect data written to Volumes. This command can"
560 " be dangerous because you can delete catalog records associated with current"
561 " backups of files, and we recommend that you do not use it unless you know what"
563 "Press OK to proceed with the purge operation?"),
564 QMessageBox::Ok | QMessageBox::Cancel)
565 == QMessageBox::Cancel) { return; }
567 QString cmd("purge files jobid=");
573 * Subroutine to call preRestore to restore from a select job
575 void JobList::preRestoreFromJob()
577 new prerestorePage(m_currentJob, R_JOBIDLIST);
581 * Subroutine to call preRestore to restore from a select job
583 void JobList::preRestoreFromTime()
585 new prerestorePage(m_currentJob, R_JOBDATETIME);
589 * Subroutine to call class to show the log in the database from that job
591 void JobList::showLogForJob()
593 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
594 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
598 * Cancel a running job
600 void JobList::consoleCancelJob()
602 QString cmd("cancel jobid=");
611 void JobList::graphTable()
614 pass.recordLimitCheck = limitCheckBox->checkState();
615 pass.daysLimitCheck = daysCheckBox->checkState();
616 pass.recordLimitSpin = limitSpinBox->value();
617 pass.daysLimitSpin = daysSpinBox->value();
618 pass.jobCombo = jobComboBox->currentText();
619 pass.clientCombo = clientComboBox->currentText();
620 pass.volumeCombo = volumeComboBox->currentText();
621 pass.fileSetCombo = fileSetComboBox->currentText();
622 pass.purgedCombo = purgedComboBox->currentText();
623 pass.levelCombo = levelComboBox->currentText();
624 pass.statusCombo = statusComboBox->currentText();
626 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
627 new JobPlot(pageSelectorTreeWidgetItem, pass);
632 * Save user settings associated with this page
634 void JobList::writeSettings()
636 QSettings settings(m_console->m_dir->name(), "bat");
637 settings.beginGroup(m_groupText);
638 settings.setValue(m_splitText, m_splitter->saveState());
643 * Read and restore user settings associated with this page
645 void JobList::readSettings()
647 m_groupText = "JobListPage";
648 m_splitText = "splitterSizes_1";
649 QSettings settings(m_console->m_dir->name(), "bat");
650 settings.beginGroup(m_groupText);
651 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
656 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
658 void JobList::selectedJobsGet()
661 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
662 foreach (QTableWidgetItem *sitem, sitems) {
663 int row = sitem->row();
664 if (!rowList.contains(row)) {
671 foreach(int row, rowList) {
672 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
673 if (!first) m_selectedJobs.append(",");
675 m_selectedJobs.append(sitem->text());
677 m_selectedJobsCount = rowList.count();
678 if (m_selectedJobsCount > 1) {
679 QString text = QString("Delete list of %1 Jobs").arg(m_selectedJobsCount);
680 actionDeleteJob->setText(text);
682 actionDeleteJob->setText("Delete Single Job");