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 Kern Sibbald.
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
35 #include <QAbstractEventDispatcher>
36 #include <QTableWidgetItem>
39 #include "joblog/joblog.h"
41 #include "jobgraphs/jobplot.h"
43 #include "util/fmtwidgetitem.h"
44 #include "util/comboutil.h"
47 * Constructor for the class
49 JobList::JobList(const QString &mediaName, const QString &clientName,
50 const QString &jobName, const QString &filesetName, QTreeWidgetItem *parentTreeWidgetItem)
53 m_name = ""; /* treeWidgetName has a virtual override in this class */
54 m_mediaName = mediaName;
55 m_clientName = clientName;
57 m_filesetName = filesetName;
58 m_filesetName = filesetName;
59 pgInitialize("", parentTreeWidgetItem);
60 QTreeWidgetItem* thisitem = mainWin->getFromHash(this);
61 thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/emblem-system.png")));
66 if ((m_mediaName != "") || (m_clientName != "") || (m_jobName != "") || (m_filesetName != ""))
68 m_checkCurrentWidget = true;
71 /* Set Defaults for check and spin for limits */
72 limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked);
73 limitSpinBox->setValue(mainWin->m_recordLimitVal);
74 daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked);
75 daysSpinBox->setValue(mainWin->m_daysLimitVal);
78 QGridLayout *gridLayout = new QGridLayout(this);
79 gridLayout->setSpacing(6);
80 gridLayout->setMargin(9);
81 gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
83 m_splitter = new QSplitter(Qt::Vertical, this);
84 QScrollArea *area = new QScrollArea();
85 area->setObjectName(QString::fromUtf8("area"));
86 area->setWidget(frame);
87 area->setWidgetResizable(true);
88 m_splitter->addWidget(mp_tableWidget);
89 m_splitter->addWidget(area);
91 gridLayout->addWidget(m_splitter, 0, 0, 1, 1);
96 * Write the m_splitter settings in the destructor
104 * The Meat of the class.
105 * This function will populate the QTableWidget, mp_tablewidget, with
106 * QTableWidgetItems representing the results of a query for what jobs exist on
107 * the media name passed from the constructor stored in m_mediaName.
109 void JobList::populateTable()
111 /* Can't do this in constructor because not neccesarily conected in constructor */
112 prepareFilterWidgets();
115 Freeze frz(*mp_tableWidget); /* disable updating*/
119 fillQueryString(query);
121 /* Set up the Header for the table */
122 QStringList headerlist = (QStringList()
123 << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime")
124 << tr("Job Type") << tr("Job Level") << tr("Job Files")
125 << tr("Job Bytes") << tr("Job Status") << tr("Purged") << tr("File Set"));
127 m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
128 m_purgedIndex = headerlist.indexOf(tr("Purged"));
129 m_typeIndex = headerlist.indexOf(tr("Job Type"));
130 m_statusIndex = headerlist.indexOf(tr("Job Status"));
131 m_startIndex = headerlist.indexOf(tr("Job Starttime"));
132 m_filesIndex = headerlist.indexOf(tr("Job Files"));
133 m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
135 /* Initialize the QTableWidget */
136 m_checkCurrentWidget = false;
137 mp_tableWidget->clear();
138 m_checkCurrentWidget = true;
139 mp_tableWidget->setColumnCount(headerlist.size());
140 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
141 mp_tableWidget->horizontalHeader()->setHighlightSections(false);
142 mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
143 mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
145 if (mainWin->m_sqlDebug) {
146 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
150 if (m_console->sql_cmd(query, results)) {
151 m_resultCount = results.count();
153 QStringList fieldlist;
154 mp_tableWidget->setRowCount(results.size());
157 /* Iterate through the record returned from the query */
159 foreach (resultline, results) {
160 fieldlist = resultline.split("\t");
161 if (fieldlist.size() < 12)
162 continue; /* some fields missing, ignore row */
164 TableItemFormatter jobitem(*mp_tableWidget, row);
166 /* Iterate through fields in the record */
167 QStringListIterator fld(fieldlist);
171 jobitem.setNumericFld(col++, fld.next());
174 jobitem.setTextFld(col++, fld.next());
177 jobitem.setTextFld(col++, fld.next());
180 jobitem.setTextFld(col++, fld.next(), true);
183 jobitem.setJobTypeFld(col++, fld.next());
186 jobitem.setJobLevelFld(col++, fld.next());
189 jobitem.setNumericFld(col++, fld.next());
192 jobitem.setBytesFld(col++, fld.next());
195 jobitem.setJobStatusFld(col++, fld.next());
198 jobitem.setBoolFld(col++, fld.next());
201 jobitem.setTextFld(col++, fld.next());
206 /* set default sorting */
207 mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
208 mp_tableWidget->setSortingEnabled(true);
210 /* Resize the columns */
211 mp_tableWidget->resizeColumnsToContents();
212 mp_tableWidget->resizeRowsToContents();
213 mp_tableWidget->verticalHeader()->hide();
214 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
215 /* for context sensitive searches, let the user know if there were no
217 QMessageBox::warning(this, "Bat",
218 tr("The Jobs query returned no results.\n"
219 "Press OK to continue?"), QMessageBox::Ok );
223 int rcnt = mp_tableWidget->rowCount();
224 int ccnt = mp_tableWidget->columnCount();
225 for(int r=0; r < rcnt; r++) {
226 for(int c=0; c < ccnt; c++) {
227 QTableWidgetItem* item = mp_tableWidget->item(r, c);
228 item->setFlags(Qt::ItemFlags(item->flags() & (~Qt::ItemIsEditable)));
233 void JobList::prepareFilterWidgets()
236 clientComboBox->addItem(tr("Any"));
237 clientComboBox->addItems(m_console->client_list);
238 comboSel(clientComboBox, m_clientName);
240 QStringList volumeList;
241 m_console->getVolumeList(volumeList);
242 volumeComboBox->addItem(tr("Any"));
243 volumeComboBox->addItems(volumeList);
244 comboSel(volumeComboBox, m_mediaName);
246 jobComboBox->addItem(tr("Any"));
247 jobComboBox->addItems(m_console->job_list);
248 comboSel(jobComboBox, m_jobName);
250 levelComboFill(levelComboBox);
252 boolComboFill(purgedComboBox);
254 fileSetComboBox->addItem(tr("Any"));
255 fileSetComboBox->addItems(m_console->fileset_list);
256 comboSel(fileSetComboBox, m_filesetName);
258 jobStatusComboFill(statusComboBox);
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, Job.JobStatus AS Status,"
275 " Job.PurgedFiles AS Purged, FileSet.FileSet"
277 " JOIN Client ON (Client.ClientId=Job.ClientId)"
278 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
279 QStringList conditions;
280 if (m_mediaName != tr("Any")) {
281 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
282 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
283 conditions.append("Media.VolumeName='" + m_mediaName + "'");
286 comboCond(conditions, clientComboBox, "Client.Name");
287 comboCond(conditions, jobComboBox, "Job.Name");
288 levelComboCond(conditions, levelComboBox, "Job.Level");
289 jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
290 boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
291 comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
293 /* If Limit check box For limit by days is checked */
294 if (daysCheckBox->checkState() == Qt::Checked) {
295 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
296 QString since = stamp.toString(Qt::ISODate);
297 conditions.append("Job.Starttime>'" + since + "'");
300 foreach (QString condition, conditions) {
302 query += " WHERE " + condition;
305 query += " AND " + condition;
309 query += " ORDER BY Job.JobId DESC";
310 /* If Limit check box for limit records returned is checked */
311 if (limitCheckBox->checkState() == Qt::Checked) {
313 limit.setNum(limitSpinBox->value());
314 query += " LIMIT " + limit;
319 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
320 * The tree has been populated.
322 void JobList::PgSeltreeWidgetClicked()
330 * Virtual function override of pages function which is called when this page
331 * is visible on the stack
333 void JobList::currentStackItem()
335 /* if (!m_populated) populate every time user comes back to this object */
340 * Virtual Function to return the name for the medialist tree widget
342 void JobList::treeWidgetName(QString &desc)
344 if (m_mediaName != "" ) {
345 desc = tr("JobList of Volume %1").arg(m_mediaName);
346 } else if (m_clientName != "" ) {
347 desc = tr("JobList of Client %1").arg(m_clientName);
348 } else if (m_jobName != "" ) {
349 desc = tr("JobList of Job %1").arg(m_jobName);
350 } else if (m_filesetName != "" ) {
351 desc = tr("JobList of fileset %1").arg(m_filesetName);
353 desc = tr("JobList");
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);
372 if (purged == tr("No") ) {
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);
380 if (type == tr("Backup")) {
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 == tr("Running") || status == tr("Created, not yet 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()));
406 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
408 graphButton->setEnabled(false);
409 graphButton->setVisible(false);
411 /* for the tableItemChanged to maintain m_currentJob */
412 connect(mp_tableWidget, SIGNAL(
413 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
414 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
416 /* for the tableItemChanged to maintain a delete selection */
417 connect(mp_tableWidget, SIGNAL( itemSelectionChanged()),
418 this, SLOT(selectedJobsGet()) );
420 /* Do what is required for the local context sensitive menu */
423 /* setContextMenuPolicy is required */
424 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
427 mp_tableWidget->addAction(actionRefreshJobList);
428 mp_tableWidget->addAction(actionListJobid);
429 mp_tableWidget->addAction(actionListFilesOnJob);
430 mp_tableWidget->addAction(actionListJobMedia);
431 mp_tableWidget->addAction(actionListVolumes);
432 mp_tableWidget->addAction(actionDeleteJob);
433 mp_tableWidget->addAction(actionPurgeFiles);
434 mp_tableWidget->addAction(actionRestoreFromJob);
435 mp_tableWidget->addAction(actionRestoreFromTime);
436 mp_tableWidget->addAction(actionShowLogForJob);
438 /* Make Connections */
439 connect(actionListJobid, SIGNAL(triggered()), this,
440 SLOT(consoleListJobid()));
441 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
442 SLOT(consoleListFilesOnJob()));
443 connect(actionListJobMedia, SIGNAL(triggered()), this,
444 SLOT(consoleListJobMedia()));
445 connect(actionListVolumes, SIGNAL(triggered()), this,
446 SLOT(consoleListVolumes()));
447 connect(actionDeleteJob, SIGNAL(triggered()), this,
448 SLOT(consoleDeleteJob()));
449 connect(actionPurgeFiles, SIGNAL(triggered()), this,
450 SLOT(consolePurgeFiles()));
451 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
452 SLOT(preRestoreFromJob()));
453 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
454 SLOT(preRestoreFromTime()));
455 connect(actionShowLogForJob, SIGNAL(triggered()), this,
456 SLOT(showLogForJob()));
457 connect(actionCancelJob, SIGNAL(triggered()), this,
458 SLOT(consoleCancelJob()));
459 connect(actionListJobTotals, SIGNAL(triggered()), this,
460 SLOT(consoleListJobTotals()));
462 m_contextActions.append(actionRefreshJobList);
463 m_contextActions.append(actionListJobTotals);
467 * Functions to respond to local context sensitive menu sending console commands
468 * If I could figure out how to make these one function passing a string, Yaaaaaa
470 void JobList::consoleListJobid()
472 QString cmd("list jobid=");
474 if (mainWin->m_longList) { cmd.prepend("l"); }
477 void JobList::consoleListFilesOnJob()
479 QString cmd("list files jobid=");
481 if (mainWin->m_longList) { cmd.prepend("l"); }
484 void JobList::consoleListJobMedia()
486 QString cmd("list jobmedia jobid=");
488 if (mainWin->m_longList) { cmd.prepend("l"); }
491 void JobList::consoleListVolumes()
493 QString cmd("list volumes jobid=");
495 if (mainWin->m_longList) { cmd.prepend("l"); }
498 void JobList::consoleListJobTotals()
500 QString cmd("list jobtotals");
501 if (mainWin->m_longList) { cmd.prepend("l"); }
504 void JobList::consoleDeleteJob()
506 if (QMessageBox::warning(this, "Bat",
507 tr("Are you sure you want to delete?? !!!.\n"
508 "This delete command is used to delete a Job record and all associated catalog"
509 " records that were created. This command operates only on the Catalog"
510 " database and has no effect on the actual data written to a Volume. This"
511 " command can be dangerous and we strongly recommend that you do not use"
512 " it unless you know what you are doing. The Job and all its associated"
513 " records (File and JobMedia) will be deleted from the catalog."
514 "Press OK to proceed with delete operation.?"),
515 QMessageBox::Ok | QMessageBox::Cancel)
516 == QMessageBox::Cancel) { return; }
518 QString cmd("delete job jobid=");
519 cmd += m_selectedJobs;
522 void JobList::consolePurgeFiles()
524 if (QMessageBox::warning(this, "Bat",
525 tr("Are you sure you want to purge ?? !!!.\n"
526 "The Purge command will delete associated Catalog database records from Jobs and"
527 " Volumes without considering the retention period. Purge works only on the"
528 " Catalog database and does not affect data written to Volumes. This command can"
529 " be dangerous because you can delete catalog records associated with current"
530 " backups of files, and we recommend that you do not use it unless you know what"
532 "Press OK to proceed with the purge operation?"),
533 QMessageBox::Ok | QMessageBox::Cancel)
534 == QMessageBox::Cancel) { return; }
536 QString cmd("purge files jobid=");
542 * Subroutine to call preRestore to restore from a select job
544 void JobList::preRestoreFromJob()
546 new prerestorePage(m_currentJob, R_JOBIDLIST);
550 * Subroutine to call preRestore to restore from a select job
552 void JobList::preRestoreFromTime()
554 new prerestorePage(m_currentJob, R_JOBDATETIME);
558 * Subroutine to call class to show the log in the database from that job
560 void JobList::showLogForJob()
562 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
563 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
567 * Cancel a running job
569 void JobList::consoleCancelJob()
571 QString cmd("cancel jobid=");
579 void JobList::graphTable()
583 pass.recordLimitCheck = limitCheckBox->checkState();
584 pass.daysLimitCheck = daysCheckBox->checkState();
585 pass.recordLimitSpin = limitSpinBox->value();
586 pass.daysLimitSpin = daysSpinBox->value();
587 pass.jobCombo = jobComboBox->currentText();
588 pass.clientCombo = clientComboBox->currentText();
589 pass.volumeCombo = volumeComboBox->currentText();
590 pass.fileSetCombo = fileSetComboBox->currentText();
591 pass.purgedCombo = purgedComboBox->currentText();
592 pass.levelCombo = levelComboBox->currentText();
593 pass.statusCombo = statusComboBox->currentText();
595 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
596 new JobPlot(pageSelectorTreeWidgetItem, pass);
601 * Save user settings associated with this page
603 void JobList::writeSettings()
605 QSettings settings(m_console->m_dir->name(), "bat");
606 settings.beginGroup(m_groupText);
607 settings.setValue(m_splitText, m_splitter->saveState());
612 * Read and restore user settings associated with this page
614 void JobList::readSettings()
616 m_groupText = "JobListPage";
617 m_splitText = "splitterSizes_1";
618 QSettings settings(m_console->m_dir->name(), "bat");
619 settings.beginGroup(m_groupText);
620 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
625 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
627 void JobList::selectedJobsGet()
630 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
631 foreach (QTableWidgetItem *sitem, sitems) {
632 int row = sitem->row();
633 if (!rowList.contains(row)) {
640 foreach(int row, rowList) {
641 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
642 if (!first) m_selectedJobs.append(",");
644 m_selectedJobs.append(sitem->text());
646 m_selectedJobsCount = rowList.count();
647 if (m_selectedJobsCount > 1) {
648 QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
649 actionDeleteJob->setText(text);
651 actionDeleteJob->setText(tr("Delete Single Job"));