2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2007 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("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("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("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("Any");
137 levelComboBox->addItems( QStringList() << "F" << "D" << "I");
138 purgedComboBox->addItem("Any");
139 purgedComboBox->addItems( QStringList() << "0" << "1");
140 fileSetComboBox->addItem("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("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 != "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 != "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 != "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) != "Any")) {
186 conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
188 int levelIndex = levelComboBox->currentIndex();
189 if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
190 conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
192 int statusIndex = statusComboBox->currentIndex();
193 if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
194 conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
196 int purgedIndex = purgedComboBox->currentIndex();
197 if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "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) != "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 << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type"
233 << "Job Level" << "Job Files" << "Job Bytes" << "Job Status" << "Purged" << "File Set" );
234 m_jobIdIndex = headerlist.indexOf("Job Id");
235 m_purgedIndex = headerlist.indexOf("Purged");
236 m_typeIndex = headerlist.indexOf("Job Type");
237 m_statusIndex = headerlist.indexOf("Job Status");
238 m_startIndex = headerlist.indexOf("Job Starttime");
239 m_filesIndex = headerlist.indexOf("Job Files");
240 m_bytesIndex = headerlist.indexOf("Job Bytes");
241 int jobLevelIndex = headerlist.indexOf("Job Level");
243 /* Initialize the QTableWidget */
244 m_checkCurrentWidget = false;
245 mp_tableWidget->clear();
246 m_checkCurrentWidget = true;
247 mp_tableWidget->setColumnCount(headerlist.size());
248 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
250 if (mainWin->m_sqlDebug) {
251 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
253 if (m_console->sql_cmd(query, results)) {
254 m_resultCount = results.count();
256 QTableWidgetItem* p_tableitem;
258 QStringList fieldlist;
259 mp_tableWidget->setRowCount(results.size());
262 /* Iterate through the record returned from the query */
263 foreach (resultline, results) {
264 fieldlist = resultline.split("\t");
266 bool m_statusIndexDone = false;
267 QString statusCode("");
268 /* Iterate through fields in the record */
269 foreach (field, fieldlist) {
270 field = field.trimmed(); /* strip leading & trailing spaces */
271 if ((column == m_statusIndex) && (!m_statusIndexDone)){
272 m_statusIndexDone = true;
275 p_tableitem = new QTableWidgetItem(field, 1);
276 p_tableitem->setFlags(Qt::ItemIsSelectable);
277 p_tableitem->setForeground(blackBrush);
278 mp_tableWidget->setItem(row, column, p_tableitem);
279 if (column == m_statusIndex)
280 setStatusColor(p_tableitem, statusCode);
281 if (column == m_bytesIndex) {
284 qlonglong bytes = field.toULongLong(&okay);
286 QString test = QString("%1").arg(bytes);
287 mainWin->hrConvert(text, bytes);
288 p_tableitem->setText(text);
289 } else { Pmsg1(000, "conversion error %s\n", field.toUtf8().data()); }
290 } else if (column == m_purgedIndex) {
292 int isPurged = field.toInt(&okay);
294 if (isPurged) { p_tableitem->setText("IS");
295 } else { p_tableitem->setText("NOT"); }
297 } else if (column == m_typeIndex) {
298 if (field == "B") { p_tableitem->setText("Backup"); }
299 else if (field == "R") { p_tableitem->setText("Restore"); }
300 } else if (column == jobLevelIndex) {
301 if (field == "F") { p_tableitem->setText("Full"); }
302 else if (field == "D") { p_tableitem->setText("Diff"); }
303 else if (field == "I") { p_tableitem->setText("Incr"); }
305 if ((column == m_bytesIndex) || (column == m_filesIndex)){
306 p_tableitem->setTextAlignment(Qt::AlignRight);
314 /* Resize the columns */
315 mp_tableWidget->resizeColumnsToContents();
316 mp_tableWidget->resizeRowsToContents();
317 mp_tableWidget->verticalHeader()->hide();
318 if ((m_mediaName != "Any") && (m_resultCount == 0)){
319 /* for context sensitive searches, let the user know if there were no
321 QMessageBox::warning(this, tr("Bat"),
322 tr("The Jobs query returned no results.\n"
323 "Press OK to continue?"), QMessageBox::Ok );
327 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
329 QString greenchars("TCR");
330 QString redchars("BEf");
331 QString yellowchars("eDAFSMmsjdctp");
332 if (greenchars.contains(field, Qt::CaseSensitive)) {
333 item->setBackground(Qt::green);
334 } else if (redchars.contains(field, Qt::CaseSensitive)) {
335 item->setBackground(Qt::red);
336 } else if (yellowchars.contains(field, Qt::CaseSensitive)){
337 item->setBackground(Qt::yellow);
342 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
343 * The tree has been populated.
345 void JobList::PgSeltreeWidgetClicked()
354 * Virtual function override of pages function which is called when this page
355 * is visible on the stack
357 void JobList::currentStackItem()
366 * Virtual Function to return the name for the medialist tree widget
368 void JobList::treeWidgetName(QString &desc)
370 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
374 if (m_mediaName != "" ) {
375 desc += "of Volume " + m_mediaName;
377 if (m_clientName != "" ) {
378 desc += "of Client " + m_clientName;
380 if (m_jobName != "" ) {
381 desc += "of Job " + m_jobName;
383 if (m_filesetName != "" ) {
384 desc += "of fileset " + m_filesetName;
390 * This functions much line tableItemChanged for trees like the page selector,
391 * but I will do much less here
393 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
395 if (m_checkCurrentWidget) {
396 int row = currentItem->row();
397 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
398 m_currentJob = jobitem->text();
401 /* include purged action or not */
402 jobitem = mp_tableWidget->item(row, m_purgedIndex);
403 QString purged = jobitem->text();
404 mp_tableWidget->removeAction(actionPurgeFiles);
406 mp_tableWidget->addAction(actionPurgeFiles);
408 /* include restore from time and job action or not */
409 jobitem = mp_tableWidget->item(row, m_typeIndex);
410 QString type = jobitem->text();
411 mp_tableWidget->removeAction(actionRestoreFromJob);
412 mp_tableWidget->removeAction(actionRestoreFromTime);
414 mp_tableWidget->addAction(actionRestoreFromJob);
415 mp_tableWidget->addAction(actionRestoreFromTime);
417 /* include cancel action or not */
418 jobitem = mp_tableWidget->item(row, m_statusIndex);
419 QString status = jobitem->text();
420 mp_tableWidget->removeAction(actionCancelJob);
421 if (status == "Running") {
422 mp_tableWidget->addAction(actionCancelJob);
428 * Function to create connections for context sensitive menu for this and
431 void JobList::createConnections()
433 /* connect to the action specific to this pages class that shows up in the
434 * page selector tree */
435 connect(actionRefreshJobList, SIGNAL(triggered()), this,
436 SLOT(populateTable()));
437 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
438 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
439 /* for the tableItemChanged to maintain m_currentJob */
440 connect(mp_tableWidget, SIGNAL(
441 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
442 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
444 /* Do what is required for the local context sensitive menu */
447 /* setContextMenuPolicy is required */
448 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
451 mp_tableWidget->addAction(actionRefreshJobList);
452 mp_tableWidget->addAction(actionListJobid);
453 mp_tableWidget->addAction(actionListFilesOnJob);
454 mp_tableWidget->addAction(actionListJobMedia);
455 mp_tableWidget->addAction(actionListVolumes);
456 mp_tableWidget->addAction(actionDeleteJob);
457 mp_tableWidget->addAction(actionPurgeFiles);
458 mp_tableWidget->addAction(actionRestoreFromJob);
459 mp_tableWidget->addAction(actionRestoreFromTime);
460 mp_tableWidget->addAction(actionShowLogForJob);
462 /* Make Connections */
463 connect(actionListJobid, SIGNAL(triggered()), this,
464 SLOT(consoleListJobid()));
465 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
466 SLOT(consoleListFilesOnJob()));
467 connect(actionListJobMedia, SIGNAL(triggered()), this,
468 SLOT(consoleListJobMedia()));
469 connect(actionListVolumes, SIGNAL(triggered()), this,
470 SLOT(consoleListVolumes()));
471 connect(actionDeleteJob, SIGNAL(triggered()), this,
472 SLOT(consoleDeleteJob()));
473 connect(actionPurgeFiles, SIGNAL(triggered()), this,
474 SLOT(consolePurgeFiles()));
475 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
476 SLOT(preRestoreFromJob()));
477 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
478 SLOT(preRestoreFromTime()));
479 connect(actionShowLogForJob, SIGNAL(triggered()), this,
480 SLOT(showLogForJob()));
481 connect(actionCancelJob, SIGNAL(triggered()), this,
482 SLOT(consoleCancelJob()));
483 connect(actionListJobTotals, SIGNAL(triggered()), this,
484 SLOT(consoleListJobTotals()));
486 m_contextActions.append(actionRefreshJobList);
487 m_contextActions.append(actionListJobTotals);
491 * Functions to respond to local context sensitive menu sending console commands
492 * If I could figure out how to make these one function passing a string, Yaaaaaa
494 void JobList::consoleListJobid()
496 QString cmd("list jobid=");
498 if (mainWin->m_longList) { cmd.prepend("l"); }
501 void JobList::consoleListFilesOnJob()
503 QString cmd("list files jobid=");
505 if (mainWin->m_longList) { cmd.prepend("l"); }
508 void JobList::consoleListJobMedia()
510 QString cmd("list jobmedia jobid=");
512 if (mainWin->m_longList) { cmd.prepend("l"); }
515 void JobList::consoleListVolumes()
517 QString cmd("list volumes jobid=");
519 if (mainWin->m_longList) { cmd.prepend("l"); }
522 void JobList::consoleListJobTotals()
524 QString cmd("list jobtotals");
525 if (mainWin->m_longList) { cmd.prepend("l"); }
528 void JobList::consoleDeleteJob()
530 if (QMessageBox::warning(this, tr("Bat"),
531 tr("Are you sure you want to delete?? !!!.\n"
532 "This delete command is used to delete a Job record and all associated catalog"
533 " records that were created. This command operates only on the Catalog"
534 " database and has no effect on the actual data written to a Volume. This"
535 " command can be dangerous and we strongly recommend that you do not use"
536 " it unless you know what you are doing. The Job and all its associated"
537 " records (File and JobMedia) will be deleted from the catalog."
538 "Press OK to proceed with delete operation.?"),
539 QMessageBox::Ok | QMessageBox::Cancel)
540 == QMessageBox::Cancel) { return; }
542 QString cmd("delete job jobid=");
543 cmd += m_selectedJobs;
546 void JobList::consolePurgeFiles()
548 if (QMessageBox::warning(this, tr("Bat"),
549 tr("Are you sure you want to purge ?? !!!.\n"
550 "The Purge command will delete associated Catalog database records from Jobs and"
551 " Volumes without considering the retention period. Purge works only on the"
552 " Catalog database and does not affect data written to Volumes. This command can"
553 " be dangerous because you can delete catalog records associated with current"
554 " backups of files, and we recommend that you do not use it unless you know what"
556 "Press OK to proceed with the purge operation?"),
557 QMessageBox::Ok | QMessageBox::Cancel)
558 == QMessageBox::Cancel) { return; }
560 QString cmd("purge files jobid=");
566 * Subroutine to call preRestore to restore from a select job
568 void JobList::preRestoreFromJob()
570 new prerestorePage(m_currentJob, R_JOBIDLIST);
574 * Subroutine to call preRestore to restore from a select job
576 void JobList::preRestoreFromTime()
578 new prerestorePage(m_currentJob, R_JOBDATETIME);
582 * Subroutine to call class to show the log in the database from that job
584 void JobList::showLogForJob()
586 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
587 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
591 * Cancel a running job
593 void JobList::consoleCancelJob()
595 QString cmd("cancel jobid=");
603 void JobList::graphTable()
606 pass.recordLimitCheck = limitCheckBox->checkState();
607 pass.daysLimitCheck = daysCheckBox->checkState();
608 pass.recordLimitSpin = limitSpinBox->value();
609 pass.daysLimitSpin = daysSpinBox->value();
610 pass.jobCombo = jobComboBox->currentText();
611 pass.clientCombo = clientComboBox->currentText();
612 pass.volumeCombo = volumeComboBox->currentText();
613 pass.fileSetCombo = fileSetComboBox->currentText();
614 pass.purgedCombo = purgedComboBox->currentText();
615 pass.levelCombo = levelComboBox->currentText();
616 pass.statusCombo = statusComboBox->currentText();
618 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
619 new JobPlot(pageSelectorTreeWidgetItem, pass);
622 * Save user settings associated with this page
624 void JobList::writeSettings()
626 QSettings settings(m_console->m_dir->name(), "bat");
627 settings.beginGroup(m_groupText);
628 settings.setValue(m_splitText, m_splitter->saveState());
633 * Read and restore user settings associated with this page
635 void JobList::readSettings()
637 m_groupText = "JobListPage";
638 m_splitText = "splitterSizes_1";
639 QSettings settings(m_console->m_dir->name(), "bat");
640 settings.beginGroup(m_groupText);
641 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
646 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
648 void JobList::selectedJobsGet()
651 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
652 foreach (QTableWidgetItem *sitem, sitems) {
653 int row = sitem->row();
654 if (!rowList.contains(row)) {
661 foreach(int row, rowList) {
662 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
663 if (!first) m_selectedJobs.append(",");
665 m_selectedJobs.append(sitem->text());
667 m_selectedJobsCount = rowList.count();
668 if (m_selectedJobsCount > 1) {
669 QString text = QString("Delete list of %1 Jobs").arg(m_selectedJobsCount);
670 actionDeleteJob->setText(text);
672 actionDeleteJob->setText("Delete Single Job");