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_purgedIndex = headerlist.indexOf("Purged");
235 m_typeIndex = headerlist.indexOf("Job Type");
236 m_statusIndex = headerlist.indexOf("Job Status");
237 m_startIndex = headerlist.indexOf("Job Starttime");
238 m_filesIndex = headerlist.indexOf("Job Files");
239 m_bytesIndex = headerlist.indexOf("Job Bytes");
241 /* Initialize the QTableWidget */
242 m_checkCurrentWidget = false;
243 mp_tableWidget->clear();
244 m_checkCurrentWidget = true;
245 mp_tableWidget->setColumnCount(headerlist.size());
246 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
248 if (mainWin->m_sqlDebug) {
249 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
251 if (m_console->sql_cmd(query, results)) {
252 m_resultCount = results.count();
254 QTableWidgetItem* p_tableitem;
256 QStringList fieldlist;
257 mp_tableWidget->setRowCount(results.size());
260 /* Iterate through the record returned from the query */
261 foreach (resultline, results) {
262 fieldlist = resultline.split("\t");
264 bool m_statusIndexDone = false;
265 QString statusCode("");
266 /* Iterate through fields in the record */
267 foreach (field, fieldlist) {
268 field = field.trimmed(); /* strip leading & trailing spaces */
269 if ((column == m_statusIndex) && (!m_statusIndexDone)){
270 m_statusIndexDone = true;
273 p_tableitem = new QTableWidgetItem(field,1);
274 p_tableitem->setFlags(0);
275 p_tableitem->setForeground(blackBrush);
276 mp_tableWidget->setItem(row, column, p_tableitem);
277 if (column == m_statusIndex)
278 setStatusColor(p_tableitem, statusCode);
285 /* Resize the columns */
286 mp_tableWidget->resizeColumnsToContents();
287 mp_tableWidget->resizeRowsToContents();
288 mp_tableWidget->verticalHeader()->hide();
289 if ((m_mediaName != "Any") && (m_resultCount == 0)){
290 /* for context sensitive searches, let the user know if there were no
292 QMessageBox::warning(this, tr("Bat"),
293 tr("The Jobs query returned no results.\n"
294 "Press OK to continue?"), QMessageBox::Ok );
298 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
300 QString greenchars("TCR");
301 QString redchars("BEf");
302 QString yellowchars("eDAFSMmsjdctp");
303 if (greenchars.contains(field, Qt::CaseSensitive)) {
304 item->setBackground(Qt::green);
305 } else if (redchars.contains(field, Qt::CaseSensitive)) {
306 item->setBackground(Qt::red);
307 } else if (yellowchars.contains(field, Qt::CaseSensitive)){
308 item->setBackground(Qt::yellow);
313 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
314 * The tree has been populated.
316 void JobList::PgSeltreeWidgetClicked()
325 * Virtual function override of pages function which is called when this page
326 * is visible on the stack
328 void JobList::currentStackItem()
337 * Virtual Function to return the name for the medialist tree widget
339 void JobList::treeWidgetName(QString &desc)
341 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
345 if (m_mediaName != "" ) {
346 desc += "of Volume " + m_mediaName;
348 if (m_clientName != "" ) {
349 desc += "of Client " + m_clientName;
351 if (m_jobName != "" ) {
352 desc += "of Job " + m_jobName;
354 if (m_filesetName != "" ) {
355 desc += "of fileset " + m_filesetName;
361 * This functions much line tableItemChanged for trees like the page selector,
362 * but I will do much less here
364 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
366 if (m_checkCurrentWidget) {
367 int row = currentItem->row();
368 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
369 m_currentJob = jobitem->text();
371 /* include purged action or not */
372 jobitem = mp_tableWidget->item(row, m_purgedIndex);
373 QString purged = jobitem->text();
374 mp_tableWidget->removeAction(actionPurgeFiles);
376 mp_tableWidget->addAction(actionPurgeFiles);
378 /* include restore from time and job action or not */
379 jobitem = mp_tableWidget->item(row, m_typeIndex);
380 QString type = jobitem->text();
381 mp_tableWidget->removeAction(actionRestoreFromJob);
382 mp_tableWidget->removeAction(actionRestoreFromTime);
384 mp_tableWidget->addAction(actionRestoreFromJob);
385 mp_tableWidget->addAction(actionRestoreFromTime);
387 /* include cancel action or not */
388 jobitem = mp_tableWidget->item(row, m_statusIndex);
389 QString status = jobitem->text();
390 mp_tableWidget->removeAction(actionCancelJob);
391 if (status == "Running") {
392 mp_tableWidget->addAction(actionCancelJob);
398 * Function to create connections for context sensitive menu for this and
401 void JobList::createConnections()
403 /* connect to the action specific to this pages class that shows up in the
404 * page selector tree */
405 connect(actionRefreshJobList, SIGNAL(triggered()), this,
406 SLOT(populateTable()));
407 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
408 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
409 /* for the tableItemChanged to maintain m_currentJob */
410 connect(mp_tableWidget, SIGNAL(
411 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
412 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
414 /* Do what is required for the local context sensitive menu */
417 /* setContextMenuPolicy is required */
418 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
421 mp_tableWidget->addAction(actionRefreshJobList);
422 mp_tableWidget->addAction(actionListJobid);
423 mp_tableWidget->addAction(actionListFilesOnJob);
424 mp_tableWidget->addAction(actionListJobMedia);
425 mp_tableWidget->addAction(actionListVolumes);
426 mp_tableWidget->addAction(actionDeleteJob);
427 mp_tableWidget->addAction(actionPurgeFiles);
428 mp_tableWidget->addAction(actionRestoreFromJob);
429 mp_tableWidget->addAction(actionRestoreFromTime);
430 mp_tableWidget->addAction(actionShowLogForJob);
432 /* Make Connections */
433 connect(actionListJobid, SIGNAL(triggered()), this,
434 SLOT(consoleListJobid()));
435 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
436 SLOT(consoleListFilesOnJob()));
437 connect(actionListJobMedia, SIGNAL(triggered()), this,
438 SLOT(consoleListJobMedia()));
439 connect(actionListVolumes, SIGNAL(triggered()), this,
440 SLOT(consoleListVolumes()));
441 connect(actionDeleteJob, SIGNAL(triggered()), this,
442 SLOT(consoleDeleteJob()));
443 connect(actionPurgeFiles, SIGNAL(triggered()), this,
444 SLOT(consolePurgeFiles()));
445 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
446 SLOT(preRestoreFromJob()));
447 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
448 SLOT(preRestoreFromTime()));
449 connect(actionShowLogForJob, SIGNAL(triggered()), this,
450 SLOT(showLogForJob()));
451 connect(actionCancelJob, SIGNAL(triggered()), this,
452 SLOT(consoleCancelJob()));
453 connect(actionListJobTotals, SIGNAL(triggered()), this,
454 SLOT(consoleListJobTotals()));
456 m_contextActions.append(actionRefreshJobList);
457 m_contextActions.append(actionListJobTotals);
461 * Functions to respond to local context sensitive menu sending console commands
462 * If I could figure out how to make these one function passing a string, Yaaaaaa
464 void JobList::consoleListJobid()
466 QString cmd("list jobid=");
468 if (mainWin->m_longList) { cmd.prepend("l"); }
471 void JobList::consoleListFilesOnJob()
473 QString cmd("list files jobid=");
475 if (mainWin->m_longList) { cmd.prepend("l"); }
478 void JobList::consoleListJobMedia()
480 QString cmd("list jobmedia jobid=");
482 if (mainWin->m_longList) { cmd.prepend("l"); }
485 void JobList::consoleListVolumes()
487 QString cmd("list volumes jobid=");
489 if (mainWin->m_longList) { cmd.prepend("l"); }
492 void JobList::consoleListJobTotals()
494 QString cmd("list jobtotals");
495 if (mainWin->m_longList) { cmd.prepend("l"); }
498 void JobList::consoleDeleteJob()
500 if (QMessageBox::warning(this, tr("Bat"),
501 tr("Are you sure you want to delete?? !!!.\n"
502 "This delete command is used to delete a Job record and all associated catalog"
503 " records that were created. This command operates only on the Catalog"
504 " database and has no effect on the actual data written to a Volume. This"
505 " command can be dangerous and we strongly recommend that you do not use"
506 " it unless you know what you are doing. The Job and all its associated"
507 " records (File and JobMedia) will be deleted from the catalog."
508 "Press OK to proceed with delete operation.?"),
509 QMessageBox::Ok | QMessageBox::Cancel)
510 == QMessageBox::Cancel) { return; }
512 QString cmd("delete job jobid=");
516 void JobList::consolePurgeFiles()
518 if (QMessageBox::warning(this, tr("Bat"),
519 tr("Are you sure you want to purge ?? !!!.\n"
520 "The Purge command will delete associated Catalog database records from Jobs and"
521 " Volumes without considering the retention period. Purge works only on the"
522 " Catalog database and does not affect data written to Volumes. This command can"
523 " be dangerous because you can delete catalog records associated with current"
524 " backups of files, and we recommend that you do not use it unless you know what"
526 "Press OK to proceed with the purge operation?"),
527 QMessageBox::Ok | QMessageBox::Cancel)
528 == QMessageBox::Cancel) { return; }
530 QString cmd("purge files jobid=");
536 * Subroutine to call preRestore to restore from a select job
538 void JobList::preRestoreFromJob()
540 new prerestorePage(m_currentJob, R_JOBIDLIST);
544 * Subroutine to call preRestore to restore from a select job
546 void JobList::preRestoreFromTime()
548 new prerestorePage(m_currentJob, R_JOBDATETIME);
552 * Subroutine to call class to show the log in the database from that job
554 void JobList::showLogForJob()
556 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
557 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
561 * Cancel a running job
563 void JobList::consoleCancelJob()
565 QString cmd("cancel jobid=");
573 void JobList::graphTable()
576 pass.recordLimitCheck = limitCheckBox->checkState();
577 pass.daysLimitCheck = daysCheckBox->checkState();
578 pass.recordLimitSpin = limitSpinBox->value();
579 pass.daysLimitSpin = daysSpinBox->value();
580 pass.jobCombo = jobComboBox->currentText();
581 pass.clientCombo = clientComboBox->currentText();
582 pass.volumeCombo = volumeComboBox->currentText();
583 pass.fileSetCombo = fileSetComboBox->currentText();
584 pass.purgedCombo = purgedComboBox->currentText();
585 pass.levelCombo = levelComboBox->currentText();
586 pass.statusCombo = statusComboBox->currentText();
588 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
589 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());