2 Bacula® - The Network Backup Solution
4 Copyright (C) 2000-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 *m_gridLayout = new QGridLayout(this);
75 m_gridLayout->setSpacing(6);
76 m_gridLayout->setMargin(9);
77 m_gridLayout->setObjectName(QString::fromUtf8("m_gridLayout"));
79 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 splitter->addWidget(mp_tableWidget);
85 splitter->addWidget(area);
87 m_gridLayout->addWidget(splitter, 0, 0, 1, 1);
92 * Write the 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 query += "SELECT DISTINCT Job.Jobid AS Id, Job.Name AS JobName, Client.Name AS Client,"
158 " Job.Starttime AS JobStart, Job.Type AS JobType,"
159 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
160 " Job.JobBytes AS Bytes,"
161 " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
162 " Job.PurgedFiles AS Purged, FileSet.FileSet"
164 " LEFT OUTER JOIN Client ON (Client.ClientId=Job.ClientId)"
165 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)"
166 " LEFT OUTER JOIN Status ON (Job.JobStatus=Status.JobStatus)"
167 " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)"
168 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)";
169 QStringList conditions;
170 if (m_mediaName != "Any") {
171 conditions.append("Media.VolumeName='" + m_mediaName + "'");
173 int clientIndex = clientComboBox->currentIndex();
174 if (clientIndex != -1)
175 m_clientName = clientComboBox->itemText(clientIndex);
176 if (m_clientName != "Any") {
177 conditions.append("Client.Name='" + m_clientName + "'");
179 int jobIndex = jobComboBox->currentIndex();
181 m_jobName = jobComboBox->itemText(jobIndex);
182 if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
183 conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
185 int levelIndex = levelComboBox->currentIndex();
186 if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
187 conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
189 int statusIndex = statusComboBox->currentIndex();
190 if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
191 conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
193 int purgedIndex = purgedComboBox->currentIndex();
194 if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
195 conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
197 int fileSetIndex = fileSetComboBox->currentIndex();
198 if (fileSetIndex != -1)
199 m_filesetName = fileSetComboBox->itemText(fileSetIndex);
200 if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
201 conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
203 /* If Limit check box For limit by days is checked */
204 if (daysCheckBox->checkState() == Qt::Checked) {
205 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
206 QString since = stamp.toString(Qt::ISODate);
207 conditions.append("Job.Starttime>'" + since + "'");
210 foreach (QString condition, conditions) {
212 query += " WHERE " + condition;
215 query += " AND " + condition;
219 query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
220 /* If Limit check box for limit records returned is checked */
221 if (limitCheckBox->checkState() == Qt::Checked) {
223 limit.setNum(limitSpinBox->value());
224 query += " LIMIT " + limit;
227 /* Set up the Header for the table */
228 QStringList headerlist = (QStringList()
229 << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type"
230 << "Job Level" << "Job Files" << "Job Bytes" << "Job Status" << "Purged" << "File Set" );
231 m_purgedIndex = headerlist.indexOf("Purged");
232 m_typeIndex = headerlist.indexOf("Job Type");
233 m_statusIndex = headerlist.indexOf("Job Status");
234 m_startIndex = headerlist.indexOf("Job Starttime");
235 m_filesIndex = headerlist.indexOf("Job Files");
236 m_bytesIndex = headerlist.indexOf("Job Bytes");
238 /* Initialize the QTableWidget */
239 m_checkCurrentWidget = false;
240 mp_tableWidget->clear();
241 m_checkCurrentWidget = true;
242 mp_tableWidget->setColumnCount(headerlist.size());
243 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
245 if (mainWin->m_sqlDebug) {
246 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
248 if (m_console->sql_cmd(query, results)) {
249 m_resultCount = results.count();
251 QTableWidgetItem* p_tableitem;
253 QStringList fieldlist;
254 mp_tableWidget->setRowCount(results.size());
257 /* Iterate through the record returned from the query */
258 foreach (resultline, results) {
259 fieldlist = resultline.split("\t");
261 bool m_statusIndexDone = false;
262 QString statusCode("");
263 /* Iterate through fields in the record */
264 foreach (field, fieldlist) {
265 field = field.trimmed(); /* strip leading & trailing spaces */
266 if ((column == m_statusIndex) && (!m_statusIndexDone)){
267 m_statusIndexDone = true;
270 p_tableitem = new QTableWidgetItem(field,1);
271 p_tableitem->setFlags(0);
272 p_tableitem->setForeground(blackBrush);
273 mp_tableWidget->setItem(row, column, p_tableitem);
274 if (column == m_statusIndex)
275 setStatusColor(p_tableitem, statusCode);
282 /* Resize the columns */
283 mp_tableWidget->resizeColumnsToContents();
284 mp_tableWidget->resizeRowsToContents();
285 mp_tableWidget->verticalHeader()->hide();
286 if ((m_mediaName != "Any") && (m_resultCount == 0)){
287 /* for context sensitive searches, let the user know if there were no
289 QMessageBox::warning(this, tr("Bat"),
290 tr("The Jobs query returned no results.\n"
291 "Press OK to continue?"), QMessageBox::Ok );
295 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
297 QString greenchars("TCR");
298 QString redchars("BEf");
299 QString yellowchars("eDAFSMmsjdctp");
300 if (greenchars.contains(field, Qt::CaseSensitive)) {
301 item->setBackground(Qt::green);
302 } else if (redchars.contains(field, Qt::CaseSensitive)) {
303 item->setBackground(Qt::red);
304 } else if (yellowchars.contains(field, Qt::CaseSensitive)){
305 item->setBackground(Qt::yellow);
310 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
311 * The tree has been populated.
313 void JobList::PgSeltreeWidgetClicked()
322 * Virtual function override of pages function which is called when this page
323 * is visible on the stack
325 void JobList::currentStackItem()
334 * Virtual Function to return the name for the medialist tree widget
336 void JobList::treeWidgetName(QString &desc)
338 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
342 if (m_mediaName != "" ) {
343 desc += "of Volume " + m_mediaName;
345 if (m_clientName != "" ) {
346 desc += "of Client " + m_clientName;
348 if (m_jobName != "" ) {
349 desc += "of Job " + m_jobName;
351 if (m_filesetName != "" ) {
352 desc += "of fileset " + m_filesetName;
358 * This functions much line tableItemChanged for trees like the page selector,
359 * but I will do much less here
361 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
363 if (m_checkCurrentWidget) {
364 int row = currentItem->row();
365 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
366 m_currentJob = jobitem->text();
368 /* include purged action or not */
369 jobitem = mp_tableWidget->item(row, m_purgedIndex);
370 QString purged = jobitem->text();
371 mp_tableWidget->removeAction(actionPurgeFiles);
373 mp_tableWidget->addAction(actionPurgeFiles);
375 /* include restore from time and job action or not */
376 jobitem = mp_tableWidget->item(row, m_typeIndex);
377 QString type = jobitem->text();
378 mp_tableWidget->removeAction(actionRestoreFromJob);
379 mp_tableWidget->removeAction(actionRestoreFromTime);
381 mp_tableWidget->addAction(actionRestoreFromJob);
382 mp_tableWidget->addAction(actionRestoreFromTime);
384 /* include cancel action or not */
385 jobitem = mp_tableWidget->item(row, m_statusIndex);
386 QString status = jobitem->text();
387 mp_tableWidget->removeAction(actionCancelJob);
388 if (status == "Running") {
389 mp_tableWidget->addAction(actionCancelJob);
395 * Function to create connections for context sensitive menu for this and
398 void JobList::createConnections()
400 /* connect to the action specific to this pages class that shows up in the
401 * page selector tree */
402 connect(actionRefreshJobList, SIGNAL(triggered()), this,
403 SLOT(populateTable()));
404 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
405 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
406 /* for the tableItemChanged to maintain m_currentJob */
407 connect(mp_tableWidget, SIGNAL(
408 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
409 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
411 /* Do what is required for the local context sensitive menu */
414 /* setContextMenuPolicy is required */
415 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
418 mp_tableWidget->addAction(actionRefreshJobList);
419 mp_tableWidget->addAction(actionListJobid);
420 mp_tableWidget->addAction(actionListFilesOnJob);
421 mp_tableWidget->addAction(actionListJobMedia);
422 mp_tableWidget->addAction(actionListVolumes);
423 mp_tableWidget->addAction(actionDeleteJob);
424 mp_tableWidget->addAction(actionPurgeFiles);
425 mp_tableWidget->addAction(actionRestoreFromJob);
426 mp_tableWidget->addAction(actionRestoreFromTime);
427 mp_tableWidget->addAction(actionShowLogForJob);
429 /* Make Connections */
430 connect(actionListJobid, SIGNAL(triggered()), this,
431 SLOT(consoleListJobid()));
432 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
433 SLOT(consoleListFilesOnJob()));
434 connect(actionListJobMedia, SIGNAL(triggered()), this,
435 SLOT(consoleListJobMedia()));
436 connect(actionListVolumes, SIGNAL(triggered()), this,
437 SLOT(consoleListVolumes()));
438 connect(actionDeleteJob, SIGNAL(triggered()), this,
439 SLOT(consoleDeleteJob()));
440 connect(actionPurgeFiles, SIGNAL(triggered()), this,
441 SLOT(consolePurgeFiles()));
442 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
443 SLOT(preRestoreFromJob()));
444 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
445 SLOT(preRestoreFromTime()));
446 connect(actionShowLogForJob, SIGNAL(triggered()), this,
447 SLOT(showLogForJob()));
448 connect(actionCancelJob, SIGNAL(triggered()), this,
449 SLOT(consoleCancelJob()));
450 connect(actionListJobTotals, SIGNAL(triggered()), this,
451 SLOT(consoleListJobTotals()));
453 m_contextActions.append(actionRefreshJobList);
454 m_contextActions.append(actionListJobTotals);
458 * Functions to respond to local context sensitive menu sending console commands
459 * If I could figure out how to make these one function passing a string, Yaaaaaa
461 void JobList::consoleListJobid()
463 QString cmd("list jobid=");
465 if (mainWin->m_longList) { cmd.prepend("l"); }
468 void JobList::consoleListFilesOnJob()
470 QString cmd("list files jobid=");
472 if (mainWin->m_longList) { cmd.prepend("l"); }
475 void JobList::consoleListJobMedia()
477 QString cmd("list jobmedia jobid=");
479 if (mainWin->m_longList) { cmd.prepend("l"); }
482 void JobList::consoleListVolumes()
484 QString cmd("list volumes jobid=");
486 if (mainWin->m_longList) { cmd.prepend("l"); }
489 void JobList::consoleListJobTotals()
491 QString cmd("list jobtotals");
493 if (mainWin->m_longList) { cmd.prepend("l"); }
496 void JobList::consoleDeleteJob()
498 if (QMessageBox::warning(this, tr("Bat"),
499 tr("Are you sure you want to delete?? !!!.\n"
500 "This delete command is used to delete a Job record and all associated catalog"
501 " records that were created. This command operates only on the Catalog"
502 " database and has no effect on the actual data written to a Volume. This"
503 " command can be dangerous and we strongly recommend that you do not use"
504 " it unless you know what you are doing. The Job and all its associated"
505 " records (File and JobMedia) will be deleted from the catalog."
506 "Press OK to proceed with delete operation.?"),
507 QMessageBox::Ok | QMessageBox::Cancel)
508 == QMessageBox::Cancel) { return; }
510 QString cmd("delete job jobid=");
514 void JobList::consolePurgeFiles()
516 if (QMessageBox::warning(this, tr("Bat"),
517 tr("Are you sure you want to purge ?? !!!.\n"
518 "The Purge command will delete associated Catalog database records from Jobs and"
519 " Volumes without considering the retention period. Purge works only on the"
520 " Catalog database and does not affect data written to Volumes. This command can"
521 " be dangerous because you can delete catalog records associated with current"
522 " backups of files, and we recommend that you do not use it unless you know what"
524 "Press OK to proceed with the purge operation?"),
525 QMessageBox::Ok | QMessageBox::Cancel)
526 == QMessageBox::Cancel) { return; }
528 QString cmd("purge files jobid=");
534 * Subroutine to call preRestore to restore from a select job
536 void JobList::preRestoreFromJob()
538 new prerestorePage(m_currentJob, R_JOBIDLIST);
542 * Subroutine to call preRestore to restore from a select job
544 void JobList::preRestoreFromTime()
546 new prerestorePage(m_currentJob, R_JOBDATETIME);
550 * Subroutine to call class to show the log in the database from that job
552 void JobList::showLogForJob()
554 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
555 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
559 * Cancel a running job
561 void JobList::consoleCancelJob()
563 QString cmd("cancel jobid=");
571 void JobList::graphTable()
574 pass.recordLimitCheck = limitCheckBox->checkState();
575 pass.daysLimitCheck = daysCheckBox->checkState();
576 pass.recordLimitSpin = limitSpinBox->value();
577 pass.daysLimitSpin = daysSpinBox->value();
578 pass.jobCombo = jobComboBox->currentText();
579 pass.clientCombo = clientComboBox->currentText();
580 pass.volumeCombo = volumeComboBox->currentText();
581 pass.fileSetCombo = fileSetComboBox->currentText();
582 pass.purgedCombo = purgedComboBox->currentText();
583 pass.levelCombo = levelComboBox->currentText();
584 pass.statusCombo = statusComboBox->currentText();
586 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
587 new JobPlot(pageSelectorTreeWidgetItem, pass);
590 * Save user settings associated with this page
592 void JobList::writeSettings()
594 QSettings settings(m_console->m_dir->name(), "bat");
595 settings.beginGroup("JobListPage");
596 settings.setValue("splitterSizes", splitter->saveState());
601 * Read and restore user settings associated with this page
603 void JobList::readSettings()
605 QSettings settings(m_console->m_dir->name(), "bat");
606 settings.beginGroup("JobListPage");
607 splitter->restoreState(settings.value("splitterSizes").toByteArray());