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");
242 /* Initialize the QTableWidget */
243 m_checkCurrentWidget = false;
244 mp_tableWidget->clear();
245 m_checkCurrentWidget = true;
246 mp_tableWidget->setColumnCount(headerlist.size());
247 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
249 if (mainWin->m_sqlDebug) {
250 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
252 if (m_console->sql_cmd(query, results)) {
253 m_resultCount = results.count();
255 QTableWidgetItem* p_tableitem;
257 QStringList fieldlist;
258 mp_tableWidget->setRowCount(results.size());
261 /* Iterate through the record returned from the query */
262 foreach (resultline, results) {
263 fieldlist = resultline.split("\t");
265 bool m_statusIndexDone = false;
266 QString statusCode("");
267 /* Iterate through fields in the record */
268 foreach (field, fieldlist) {
269 field = field.trimmed(); /* strip leading & trailing spaces */
270 if ((column == m_statusIndex) && (!m_statusIndexDone)){
271 m_statusIndexDone = true;
274 p_tableitem = new QTableWidgetItem(field, 1);
275 p_tableitem->setFlags(Qt::ItemIsSelectable);
276 p_tableitem->setForeground(blackBrush);
277 mp_tableWidget->setItem(row, column, p_tableitem);
278 if (column == m_statusIndex)
279 setStatusColor(p_tableitem, statusCode);
286 /* Resize the columns */
287 mp_tableWidget->resizeColumnsToContents();
288 mp_tableWidget->resizeRowsToContents();
289 mp_tableWidget->verticalHeader()->hide();
290 if ((m_mediaName != "Any") && (m_resultCount == 0)){
291 /* for context sensitive searches, let the user know if there were no
293 QMessageBox::warning(this, tr("Bat"),
294 tr("The Jobs query returned no results.\n"
295 "Press OK to continue?"), QMessageBox::Ok );
299 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
301 QString greenchars("TCR");
302 QString redchars("BEf");
303 QString yellowchars("eDAFSMmsjdctp");
304 if (greenchars.contains(field, Qt::CaseSensitive)) {
305 item->setBackground(Qt::green);
306 } else if (redchars.contains(field, Qt::CaseSensitive)) {
307 item->setBackground(Qt::red);
308 } else if (yellowchars.contains(field, Qt::CaseSensitive)){
309 item->setBackground(Qt::yellow);
314 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
315 * The tree has been populated.
317 void JobList::PgSeltreeWidgetClicked()
326 * Virtual function override of pages function which is called when this page
327 * is visible on the stack
329 void JobList::currentStackItem()
338 * Virtual Function to return the name for the medialist tree widget
340 void JobList::treeWidgetName(QString &desc)
342 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
346 if (m_mediaName != "" ) {
347 desc += "of Volume " + m_mediaName;
349 if (m_clientName != "" ) {
350 desc += "of Client " + m_clientName;
352 if (m_jobName != "" ) {
353 desc += "of Job " + m_jobName;
355 if (m_filesetName != "" ) {
356 desc += "of fileset " + m_filesetName;
362 * This functions much line tableItemChanged for trees like the page selector,
363 * but I will do much less here
365 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
367 if (m_checkCurrentWidget) {
368 int row = currentItem->row();
369 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
370 m_currentJob = jobitem->text();
373 /* include purged action or not */
374 jobitem = mp_tableWidget->item(row, m_purgedIndex);
375 QString purged = jobitem->text();
376 mp_tableWidget->removeAction(actionPurgeFiles);
378 mp_tableWidget->addAction(actionPurgeFiles);
380 /* include restore from time and job action or not */
381 jobitem = mp_tableWidget->item(row, m_typeIndex);
382 QString type = jobitem->text();
383 mp_tableWidget->removeAction(actionRestoreFromJob);
384 mp_tableWidget->removeAction(actionRestoreFromTime);
386 mp_tableWidget->addAction(actionRestoreFromJob);
387 mp_tableWidget->addAction(actionRestoreFromTime);
389 /* include cancel action or not */
390 jobitem = mp_tableWidget->item(row, m_statusIndex);
391 QString status = jobitem->text();
392 mp_tableWidget->removeAction(actionCancelJob);
393 if (status == "Running") {
394 mp_tableWidget->addAction(actionCancelJob);
400 * Function to create connections for context sensitive menu for this and
403 void JobList::createConnections()
405 /* connect to the action specific to this pages class that shows up in the
406 * page selector tree */
407 connect(actionRefreshJobList, SIGNAL(triggered()), this,
408 SLOT(populateTable()));
409 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
410 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
411 /* for the tableItemChanged to maintain m_currentJob */
412 connect(mp_tableWidget, SIGNAL(
413 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
414 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
416 /* Do what is required for the local context sensitive menu */
419 /* setContextMenuPolicy is required */
420 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
423 mp_tableWidget->addAction(actionRefreshJobList);
424 mp_tableWidget->addAction(actionListJobid);
425 mp_tableWidget->addAction(actionListFilesOnJob);
426 mp_tableWidget->addAction(actionListJobMedia);
427 mp_tableWidget->addAction(actionListVolumes);
428 mp_tableWidget->addAction(actionDeleteJob);
429 mp_tableWidget->addAction(actionPurgeFiles);
430 mp_tableWidget->addAction(actionRestoreFromJob);
431 mp_tableWidget->addAction(actionRestoreFromTime);
432 mp_tableWidget->addAction(actionShowLogForJob);
434 /* Make Connections */
435 connect(actionListJobid, SIGNAL(triggered()), this,
436 SLOT(consoleListJobid()));
437 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
438 SLOT(consoleListFilesOnJob()));
439 connect(actionListJobMedia, SIGNAL(triggered()), this,
440 SLOT(consoleListJobMedia()));
441 connect(actionListVolumes, SIGNAL(triggered()), this,
442 SLOT(consoleListVolumes()));
443 connect(actionDeleteJob, SIGNAL(triggered()), this,
444 SLOT(consoleDeleteJob()));
445 connect(actionPurgeFiles, SIGNAL(triggered()), this,
446 SLOT(consolePurgeFiles()));
447 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
448 SLOT(preRestoreFromJob()));
449 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
450 SLOT(preRestoreFromTime()));
451 connect(actionShowLogForJob, SIGNAL(triggered()), this,
452 SLOT(showLogForJob()));
453 connect(actionCancelJob, SIGNAL(triggered()), this,
454 SLOT(consoleCancelJob()));
455 connect(actionListJobTotals, SIGNAL(triggered()), this,
456 SLOT(consoleListJobTotals()));
458 m_contextActions.append(actionRefreshJobList);
459 m_contextActions.append(actionListJobTotals);
463 * Functions to respond to local context sensitive menu sending console commands
464 * If I could figure out how to make these one function passing a string, Yaaaaaa
466 void JobList::consoleListJobid()
468 QString cmd("list jobid=");
470 if (mainWin->m_longList) { cmd.prepend("l"); }
473 void JobList::consoleListFilesOnJob()
475 QString cmd("list files jobid=");
477 if (mainWin->m_longList) { cmd.prepend("l"); }
480 void JobList::consoleListJobMedia()
482 QString cmd("list jobmedia jobid=");
484 if (mainWin->m_longList) { cmd.prepend("l"); }
487 void JobList::consoleListVolumes()
489 QString cmd("list volumes jobid=");
491 if (mainWin->m_longList) { cmd.prepend("l"); }
494 void JobList::consoleListJobTotals()
496 QString cmd("list jobtotals");
497 if (mainWin->m_longList) { cmd.prepend("l"); }
500 void JobList::consoleDeleteJob()
502 if (QMessageBox::warning(this, tr("Bat"),
503 tr("Are you sure you want to delete?? !!!.\n"
504 "This delete command is used to delete a Job record and all associated catalog"
505 " records that were created. This command operates only on the Catalog"
506 " database and has no effect on the actual data written to a Volume. This"
507 " command can be dangerous and we strongly recommend that you do not use"
508 " it unless you know what you are doing. The Job and all its associated"
509 " records (File and JobMedia) will be deleted from the catalog."
510 "Press OK to proceed with delete operation.?"),
511 QMessageBox::Ok | QMessageBox::Cancel)
512 == QMessageBox::Cancel) { return; }
514 QString cmd("delete job jobid=");
515 cmd += m_selectedJobs;
518 void JobList::consolePurgeFiles()
520 if (QMessageBox::warning(this, tr("Bat"),
521 tr("Are you sure you want to purge ?? !!!.\n"
522 "The Purge command will delete associated Catalog database records from Jobs and"
523 " Volumes without considering the retention period. Purge works only on the"
524 " Catalog database and does not affect data written to Volumes. This command can"
525 " be dangerous because you can delete catalog records associated with current"
526 " backups of files, and we recommend that you do not use it unless you know what"
528 "Press OK to proceed with the purge operation?"),
529 QMessageBox::Ok | QMessageBox::Cancel)
530 == QMessageBox::Cancel) { return; }
532 QString cmd("purge files jobid=");
538 * Subroutine to call preRestore to restore from a select job
540 void JobList::preRestoreFromJob()
542 new prerestorePage(m_currentJob, R_JOBIDLIST);
546 * Subroutine to call preRestore to restore from a select job
548 void JobList::preRestoreFromTime()
550 new prerestorePage(m_currentJob, R_JOBDATETIME);
554 * Subroutine to call class to show the log in the database from that job
556 void JobList::showLogForJob()
558 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
559 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
563 * Cancel a running job
565 void JobList::consoleCancelJob()
567 QString cmd("cancel jobid=");
575 void JobList::graphTable()
578 pass.recordLimitCheck = limitCheckBox->checkState();
579 pass.daysLimitCheck = daysCheckBox->checkState();
580 pass.recordLimitSpin = limitSpinBox->value();
581 pass.daysLimitSpin = daysSpinBox->value();
582 pass.jobCombo = jobComboBox->currentText();
583 pass.clientCombo = clientComboBox->currentText();
584 pass.volumeCombo = volumeComboBox->currentText();
585 pass.fileSetCombo = fileSetComboBox->currentText();
586 pass.purgedCombo = purgedComboBox->currentText();
587 pass.levelCombo = levelComboBox->currentText();
588 pass.statusCombo = statusComboBox->currentText();
590 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
591 new JobPlot(pageSelectorTreeWidgetItem, pass);
594 * Save user settings associated with this page
596 void JobList::writeSettings()
598 QSettings settings(m_console->m_dir->name(), "bat");
599 settings.beginGroup(m_groupText);
600 settings.setValue(m_splitText, m_splitter->saveState());
605 * Read and restore user settings associated with this page
607 void JobList::readSettings()
609 m_groupText = "JobListPage";
610 m_splitText = "splitterSizes_1";
611 QSettings settings(m_console->m_dir->name(), "bat");
612 settings.beginGroup(m_groupText);
613 m_splitter->restoreState(settings.value(m_splitText).toByteArray());
618 * Function to fill m_selectedJobsCount and m_selectedJobs with selected values
620 void JobList::selectedJobsGet()
623 QList<QTableWidgetItem *> sitems = mp_tableWidget->selectedItems();
624 foreach (QTableWidgetItem *sitem, sitems) {
625 int row = sitem->row();
626 if (!rowList.contains(row)) {
633 foreach(int row, rowList) {
634 QTableWidgetItem * sitem = mp_tableWidget->item(row, m_jobIdIndex);
635 if (!first) m_selectedJobs.append(",");
637 m_selectedJobs.append(sitem->text());
639 m_selectedJobsCount = rowList.count();
640 if (m_selectedJobsCount > 1) {
641 QString text = QString("Delete list of %1 Jobs").arg(m_selectedJobsCount);
642 actionDeleteJob->setText(text);
644 actionDeleteJob->setText("Delete Single Job");