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
34 #include <QAbstractEventDispatcher>
35 #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 if (!m_console->preventInUseConnect())
114 /* Can't do this in constructor because not neccesarily conected in constructor */
115 prepareFilterWidgets();
120 fillQueryString(query);
122 /* Set up the Header for the table */
123 QStringList headerlist = (QStringList()
124 << tr("Job Id") << tr("Job Name") << tr("Client") << tr("Job Starttime")
125 << tr("Job Type") << tr("Job Level") << tr("Job Files")
126 << 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() < 12)
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());
207 /* set default sorting */
208 mp_tableWidget->sortByColumn(m_jobIdIndex, Qt::DescendingOrder);
209 mp_tableWidget->setSortingEnabled(true);
211 /* Resize the columns */
212 mp_tableWidget->resizeColumnsToContents();
213 mp_tableWidget->resizeRowsToContents();
214 mp_tableWidget->verticalHeader()->hide();
215 if ((m_mediaName != tr("Any")) && (m_resultCount == 0)){
216 /* for context sensitive searches, let the user know if there were no
218 QMessageBox::warning(this, "Bat",
219 tr("The Jobs query returned no results.\n"
220 "Press OK to continue?"), QMessageBox::Ok );
224 void JobList::prepareFilterWidgets()
227 clientComboBox->addItem(tr("Any"));
228 clientComboBox->addItems(m_console->client_list);
229 comboSel(clientComboBox, m_clientName);
231 QStringList volumeList;
232 m_console->getVolumeList(volumeList);
233 volumeComboBox->addItem(tr("Any"));
234 volumeComboBox->addItems(volumeList);
235 comboSel(volumeComboBox, m_mediaName);
237 jobComboBox->addItem(tr("Any"));
238 jobComboBox->addItems(m_console->job_list);
239 comboSel(jobComboBox, m_jobName);
241 levelComboFill(levelComboBox);
243 boolComboFill(purgedComboBox);
245 fileSetComboBox->addItem(tr("Any"));
246 fileSetComboBox->addItems(m_console->fileset_list);
247 comboSel(fileSetComboBox, m_filesetName);
249 jobStatusComboFill(statusComboBox);
253 void JobList::fillQueryString(QString &query)
256 int volumeIndex = volumeComboBox->currentIndex();
257 if (volumeIndex != -1)
258 m_mediaName = volumeComboBox->itemText(volumeIndex);
259 QString distinct = "";
260 if (m_mediaName != tr("Any")) { distinct = "DISTINCT "; }
261 query += "SELECT " + distinct + "Job.Jobid AS Id, Job.Name AS JobName, "
262 " Client.Name AS Client,"
263 " Job.Starttime AS JobStart, Job.Type AS JobType,"
264 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
265 " Job.JobBytes AS Bytes, Job.JobStatus AS Status,"
266 " Job.PurgedFiles AS Purged, FileSet.FileSet"
268 " JOIN Client ON (Client.ClientId=Job.ClientId)"
269 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
270 QStringList conditions;
271 if (m_mediaName != tr("Any")) {
272 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
273 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
274 conditions.append("Media.VolumeName='" + m_mediaName + "'");
277 comboCond(conditions, clientComboBox, "Client.Name");
278 comboCond(conditions, jobComboBox, "Job.Name");
279 levelComboCond(conditions, levelComboBox, "Job.Level");
280 jobStatusComboCond(conditions, statusComboBox, "Job.JobStatus");
281 boolComboCond(conditions, purgedComboBox, "Job.PurgedFiles");
282 comboCond(conditions, fileSetComboBox, "FileSet.FileSet");
284 /* If Limit check box For limit by days is checked */
285 if (daysCheckBox->checkState() == Qt::Checked) {
286 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
287 QString since = stamp.toString(Qt::ISODate);
288 conditions.append("Job.Starttime>'" + since + "'");
291 foreach (QString condition, conditions) {
293 query += " WHERE " + condition;
296 query += " AND " + condition;
300 query += " ORDER BY Job.JobId DESC";
301 /* If Limit check box for limit records returned is checked */
302 if (limitCheckBox->checkState() == Qt::Checked) {
304 limit.setNum(limitSpinBox->value());
305 query += " LIMIT " + limit;
310 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
311 * The tree has been populated.
313 void JobList::PgSeltreeWidgetClicked()
321 * Virtual function override of pages function which is called when this page
322 * is visible on the stack
324 void JobList::currentStackItem()
326 /* if (!m_populated) populate every time user comes back to this object */
331 * Virtual Function to return the name for the medialist tree widget
333 void JobList::treeWidgetName(QString &desc)
335 if (m_mediaName != "" ) {
336 desc = tr("JobList of Volume %1").arg(m_mediaName);
337 } else if (m_clientName != "" ) {
338 desc = tr("JobList of Client %1").arg(m_clientName);
339 } else if (m_jobName != "" ) {
340 desc = tr("JobList of Job %1").arg(m_jobName);
341 } else if (m_filesetName != "" ) {
342 desc = tr("JobList of fileset %1").arg(m_filesetName);
344 desc = tr("JobList");
349 * This functions much line tableItemChanged for trees like the page selector,
350 * but I will do much less here
352 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
354 if (m_checkCurrentWidget) {
355 int row = currentItem->row();
356 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
357 m_currentJob = jobitem->text();
359 /* include purged action or not */
360 jobitem = mp_tableWidget->item(row, m_purgedIndex);
361 QString purged = jobitem->text();
362 mp_tableWidget->removeAction(actionPurgeFiles);
363 if (purged == tr("No") ) {
364 mp_tableWidget->addAction(actionPurgeFiles);
366 /* include restore from time and job action or not */
367 jobitem = mp_tableWidget->item(row, m_typeIndex);
368 QString type = jobitem->text();
369 mp_tableWidget->removeAction(actionRestoreFromJob);
370 mp_tableWidget->removeAction(actionRestoreFromTime);
371 if (type == tr("Backup")) {
372 mp_tableWidget->addAction(actionRestoreFromJob);
373 mp_tableWidget->addAction(actionRestoreFromTime);
375 /* include cancel action or not */
376 jobitem = mp_tableWidget->item(row, m_statusIndex);
377 QString status = jobitem->text();
378 mp_tableWidget->removeAction(actionCancelJob);
379 if (status == tr("Running") || status == tr("Created, not yet running")) {
380 mp_tableWidget->addAction(actionCancelJob);
386 * Function to create connections for context sensitive menu for this and
389 void JobList::createConnections()
391 /* connect to the action specific to this pages class that shows up in the
392 * page selector tree */
393 connect(actionRefreshJobList, SIGNAL(triggered()), this,
394 SLOT(populateTable()));
395 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
397 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
399 graphButton->setEnabled(false);
400 graphButton->setVisible(false);
402 /* for the tableItemChanged to maintain m_currentJob */
403 connect(mp_tableWidget, SIGNAL(
404 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
405 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
407 /* for the tableItemChanged to maintain a delete selection */
408 connect(mp_tableWidget, SIGNAL( itemSelectionChanged()),
409 this, SLOT(selectedJobsGet()) );
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");
492 if (mainWin->m_longList) { cmd.prepend("l"); }
495 void JobList::consoleDeleteJob()
497 if (QMessageBox::warning(this, "Bat",
498 tr("Are you sure you want to delete?? !!!.\n"
499 "This delete command is used to delete a Job record and all associated catalog"
500 " records that were created. This command operates only on the Catalog"
501 " database and has no effect on the actual data written to a Volume. This"
502 " command can be dangerous and we strongly recommend that you do not use"
503 " it unless you know what you are doing. The Job and all its associated"
504 " records (File and JobMedia) will be deleted from the catalog."
505 "Press OK to proceed with delete operation.?"),
506 QMessageBox::Ok | QMessageBox::Cancel)
507 == QMessageBox::Cancel) { return; }
509 QString cmd("delete job jobid=");
510 cmd += m_selectedJobs;
513 void JobList::consolePurgeFiles()
515 if (QMessageBox::warning(this, "Bat",
516 tr("Are you sure you want to purge ?? !!!.\n"
517 "The Purge command will delete associated Catalog database records from Jobs and"
518 " Volumes without considering the retention period. Purge works only on the"
519 " Catalog database and does not affect data written to Volumes. This command can"
520 " be dangerous because you can delete catalog records associated with current"
521 " backups of files, and we recommend that you do not use it unless you know what"
523 "Press OK to proceed with the purge operation?"),
524 QMessageBox::Ok | QMessageBox::Cancel)
525 == QMessageBox::Cancel) { return; }
527 QString cmd("purge files jobid=");
533 * Subroutine to call preRestore to restore from a select job
535 void JobList::preRestoreFromJob()
537 new prerestorePage(m_currentJob, R_JOBIDLIST);
541 * Subroutine to call preRestore to restore from a select job
543 void JobList::preRestoreFromTime()
545 new prerestorePage(m_currentJob, R_JOBDATETIME);
549 * Subroutine to call class to show the log in the database from that job
551 void JobList::showLogForJob()
553 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
554 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
558 * Cancel a running job
560 void JobList::consoleCancelJob()
562 QString cmd("cancel jobid=");
570 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);
592 * Save user settings associated with this page
594 void JobList::writeSettings()
596 QSettings settings(m_console->m_dir->name(), "bat");
597 settings.beginGroup(m_groupText);
598 settings.setValue(m_splitText, m_splitter->saveState());
603 * Read and restore user settings associated with this page
605 void JobList::readSettings()
607 m_groupText = "JobListPage";
608 m_splitText = "splitterSizes_1";
609 QSettings settings(m_console->m_dir->name(), "bat");
610 settings.beginGroup(m_groupText);
611 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
616 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
618 void JobList::selectedJobsGet()
621 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
622 foreach (QTableWidgetItem *sitem, sitems) {
623 int row = sitem->row();
624 if (!rowList.contains(row)) {
631 foreach(int row, rowList) {
632 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
633 if (!first) m_selectedJobs.append(",");
635 m_selectedJobs.append(sitem->text());
637 m_selectedJobsCount = rowList.count();
638 if (m_selectedJobsCount > 1) {
639 QString text = QString( tr("Delete list of %1 Jobs")).arg(m_selectedJobsCount);
640 actionDeleteJob->setText(text);
642 actionDeleteJob->setText(tr("Delete Single Job"));