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"
40 #include "jobgraphs/jobplot.h"
43 * Constructor for the class
45 JobList::JobList(const QString &mediaName, const QString &clientName,
46 const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
49 m_name = ""; /* treeWidgetName has a virtual override in this class */
50 m_mediaName = mediaName;
51 m_clientName = clientName;
53 m_filesetName = filesetName;
54 m_filesetName = filesetName;
55 pgInitialize(parentTreeWidgetItem);
56 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
57 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
62 if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
64 m_checkCurrentWidget = true;
67 /* Set Defaults for check and spin for limits */
68 limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
69 limitSpinBox->setValue(mainWin->m_recordLimitVal);
70 daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
71 daysSpinBox->setValue(mainWin->m_daysLimitVal);
74 QGridLayout *gridLayout = new QGridLayout(this);
75 gridLayout->setSpacing(6);
76 gridLayout->setMargin(9);
77 gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
79 m_splitter = new QSplitter(Qt::Vertical, this);
80 QScrollArea *area = new QScrollArea();
81 area->setObjectName(QString::fromUtf8("area"));
82 area->setWidget(frame);
83 area->setWidgetResizable(true);
84 m_splitter->addWidget(mp_tableWidget);
85 m_splitter->addWidget(area);
87 gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
92 * Write the m_splitter settings in the destructor
100 * The Meat of the class.
101 * This function will populate the QTableWidget, mp_tablewidget, with
102 * QTableWidgetItems representing the results of a query for what jobs exist on
103 * the media name passed from the constructor stored in m_mediaName.
105 void JobList::populateTable()
109 QBrush blackBrush(Qt::black);
111 if (!m_console->preventInUseConnect())
114 /* Can't do this in constructor because not neccesarily conected in constructor */
116 clientComboBox->addItem(tr("Any"));
117 clientComboBox->addItems(m_console->client_list);
118 int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly);
119 if (clientIndex != -1)
120 clientComboBox->setCurrentIndex(clientIndex);
122 QStringList volumeList;
123 m_console->getVolumeList(volumeList);
124 volumeComboBox->addItem(tr("Any"));
125 volumeComboBox->addItems(volumeList);
126 int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly);
127 if (volumeIndex != -1) {
128 volumeComboBox->setCurrentIndex(volumeIndex);
130 jobComboBox->addItem(tr("Any"));
131 jobComboBox->addItems(m_console->job_list);
132 int jobIndex = jobComboBox->findText(m_jobName, Qt::MatchExactly);
133 if (jobIndex != -1) {
134 jobComboBox->setCurrentIndex(jobIndex);
136 levelComboBox->addItem(tr("Any"));
137 levelComboBox->addItems( QStringList() << "F" << "D" << "I");
138 purgedComboBox->addItem(tr("Any"));
139 purgedComboBox->addItems( QStringList() << "0" << "1");
140 fileSetComboBox->addItem(tr("Any"));
141 fileSetComboBox->addItems(m_console->fileset_list);
142 int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly);
143 if (filesetIndex != -1) {
144 fileSetComboBox->setCurrentIndex(filesetIndex);
146 QStringList statusLongList;
147 m_console->getStatusList(statusLongList);
148 statusComboBox->addItem(tr("Any"));
149 statusComboBox->addItems(statusLongList);
154 int volumeIndex = volumeComboBox->currentIndex();
155 if (volumeIndex != -1)
156 m_mediaName = volumeComboBox->itemText(volumeIndex);
157 QString distinct = "";
158 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
159 query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, "
160 " Client.Name AS Client,"
161 " Job.Starttime AS JobStart, Job.Type AS JobType,"
162 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
163 " Job.JobBytes AS Bytes,"
164 " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
165 " Job.PurgedFiles AS Purged, FileSet.FileSet"
167 " JOIN Client ON (Client.ClientId=Job.ClientId)"
168 " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
169 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
170 QStringList conditions;
171 if (m_mediaName != tr("Any")) {
172 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
173 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
174 conditions.append("Media.VolumeName='" + m_mediaName + "'");
176 int clientIndex = clientComboBox->currentIndex();
177 if (clientIndex != -1)
178 m_clientName = clientComboBox->itemText(clientIndex);
179 if (m_clientName != tr("Any")) {
180 conditions.append("Client.Name='" + m_clientName + "'");
182 int jobIndex = jobComboBox->currentIndex();
184 m_jobName = jobComboBox->itemText(jobIndex);
185 if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != tr("Any"))) {
186 conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
188 int levelIndex = levelComboBox->currentIndex();
189 if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != tr("Any"))) {
190 conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
192 int statusIndex = statusComboBox->currentIndex();
193 if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != tr("Any"))) {
194 conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
196 int purgedIndex = purgedComboBox->currentIndex();
197 if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != tr("Any"))) {
198 conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
200 int fileSetIndex = fileSetComboBox->currentIndex();
201 if (fileSetIndex != -1)
202 m_filesetName = fileSetComboBox->itemText(fileSetIndex);
203 if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != tr("Any"))) {
204 conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
206 /* If Limit check box For limit by days is checked */
207 if (daysCheckBox->checkState() == Qt::Checked) {
208 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
209 QString since = stamp.toString(Qt::ISODate);
210 conditions.append("Job.Starttime>'" + since + "'");
213 foreach (QString condition, conditions) {
215 query += " WHERE " + condition;
218 query += " AND " + condition;
222 query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
223 /* If Limit check box for limit records returned is checked */
224 if (limitCheckBox->checkState() == Qt::Checked) {
226 limit.setNum(limitSpinBox->value());
227 query += " LIMIT " + limit;
230 /* Set up the Header for the table */
231 QStringList headerlist = (QStringList()
232 << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime")
233 << tr("Job Type") << tr("Job Level") << tr("Job Files")
234 << tr("Job Bytes") << tr("Job Status") << tr("Purged") << tr("File Set"));
235 m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
236 m_purgedIndex = headerlist.indexOf(tr("Purged"));
237 m_typeIndex = headerlist.indexOf(tr("Job Type"));
238 m_statusIndex = headerlist.indexOf(tr("Job Status"));
239 m_startIndex = headerlist.indexOf(tr("Job Starttime"));
240 m_filesIndex = headerlist.indexOf(tr("Job Files"));
241 m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
242 int jobLevelIndex = headerlist.indexOf(tr("Job Level"));
244 /* Initialize the QTableWidget */
245 m_checkCurrentWidget = false;
246 mp_tableWidget->clear();
247 m_checkCurrentWidget = true;
248 mp_tableWidget->setColumnCount(headerlist.size());
249 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
251 if (mainWin->m_sqlDebug) {
252 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
254 if (m_console->sql_cmd(query, results)) {
255 m_resultCount = results.count();
257 QTableWidgetItem* p_tableitem;
259 QStringList fieldlist;
260 mp_tableWidget->setRowCount(results.size());
263 /* Iterate through the record returned from the query */
264 foreach (resultline, results) {
265 fieldlist = resultline.split("\t");
267 bool m_statusIndexDone = false;
268 QString statusCode("");
269 /* Iterate through fields in the record */
270 foreach (field, fieldlist) {
271 field = field.trimmed(); /* strip leading & trailing spaces */
272 if ((column == m_statusIndex) && (!m_statusIndexDone)){
273 m_statusIndexDone = true;
276 p_tableitem = new QTableWidgetItem(field, 1);
277 p_tableitem->setFlags(Qt::ItemIsSelectable);
278 p_tableitem->setForeground(blackBrush);
279 mp_tableWidget->setItem(row, column, p_tableitem);
280 if (column == m_statusIndex)
281 setStatusColor(p_tableitem, statusCode);
282 if (column == m_bytesIndex) {
285 qlonglong bytes = field.toULongLong(&okay);
287 QString test = QString("%1").arg(bytes);
288 mainWin->hrConvert(text, bytes);
289 p_tableitem->setText(text);
290 } else { Pmsg1(000, "conversion error %s\n", field.toUtf8().data()); }
291 } else if (column == m_purgedIndex) {
293 int isPurged = field.toInt(&okay);
295 if (isPurged) { p_tableitem->setText(tr("IS"));
296 } else { p_tableitem->setText(tr("NOT")); }
298 } else if (column == m_typeIndex) {
299 if (field == "B") { p_tableitem->setText(tr("Backup")); }
300 else if (field == "R") { p_tableitem->setText(tr("Restore")); }
301 } else if (column == jobLevelIndex) {
302 if (field == "F") { p_tableitem->setText("Full"); }
303 else if (field == "D") { p_tableitem->setText("Diff"); }
304 else if (field == "I") { p_tableitem->setText("Incr"); }
306 if ((column == m_bytesIndex) || (column == m_filesIndex)){
307 p_tableitem->setTextAlignment(Qt::AlignRight);
315 /* Resize the columns */
316 mp_tableWidget->resizeColumnsToContents();
317 mp_tableWidget->resizeRowsToContents();
318 mp_tableWidget->verticalHeader()->hide();
319 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
320 /* for context sensitive searches, let the user know if there were no
322 QMessageBox::warning(this, "Bat",
323 tr("The Jobs query returned no results.\n"
324 "Press OK to continue?"), QMessageBox::Ok );
328 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
330 QString greenchars("TCR");
331 QString redchars("BEf");
332 QString yellowchars("eDAFSMmsjdctp");
333 if (greenchars.contains(field, Qt::CaseSensitive)) {
334 item->setBackground(Qt::green);
335 } else if (redchars.contains(field, Qt::CaseSensitive)) {
336 item->setBackground(Qt::red);
337 } else if (yellowchars.contains(field, Qt::CaseSensitive)){
338 item->setBackground(Qt::yellow);
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()));
439 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
440 /* for the tableItemChanged to maintain m_currentJob */
441 connect(mp_tableWidget, SIGNAL(
442 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
443 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
445 /* Do what is required for the local context sensitive menu */
448 /* setContextMenuPolicy is required */
449 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
452 mp_tableWidget->addAction(actionRefreshJobList);
453 mp_tableWidget->addAction(actionListJobid);
454 mp_tableWidget->addAction(actionListFilesOnJob);
455 mp_tableWidget->addAction(actionListJobMedia);
456 mp_tableWidget->addAction(actionListVolumes);
457 mp_tableWidget->addAction(actionDeleteJob);
458 mp_tableWidget->addAction(actionPurgeFiles);
459 mp_tableWidget->addAction(actionRestoreFromJob);
460 mp_tableWidget->addAction(actionRestoreFromTime);
461 mp_tableWidget->addAction(actionShowLogForJob);
463 /* Make Connections */
464 connect(actionListJobid, SIGNAL(triggered()), this,
465 SLOT(consoleListJobid()));
466 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
467 SLOT(consoleListFilesOnJob()));
468 connect(actionListJobMedia, SIGNAL(triggered()), this,
469 SLOT(consoleListJobMedia()));
470 connect(actionListVolumes, SIGNAL(triggered()), this,
471 SLOT(consoleListVolumes()));
472 connect(actionDeleteJob, SIGNAL(triggered()), this,
473 SLOT(consoleDeleteJob()));
474 connect(actionPurgeFiles, SIGNAL(triggered()), this,
475 SLOT(consolePurgeFiles()));
476 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
477 SLOT(preRestoreFromJob()));
478 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
479 SLOT(preRestoreFromTime()));
480 connect(actionShowLogForJob, SIGNAL(triggered()), this,
481 SLOT(showLogForJob()));
482 connect(actionCancelJob, SIGNAL(triggered()), this,
483 SLOT(consoleCancelJob()));
484 connect(actionListJobTotals, SIGNAL(triggered()), this,
485 SLOT(consoleListJobTotals()));
487 m_contextActions.append(actionRefreshJobList);
488 m_contextActions.append(actionListJobTotals);
492 * Functions to respond to local context sensitive menu sending console commands
493 * If I could figure out how to make these one function passing a string, Yaaaaaa
495 void JobList::consoleListJobid()
497 QString cmd("list jobid=");
499 if (mainWin->m_longList) { cmd.prepend("l"); }
502 void JobList::consoleListFilesOnJob()
504 QString cmd("list files jobid=");
506 if (mainWin->m_longList) { cmd.prepend("l"); }
509 void JobList::consoleListJobMedia()
511 QString cmd("list jobmedia jobid=");
513 if (mainWin->m_longList) { cmd.prepend("l"); }
516 void JobList::consoleListVolumes()
518 QString cmd("list volumes jobid=");
520 if (mainWin->m_longList) { cmd.prepend("l"); }
523 void JobList::consoleListJobTotals()
525 QString cmd("list jobtotals");
526 if (mainWin->m_longList) { cmd.prepend("l"); }
529 void JobList::consoleDeleteJob()
531 if (QMessageBox::warning(this, "Bat",
532 tr("Are you sure you want to delete?? !!!.\n"
533 "This delete command is used to delete a Job record and all associated catalog"
534 " records that were created. This command operates only on the Catalog"
535 " database and has no effect on the actual data written to a Volume. This"
536 " command can be dangerous and we strongly recommend that you do not use"
537 " it unless you know what you are doing. The Job and all its associated"
538 " records (File and JobMedia) will be deleted from the catalog."
539 "Press OK to proceed with delete operation.?"),
540 QMessageBox::Ok | QMessageBox::Cancel)
541 == QMessageBox::Cancel) { return; }
543 QString cmd("delete job jobid=");
544 cmd += m_selectedJobs;
547 void JobList::consolePurgeFiles()
549 if (QMessageBox::warning(this, "Bat",
550 tr("Are you sure you want to purge ?? !!!.\n"
551 "The Purge command will delete associated Catalog database records from Jobs and"
552 " Volumes without considering the retention period. Purge works only on the"
553 " Catalog database and does not affect data written to Volumes. This command can"
554 " be dangerous because you can delete catalog records associated with current"
555 " backups of files, and we recommend that you do not use it unless you know what"
557 "Press OK to proceed with the purge operation?"),
558 QMessageBox::Ok | QMessageBox::Cancel)
559 == QMessageBox::Cancel) { return; }
561 QString cmd("purge files jobid=");
567 * Subroutine to call preRestore to restore from a select job
569 void JobList::preRestoreFromJob()
571 new prerestorePage(m_currentJob, R_JOBIDLIST);
575 * Subroutine to call preRestore to restore from a select job
577 void JobList::preRestoreFromTime()
579 new prerestorePage(m_currentJob, R_JOBDATETIME);
583 * Subroutine to call class to show the log in the database from that job
585 void JobList::showLogForJob()
587 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
588 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
592 * Cancel a running job
594 void JobList::consoleCancelJob()
596 QString cmd("cancel jobid=");
604 void JobList::graphTable()
607 pass.recordLimitCheck = limitCheckBox->checkState();
608 pass.daysLimitCheck = daysCheckBox->checkState();
609 pass.recordLimitSpin = limitSpinBox->value();
610 pass.daysLimitSpin = daysSpinBox->value();
611 pass.jobCombo = jobComboBox->currentText();
612 pass.clientCombo = clientComboBox->currentText();
613 pass.volumeCombo = volumeComboBox->currentText();
614 pass.fileSetCombo = fileSetComboBox->currentText();
615 pass.purgedCombo = purgedComboBox->currentText();
616 pass.levelCombo = levelComboBox->currentText();
617 pass.statusCombo = statusComboBox->currentText();
619 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
620 new JobPlot(pageSelectorTreeWidgetItem, pass);
623 * Save user settings associated with this page
625 void JobList::writeSettings()
627 QSettings settings(m_console->m_dir->name(), "bat");
628 settings.beginGroup(m_groupText);
629 settings.setValue(m_splitText, m_splitter->saveState());
634 * Read and restore user settings associated with this page
636 void JobList::readSettings()
638 m_groupText = "JobListPage";
639 m_splitText = "splitterSizes_1";
640 QSettings settings(m_console->m_dir->name(), "bat");
641 settings.beginGroup(m_groupText);
642 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
647 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
649 void JobList::selectedJobsGet()
652 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
653 foreach (QTableWidgetItem *sitem, sitems) {
654 int row = sitem->row();
655 if (!rowList.contains(row)) {
662 foreach(int row, rowList) {
663 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
664 if (!first) m_selectedJobs.append(",");
666 m_selectedJobs.append(sitem->text());
668 m_selectedJobsCount = rowList.count();
669 if (m_selectedJobsCount > 1) {
670 QString text = QString("Delete list of %1 Jobs").arg(m_selectedJobsCount);
671 actionDeleteJob->setText(text);
673 actionDeleteJob->setText("Delete Single Job");