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 query += "SELECT Job.Jobid AS Id, Job.Name AS JobName, "
158 " Client.Name AS Client,"
159 " Job.Starttime AS JobStart, Job.Type AS JobType,"
160 " Job.Level AS BackupLevel, Job.Jobfiles AS FileCount,"
161 " Job.JobBytes AS Bytes,"
162 " Job.JobStatus AS Status, Status.JobStatusLong AS StatusLong,"
163 " Job.PurgedFiles AS Purged, FileSet.FileSet"
165 " JOIN Client ON (Client.ClientId=Job.ClientId)"
166 " JOIN Status ON (Job.JobStatus=Status.JobStatus)"
167 " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId) ";
168 QStringList conditions;
169 if (m_mediaName != "Any") {
170 query += " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId) "
171 " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId) ";
172 conditions.append("Media.VolumeName='" + m_mediaName + "'");
174 int clientIndex = clientComboBox->currentIndex();
175 if (clientIndex != -1)
176 m_clientName = clientComboBox->itemText(clientIndex);
177 if (m_clientName != "Any") {
178 conditions.append("Client.Name='" + m_clientName + "'");
180 int jobIndex = jobComboBox->currentIndex();
182 m_jobName = jobComboBox->itemText(jobIndex);
183 if ((jobIndex != -1) && (jobComboBox->itemText(jobIndex) != "Any")) {
184 conditions.append("Job.Name='" + jobComboBox->itemText(jobIndex) + "'");
186 int levelIndex = levelComboBox->currentIndex();
187 if ((levelIndex != -1) && (levelComboBox->itemText(levelIndex) != "Any")) {
188 conditions.append("Job.Level='" + levelComboBox->itemText(levelIndex) + "'");
190 int statusIndex = statusComboBox->currentIndex();
191 if ((statusIndex != -1) && (statusComboBox->itemText(statusIndex) != "Any")) {
192 conditions.append("Status.JobStatusLong='" + statusComboBox->itemText(statusIndex) + "'");
194 int purgedIndex = purgedComboBox->currentIndex();
195 if ((purgedIndex != -1) && (purgedComboBox->itemText(purgedIndex) != "Any")) {
196 conditions.append("Job.PurgedFiles='" + purgedComboBox->itemText(purgedIndex) + "'");
198 int fileSetIndex = fileSetComboBox->currentIndex();
199 if (fileSetIndex != -1)
200 m_filesetName = fileSetComboBox->itemText(fileSetIndex);
201 if ((fileSetIndex != -1) && (fileSetComboBox->itemText(fileSetIndex) != "Any")) {
202 conditions.append("FileSet.FileSet='" + fileSetComboBox->itemText(fileSetIndex) + "'");
204 /* If Limit check box For limit by days is checked */
205 if (daysCheckBox->checkState() == Qt::Checked) {
206 QDateTime stamp = QDateTime::currentDateTime().addDays(-daysSpinBox->value());
207 QString since = stamp.toString(Qt::ISODate);
208 conditions.append("Job.Starttime>'" + since + "'");
211 foreach (QString condition, conditions) {
213 query += " WHERE " + condition;
216 query += " AND " + condition;
220 query += " ORDER BY Job.Starttime DESC, Job.JobId DESC";
221 /* If Limit check box for limit records returned is checked */
222 if (limitCheckBox->checkState() == Qt::Checked) {
224 limit.setNum(limitSpinBox->value());
225 query += " LIMIT " + limit;
228 /* Set up the Header for the table */
229 QStringList headerlist = (QStringList()
230 << "Job Id" << "Job Name" << "Client" << "Job Starttime" << "Job Type"
231 << "Job Level" << "Job Files" << "Job Bytes" << "Job Status" << "Purged" << "File Set" );
232 m_purgedIndex = headerlist.indexOf("Purged");
233 m_typeIndex = headerlist.indexOf("Job Type");
234 m_statusIndex = headerlist.indexOf("Job Status");
235 m_startIndex = headerlist.indexOf("Job Starttime");
236 m_filesIndex = headerlist.indexOf("Job Files");
237 m_bytesIndex = headerlist.indexOf("Job Bytes");
239 /* Initialize the QTableWidget */
240 m_checkCurrentWidget = false;
241 mp_tableWidget->clear();
242 m_checkCurrentWidget = true;
243 mp_tableWidget->setColumnCount(headerlist.size());
244 mp_tableWidget->setHorizontalHeaderLabels(headerlist);
246 if (mainWin->m_sqlDebug) {
247 Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data());
249 if (m_console->sql_cmd(query, results)) {
250 m_resultCount = results.count();
252 QTableWidgetItem* p_tableitem;
254 QStringList fieldlist;
255 mp_tableWidget->setRowCount(results.size());
258 /* Iterate through the record returned from the query */
259 foreach (resultline, results) {
260 fieldlist = resultline.split("\t");
262 bool m_statusIndexDone = false;
263 QString statusCode("");
264 /* Iterate through fields in the record */
265 foreach (field, fieldlist) {
266 field = field.trimmed(); /* strip leading & trailing spaces */
267 if ((column == m_statusIndex) && (!m_statusIndexDone)){
268 m_statusIndexDone = true;
271 p_tableitem = new QTableWidgetItem(field,1);
272 p_tableitem->setFlags(0);
273 p_tableitem->setForeground(blackBrush);
274 mp_tableWidget->setItem(row, column, p_tableitem);
275 if (column == m_statusIndex)
276 setStatusColor(p_tableitem, statusCode);
283 /* Resize the columns */
284 mp_tableWidget->resizeColumnsToContents();
285 mp_tableWidget->resizeRowsToContents();
286 mp_tableWidget->verticalHeader()->hide();
287 if ((m_mediaName != "Any") && (m_resultCount == 0)){
288 /* for context sensitive searches, let the user know if there were no
290 QMessageBox::warning(this, tr("Bat"),
291 tr("The Jobs query returned no results.\n"
292 "Press OK to continue?"), QMessageBox::Ok );
296 void JobList::setStatusColor(QTableWidgetItem *item, QString &field)
298 QString greenchars("TCR");
299 QString redchars("BEf");
300 QString yellowchars("eDAFSMmsjdctp");
301 if (greenchars.contains(field, Qt::CaseSensitive)) {
302 item->setBackground(Qt::green);
303 } else if (redchars.contains(field, Qt::CaseSensitive)) {
304 item->setBackground(Qt::red);
305 } else if (yellowchars.contains(field, Qt::CaseSensitive)){
306 item->setBackground(Qt::yellow);
311 * When the treeWidgetItem in the page selector tree is singleclicked, Make sure
312 * The tree has been populated.
314 void JobList::PgSeltreeWidgetClicked()
323 * Virtual function override of pages function which is called when this page
324 * is visible on the stack
326 void JobList::currentStackItem()
335 * Virtual Function to return the name for the medialist tree widget
337 void JobList::treeWidgetName(QString &desc)
339 if ((m_mediaName == "") && (m_clientName == "") && (m_jobName == "") && (m_filesetName == "")) {
343 if (m_mediaName != "" ) {
344 desc += "of Volume " + m_mediaName;
346 if (m_clientName != "" ) {
347 desc += "of Client " + m_clientName;
349 if (m_jobName != "" ) {
350 desc += "of Job " + m_jobName;
352 if (m_filesetName != "" ) {
353 desc += "of fileset " + m_filesetName;
359 * This functions much line tableItemChanged for trees like the page selector,
360 * but I will do much less here
362 void JobList::tableItemChanged(QTableWidgetItem *currentItem, QTableWidgetItem * /*previousItem*/)
364 if (m_checkCurrentWidget) {
365 int row = currentItem->row();
366 QTableWidgetItem* jobitem = mp_tableWidget->item(row, 0);
367 m_currentJob = jobitem->text();
369 /* include purged action or not */
370 jobitem = mp_tableWidget->item(row, m_purgedIndex);
371 QString purged = jobitem->text();
372 mp_tableWidget->removeAction(actionPurgeFiles);
374 mp_tableWidget->addAction(actionPurgeFiles);
376 /* include restore from time and job action or not */
377 jobitem = mp_tableWidget->item(row, m_typeIndex);
378 QString type = jobitem->text();
379 mp_tableWidget->removeAction(actionRestoreFromJob);
380 mp_tableWidget->removeAction(actionRestoreFromTime);
382 mp_tableWidget->addAction(actionRestoreFromJob);
383 mp_tableWidget->addAction(actionRestoreFromTime);
385 /* include cancel action or not */
386 jobitem = mp_tableWidget->item(row, m_statusIndex);
387 QString status = jobitem->text();
388 mp_tableWidget->removeAction(actionCancelJob);
389 if (status == "Running") {
390 mp_tableWidget->addAction(actionCancelJob);
396 * Function to create connections for context sensitive menu for this and
399 void JobList::createConnections()
401 /* connect to the action specific to this pages class that shows up in the
402 * page selector tree */
403 connect(actionRefreshJobList, SIGNAL(triggered()), this,
404 SLOT(populateTable()));
405 connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable()));
406 connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable()));
407 /* for the tableItemChanged to maintain m_currentJob */
408 connect(mp_tableWidget, SIGNAL(
409 currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)),
410 this, SLOT(tableItemChanged(QTableWidgetItem *, QTableWidgetItem *)));
412 /* Do what is required for the local context sensitive menu */
415 /* setContextMenuPolicy is required */
416 mp_tableWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
419 mp_tableWidget->addAction(actionRefreshJobList);
420 mp_tableWidget->addAction(actionListJobid);
421 mp_tableWidget->addAction(actionListFilesOnJob);
422 mp_tableWidget->addAction(actionListJobMedia);
423 mp_tableWidget->addAction(actionListVolumes);
424 mp_tableWidget->addAction(actionDeleteJob);
425 mp_tableWidget->addAction(actionPurgeFiles);
426 mp_tableWidget->addAction(actionRestoreFromJob);
427 mp_tableWidget->addAction(actionRestoreFromTime);
428 mp_tableWidget->addAction(actionShowLogForJob);
430 /* Make Connections */
431 connect(actionListJobid, SIGNAL(triggered()), this,
432 SLOT(consoleListJobid()));
433 connect(actionListFilesOnJob, SIGNAL(triggered()), this,
434 SLOT(consoleListFilesOnJob()));
435 connect(actionListJobMedia, SIGNAL(triggered()), this,
436 SLOT(consoleListJobMedia()));
437 connect(actionListVolumes, SIGNAL(triggered()), this,
438 SLOT(consoleListVolumes()));
439 connect(actionDeleteJob, SIGNAL(triggered()), this,
440 SLOT(consoleDeleteJob()));
441 connect(actionPurgeFiles, SIGNAL(triggered()), this,
442 SLOT(consolePurgeFiles()));
443 connect(actionRestoreFromJob, SIGNAL(triggered()), this,
444 SLOT(preRestoreFromJob()));
445 connect(actionRestoreFromTime, SIGNAL(triggered()), this,
446 SLOT(preRestoreFromTime()));
447 connect(actionShowLogForJob, SIGNAL(triggered()), this,
448 SLOT(showLogForJob()));
449 connect(actionCancelJob, SIGNAL(triggered()), this,
450 SLOT(consoleCancelJob()));
451 connect(actionListJobTotals, SIGNAL(triggered()), this,
452 SLOT(consoleListJobTotals()));
454 m_contextActions.append(actionRefreshJobList);
455 m_contextActions.append(actionListJobTotals);
459 * Functions to respond to local context sensitive menu sending console commands
460 * If I could figure out how to make these one function passing a string, Yaaaaaa
462 void JobList::consoleListJobid()
464 QString cmd("list jobid=");
466 if (mainWin->m_longList) { cmd.prepend("l"); }
469 void JobList::consoleListFilesOnJob()
471 QString cmd("list files jobid=");
473 if (mainWin->m_longList) { cmd.prepend("l"); }
476 void JobList::consoleListJobMedia()
478 QString cmd("list jobmedia jobid=");
480 if (mainWin->m_longList) { cmd.prepend("l"); }
483 void JobList::consoleListVolumes()
485 QString cmd("list volumes jobid=");
487 if (mainWin->m_longList) { cmd.prepend("l"); }
490 void JobList::consoleListJobTotals()
492 QString cmd("list jobtotals");
494 if (mainWin->m_longList) { cmd.prepend("l"); }
497 void JobList::consoleDeleteJob()
499 if (QMessageBox::warning(this, tr("Bat"),
500 tr("Are you sure you want to delete?? !!!.\n"
501 "This delete command is used to delete a Job record and all associated catalog"
502 " records that were created. This command operates only on the Catalog"
503 " database and has no effect on the actual data written to a Volume. This"
504 " command can be dangerous and we strongly recommend that you do not use"
505 " it unless you know what you are doing. The Job and all its associated"
506 " records (File and JobMedia) will be deleted from the catalog."
507 "Press OK to proceed with delete operation.?"),
508 QMessageBox::Ok | QMessageBox::Cancel)
509 == QMessageBox::Cancel) { return; }
511 QString cmd("delete job jobid=");
515 void JobList::consolePurgeFiles()
517 if (QMessageBox::warning(this, tr("Bat"),
518 tr("Are you sure you want to purge ?? !!!.\n"
519 "The Purge command will delete associated Catalog database records from Jobs and"
520 " Volumes without considering the retention period. Purge works only on the"
521 " Catalog database and does not affect data written to Volumes. This command can"
522 " be dangerous because you can delete catalog records associated with current"
523 " backups of files, and we recommend that you do not use it unless you know what"
525 "Press OK to proceed with the purge operation?"),
526 QMessageBox::Ok | QMessageBox::Cancel)
527 == QMessageBox::Cancel) { return; }
529 QString cmd("purge files jobid=");
535 * Subroutine to call preRestore to restore from a select job
537 void JobList::preRestoreFromJob()
539 new prerestorePage(m_currentJob, R_JOBIDLIST);
543 * Subroutine to call preRestore to restore from a select job
545 void JobList::preRestoreFromTime()
547 new prerestorePage(m_currentJob, R_JOBDATETIME);
551 * Subroutine to call class to show the log in the database from that job
553 void JobList::showLogForJob()
555 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
556 new JobLog(m_currentJob, pageSelectorTreeWidgetItem);
560 * Cancel a running job
562 void JobList::consoleCancelJob()
564 QString cmd("cancel jobid=");
572 void JobList::graphTable()
575 pass.recordLimitCheck = limitCheckBox->checkState();
576 pass.daysLimitCheck = daysCheckBox->checkState();
577 pass.recordLimitSpin = limitSpinBox->value();
578 pass.daysLimitSpin = daysSpinBox->value();
579 pass.jobCombo = jobComboBox->currentText();
580 pass.clientCombo = clientComboBox->currentText();
581 pass.volumeCombo = volumeComboBox->currentText();
582 pass.fileSetCombo = fileSetComboBox->currentText();
583 pass.purgedCombo = purgedComboBox->currentText();
584 pass.levelCombo = levelComboBox->currentText();
585 pass.statusCombo = statusComboBox->currentText();
587 QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this);
588 new JobPlot(pageSelectorTreeWidgetItem, pass);
591 * Save user settings associated with this page
593 void JobList::writeSettings()
595 QSettings settings(m_console->m_dir->name(), "bat");
596 settings.beginGroup(m_groupText);
597 settings.setValue(m_splitText, m_splitter->saveState());
602 * Read and restore user settings associated with this page
604 void JobList::readSettings()
606 m_groupText = "JobListPage";
607 m_splitText = "splitterSizes_1";
608 QSettings settings(m_console->m_dir->name(), "bat");
609 settings.beginGroup(m_groupText);
610 m_splitter->restoreState(settings.value(m_splitText).toByteArray());