2 Bacula® - The Network Backup Solution
4 Copyright (C) 2007-2009 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")
128 m_jobIdIndex = headerlist.indexOf(tr("Job Id"));
129 m_purgedIndex = headerlist.indexOf(tr("Purged"));
130 m_typeIndex = headerlist.indexOf(tr("Job Type"));
131 m_statusIndex = headerlist.indexOf(tr("Job Status"));
132 m_startIndex = headerlist.indexOf(tr("Job Starttime"));
133 m_filesIndex = headerlist.indexOf(tr("Job Files"));
134 m_bytesIndex = headerlist.indexOf(tr("Job Bytes"));
136 /* Initialize the QTableWidget */
137 m_checkCurrentWidget = false;
138 mp_tableWidget->clear();
139 m_checkCurrentWidget = true;
140 mp_tableWidget->setColumnCount(headerlist.size());
141 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
142 mp_tableWidget->horizontalHeader()->setHighlightSections(false);
143 mp_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
144 mp_tableWidget->setSortingEnabled(false); /* rows move on insert if sorting enabled */
146 if (mainWin->m_sqlDebug) {
147 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
151 if (m_console->sql_cmd(query, results)) {
152 m_resultCount = results.count();
154 QStringList fieldlist;
155 mp_tableWidget->setRowCount(results.size());
158 /* Iterate through the record returned from the query */
160 foreach (resultline, results) {
161 fieldlist = resultline.split("\t");
162 if (fieldlist.size() < 13)
163 continue; /* some fields missing, ignore row */
165 TableItemFormatter jobitem(*mp_tableWidget, row);
167 /* Iterate through fields in the record */
168 QStringListIterator fld(fieldlist);
172 jobitem.setNumericFld(col++, fld.next());
175 jobitem.setTextFld(col++, fld.next());
178 jobitem.setTextFld(col++, fld.next());
181 jobitem.setTextFld(col++, fld.next(), true);
184 jobitem.setJobTypeFld(col++, fld.next());
187 jobitem.setJobLevelFld(col++, fld.next());
190 jobitem.setNumericFld(col++, fld.next());
193 jobitem.setBytesFld(col++, fld.next());
196 jobitem.setJobStatusFld(col++, fld.next());
199 jobitem.setBoolFld(col++, fld.next());
202 jobitem.setTextFld(col++, fld.next());
205 jobitem.setTextFld(col++, fld.next());
209 /* set default sorting */
210 mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
211 mp_tableWidget->setSortingEnabled(true);
213 /* Resize the columns */
214 mp_tableWidget->resizeColumnsToContents();
215 mp_tableWidget->resizeRowsToContents();
216 mp_tableWidget->verticalHeader()->hide();
217 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
218 /* for context sensitive searches, let the user know if there were no
220 QMessageBox::warning(this, "Bat",
221 tr("The Jobs query returned no results.\n"
222 "Press OK to continue?"), QMessageBox::Ok );
226 int rcnt = mp_tableWidget->rowCount();
227 int ccnt = mp_tableWidget->columnCount();
228 for(int r=0; r < rcnt; r++) {
229 for(int c=0; c < ccnt; c++) {
230 QTableWidgetItem* item = mp_tableWidget->item(r, c);
232 item->setFlags(Qt::ItemFlags(item->flags() & (~Qt::ItemIsEditable)));
238 void JobList::prepareFilterWidgets()
241 clientComboBox->addItem(tr("Any"));
242 clientComboBox->addItems(m_console->client_list);
243 comboSel(clientComboBox, m_clientName);
245 QStringList volumeList;
246 getVolumeList(volumeList);
247 volumeComboBox->addItem(tr("Any"));
248 volumeComboBox->addItems(volumeList);
249 comboSel(volumeComboBox, m_mediaName);
251 jobComboBox->addItem(tr("Any"));
252 jobComboBox->addItems(m_console->job_list);
253 comboSel(jobComboBox, m_jobName);
255 levelComboFill(levelComboBox);
257 boolComboFill(purgedComboBox);
259 fileSetComboBox->addItem(tr("Any"));
260 fileSetComboBox->addItems(m_console->fileset_list);
261 comboSel(fileSetComboBox, m_filesetName);
263 poolComboBox->addItem(tr("Any"));
264 poolComboBox->addItems(m_console->pool_list);
266 jobStatusComboFill(statusComboBox);
270 void JobList::fillQueryString(QString &query)
273 int volumeIndex = volumeComboBox->currentIndex();
274 if (volumeIndex != -1)
275 m_mediaName = volumeComboBox->itemText(volumeIndex);
276 QString distinct = "";
277 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
278 query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, "
279 " Client.Name AS Client,"
280 " Job.Starttime AS JobStart, Job.Type AS JobType,"
281 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
282 " Job.JobBytes AS Bytes, Job.JobStatus AS Status,"
283 " Job.PurgedFiles AS Purged, FileSet.FileSet,"
286 " JOIN Client ON (Client.ClientId=Job.ClientId)"
287 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) "
288 " LEFT OUTER JOIN pool ON Job.PoolId = Pool.PoolId ";
289 QStringList conditions;
290 if (m_mediaName != tr("Any")) {
291 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
292 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
293 conditions.append("Media.VolumeName='" + m_mediaName + "'");
296 comboCond(conditions, clientComboBox, "Client.Name");
297 comboCond(conditions, jobComboBox, "Job.Name");
298 levelComboCond(conditions, levelComboBox, "Job.Level");
299 jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
300 boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
301 comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
302 comboCond(conditions, poolComboBox, "Pool.Name");
304 /* If Limit check box For limit by days is checked */
305 if (daysCheckBox->checkState() == Qt::Checked) {
306 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
307 QString since = stamp.toString(Qt::ISODate);
308 conditions.append("Job.Starttime>'" + since + "'");
311 foreach (QString condition, conditions) {
313 query += " WHERE " + condition;
316 query += " AND " + condition;
320 query += " ORDER BY Job.JobId DESC";
321 /* If Limit check box for limit records returned is checked */
322 if (limitCheckBox->checkState() == Qt::Checked) {
324 limit.setNum(limitSpinBox->value());
325 query += " LIMIT " + limit;
330 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
331 * The tree has been populated.
333 void JobList::PgSeltreeWidgetClicked()
341 * Virtual function override of pages function which is called when this page
342 * is visible on the stack
344 void JobList::currentStackItem()
346 /* if (!m_populated) populate every time user comes back to this object */
351 * Virtual Function to return the name for the medialist tree widget
353 void JobList::treeWidgetName(QString &desc)
355 if (m_mediaName != "" ) {
356 desc = tr("JobList of Volume %1").arg(m_mediaName);
357 } else if (m_clientName != "" ) {
358 desc = tr("JobList of Client %1").arg(m_clientName);
359 } else if (m_jobName != "" ) {
360 desc = tr("JobList of Job %1").arg(m_jobName);
361 } else if (m_filesetName != "" ) {
362 desc = tr("JobList of fileset %1").arg(m_filesetName);
364 desc = tr("JobList");
369 * This functions much line tableItemChanged for trees like the page selector,
370 * but I will do much less here
372 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
374 if (m_checkCurrentWidget) {
375 int row = currentItem->row();
376 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
377 m_currentJob = jobitem->text();
379 /* include purged action or not */
380 jobitem = mp_tableWidget->item(row, m_purgedIndex);
381 QString purged = jobitem->text();
382 mp_tableWidget->removeAction(actionPurgeFiles);
383 if (purged == tr("No") ) {
384 mp_tableWidget->addAction(actionPurgeFiles);
386 /* include restore from time and job action or not */
387 jobitem = mp_tableWidget->item(row, m_typeIndex);
388 QString type = jobitem->text();
389 mp_tableWidget->removeAction(actionRestoreFromJob);
390 mp_tableWidget->removeAction(actionRestoreFromTime);
391 if (type == tr("Backup")) {
392 mp_tableWidget->addAction(actionRestoreFromJob);
393 mp_tableWidget->addAction(actionRestoreFromTime);
395 /* include cancel action or not */
396 jobitem = mp_tableWidget->item(row, m_statusIndex);
397 QString status = jobitem->text();
398 mp_tableWidget->removeAction(actionCancelJob);
399 if (status == tr("Running") || status == tr("Created, not yet running")) {
400 mp_tableWidget->addAction(actionCancelJob);
406 * Function to create connections for context sensitive menu for this and
409 void JobList::createConnections()
411 /* connect to the action specific to this pages class that shows up in the
412 * page selector tree */
413 connect(actionRefreshJobList, SIGNAL(triggered()), this,
414 SLOT(populateTable()));
415 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
417 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
419 graphButton->setEnabled(false);
420 graphButton->setVisible(false);
422 /* for the tableItemChanged to maintain m_currentJob */
423 connect(mp_tableWidget, SIGNAL(
424 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
425 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
427 /* for the tableItemChanged to maintain a delete selection */
428 connect(mp_tableWidget, SIGNAL( itemSelectionChanged()),
429 this, SLOT(selectedJobsGet()) );
431 /* Do what is required for the local context sensitive menu */
434 /* setContextMenuPolicy is required */
435 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
438 mp_tableWidget->addAction(actionRefreshJobList);
439 mp_tableWidget->addAction(actionListJobid);
440 mp_tableWidget->addAction(actionListFilesOnJob);
441 mp_tableWidget->addAction(actionListJobMedia);
442 mp_tableWidget->addAction(actionListVolumes);
443 mp_tableWidget->addAction(actionDeleteJob);
444 mp_tableWidget->addAction(actionPurgeFiles);
445 mp_tableWidget->addAction(actionRestoreFromJob);
446 mp_tableWidget->addAction(actionRestoreFromTime);
447 mp_tableWidget->addAction(actionShowLogForJob);
449 /* Make Connections */
450 connect(actionListJobid, SIGNAL(triggered()), this,
451 SLOT(consoleListJobid()));
452 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
453 SLOT(consoleListFilesOnJob()));
454 connect(actionListJobMedia, SIGNAL(triggered()), this,
455 SLOT(consoleListJobMedia()));
456 connect(actionListVolumes, SIGNAL(triggered()), this,
457 SLOT(consoleListVolumes()));
458 connect(actionDeleteJob, SIGNAL(triggered()), this,
459 SLOT(consoleDeleteJob()));
460 connect(actionPurgeFiles, SIGNAL(triggered()), this,
461 SLOT(consolePurgeFiles()));
462 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
463 SLOT(preRestoreFromJob()));
464 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
465 SLOT(preRestoreFromTime()));
466 connect(actionShowLogForJob, SIGNAL(triggered()), this,
467 SLOT(showLogForJob()));
468 connect(actionCancelJob, SIGNAL(triggered()), this,
469 SLOT(consoleCancelJob()));
470 connect(actionListJobTotals, SIGNAL(triggered()), this,
471 SLOT(consoleListJobTotals()));
473 m_contextActions.append(actionRefreshJobList);
474 m_contextActions.append(actionListJobTotals);
478 * Functions to respond to local context sensitive menu sending console commands
479 * If I could figure out how to make these one function passing a string, Yaaaaaa
481 void JobList::consoleListJobid()
483 QString cmd("list jobid=");
485 if (mainWin->m_longList) { cmd.prepend("l"); }
488 void JobList::consoleListFilesOnJob()
490 QString cmd("list files jobid=");
492 if (mainWin->m_longList) { cmd.prepend("l"); }
495 void JobList::consoleListJobMedia()
497 QString cmd("list jobmedia jobid=");
499 if (mainWin->m_longList) { cmd.prepend("l"); }
502 void JobList::consoleListVolumes()
504 QString cmd("list volumes jobid=");
506 if (mainWin->m_longList) { cmd.prepend("l"); }
509 void JobList::consoleListJobTotals()
511 QString cmd("list jobtotals");
512 if (mainWin->m_longList) { cmd.prepend("l"); }
515 void JobList::consoleDeleteJob()
517 if (QMessageBox::warning(this, "Bat",
518 tr("Are you sure you want to delete?? !!!.\n"
519 "This delete command is used to delete a Job record and all associated catalog"
520 " records that were created. This command operates only on the Catalog"
521 " database and has no effect on the actual data written to a Volume. This"
522 " command can be dangerous and we strongly recommend that you do not use"
523 " it unless you know what you are doing. The Job and all its associated"
524 " records (File and JobMedia) will be deleted from the catalog."
525 "Press OK to proceed with delete operation.?"),
526 QMessageBox::Ok | QMessageBox::Cancel)
527 == QMessageBox::Cancel) { return; }
529 QString cmd("delete job jobid=");
530 cmd += m_selectedJobs;
533 void JobList::consolePurgeFiles()
535 if (QMessageBox::warning(this, "Bat",
536 tr("Are you sure you want to purge ?? !!!.\n"
537 "The Purge command will delete associated Catalog database records from Jobs and"
538 " Volumes without considering the retention period. Purge works only on the"
539 " Catalog database and does not affect data written to Volumes. This command can"
540 " be dangerous because you can delete catalog records associated with current"
541 " backups of files, and we recommend that you do not use it unless you know what"
543 "Press OK to proceed with the purge operation?"),
544 QMessageBox::Ok | QMessageBox::Cancel)
545 == QMessageBox::Cancel) { return; }
547 QString cmd("purge files jobid=");
553 * Subroutine to call preRestore to restore from a select job
555 void JobList::preRestoreFromJob()
557 new prerestorePage(m_currentJob, R_JOBIDLIST);
561 * Subroutine to call preRestore to restore from a select job
563 void JobList::preRestoreFromTime()
565 new prerestorePage(m_currentJob, R_JOBDATETIME);
569 * Subroutine to call class to show the log in the database from that job
571 void JobList::showLogForJob()
573 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
574 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
578 * Cancel a running job
580 void JobList::consoleCancelJob()
582 QString cmd("cancel jobid=");
590 void JobList::graphTable()
594 pass.recordLimitCheck = limitCheckBox->checkState();
595 pass.daysLimitCheck = daysCheckBox->checkState();
596 pass.recordLimitSpin = limitSpinBox->value();
597 pass.daysLimitSpin = daysSpinBox->value();
598 pass.jobCombo = jobComboBox->currentText();
599 pass.clientCombo = clientComboBox->currentText();
600 pass.volumeCombo = volumeComboBox->currentText();
601 pass.fileSetCombo = fileSetComboBox->currentText();
602 pass.purgedCombo = purgedComboBox->currentText();
603 pass.levelCombo = levelComboBox->currentText();
604 pass.statusCombo = statusComboBox->currentText();
606 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
607 new JobPlot(pageSelectorTreeWidgetItem, pass);
612 * Save user settings associated with this page
614 void JobList::writeSettings()
616 QSettings settings(m_console->m_dir->name(), "bat");
617 settings.beginGroup(m_groupText);
618 settings.setValue(m_splitText, m_splitter->saveState());
623 * Read and restore user settings associated with this page
625 void JobList::readSettings()
627 m_groupText = "JobListPage";
628 m_splitText = "splitterSizes_1";
629 QSettings settings(m_console->m_dir->name(), "bat");
630 settings.beginGroup(m_groupText);
631 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
636 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
638 void JobList::selectedJobsGet()
641 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
642 foreach (QTableWidgetItem *sitem, sitems) {
643 int row = sitem->row();
644 if (!rowList.contains(row)) {
651 foreach(int row, rowList) {
652 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
653 if (!first) m_selectedJobs.append(",");
655 m_selectedJobs.append(sitem->text());
657 m_selectedJobsCount = rowList.count();
658 if (m_selectedJobsCount > 1) {
659 QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
660 actionDeleteJob->setText(text);
662 actionDeleteJob->setText(tr("Delete Single Job"));