From: Dirk H Bartley Date: Mon, 25 Jun 2007 19:15:34 +0000 (+0000) Subject: The results of running the merge command: X-Git-Tag: Release-7.0.0~6075 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=cf2a603d4400c5403599e03a5d860d055ba760ad;p=bacula%2Fbacula The results of running the merge command: svn merge -r5003:5087 http://bacula.svn.sourceforge.net/svnroot/bacula/branches/working/qt-console . To see the log of changes made use svn log -r5003:5087 http://bacula.svn.sourceforge.net/svnroot/bacula/branches/working/qt-console git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@5090 91ce42f0-d328-0410-95d8-f526ca767f89 --- diff --git a/bacula/src/qt-console/External-qt-console b/bacula/src/qt-console/External-qt-console new file mode 100644 index 0000000000..2a4949043f --- /dev/null +++ b/bacula/src/qt-console/External-qt-console @@ -0,0 +1,26 @@ +# This file provides information about the External dependencies required by +# Bacula. +# +# There are four fields delimited by |. Only the first two fields are +# required. The other two are used when the top level directory of the +# archive is not the same as the file name with any suffixes removed. +# +# Field 1 is the name of the dependency. It is used to define the +# name of the three variables which are assigned the values of fields 2 to 4. +# +# Field 2 is the URL of the archive. It is assigned to the variable +# URL_[field1]. +# +# Field 3 is the top directory of the archive or the name of a directory that +# must be created and the archive extracted into it. It is assigned to the +# variable DIR_[field1]. +# +# Field 4 indicates if the directory specified in field 3 must be created +# first and the archive extracted into it. It is assigned to the variable +# MKD_[field1] +# +QWT|http://www.bacula.org/depkgs/qwt-5.0.2.tar.bz2|qwt-5.0.2|1 +# +# Original location +# +#QWT|http://superb-west.dl.sourceforge.net/sourceforge/qwt/qwt-5.0.2.tar.bz2|qwt-5.0.2|1 diff --git a/bacula/src/qt-console/README b/bacula/src/qt-console/README index c1e02cd44e..74b291cc22 100644 --- a/bacula/src/qt-console/README +++ b/bacula/src/qt-console/README @@ -6,6 +6,17 @@ development. If you want to help, please contact Kern directly. If you want to build it, you need Qt4 loaded and setup as your default Qt or with the appropriate Qt Environment variables set. +6/24/07 +There is now one dependancy, it is qwt. It compiles just fine with +either qwt-5.0.2 or qwt-5.0.1. If you run the command + + ./build-depkgs-qt-console + +It should download and run the appropriate commands to build and install qwt +for you. The script will download the package into a subdirectory named +depkgs. Then it will configure, make and install qwt. The installation will +go into a subdirectory named qwt in the main qt-console directory. + To build bat, you simply enter: qmake @@ -35,7 +46,7 @@ Items not implemented: Design decisions: - If possible all code for a particular component will be kept in - and appropriate subdirectory. + an appropriate subdirectory. - All private class variables are named "m_xxx" this makes it very clear if one is referencing a class variable or a local. - All signal/slots are connected by explict code (most all are diff --git a/bacula/src/qt-console/TODO b/bacula/src/qt-console/TODO index 5b46eb5935..7ba0c738b3 100644 --- a/bacula/src/qt-console/TODO +++ b/bacula/src/qt-console/TODO @@ -11,9 +11,9 @@ closing would bring the previous one back. ======================================================== This release or next: -A window showing a list of schedule resources. +A page showing a list of schedule resources. -A list of message resources?? +A page list of message resources?? Kern discussed windows showing statistics like web based interfaces. @@ -34,10 +34,15 @@ cancelled graphically. Add a status client window. Keep updating showing what file is being processed. +Documentation, Documentation, Documentaion. Help. Add help documentation. +Have context sensitve help. + bRestore add code to get working. May be in brestore, find a file by name, find a directory by name +Interfaces to commands like bextract, bscan, bcopy, btape????? + Is there a way to query the director/database for whether a storage is currently mounted so I am not presenting both mount and unmount to the user?? Yes, but it requires being able to directly connect to the SD (at least diff --git a/bacula/src/qt-console/bat.pro.in b/bacula/src/qt-console/bat.pro.in index ad45ffcfd7..ec236623a9 100644 --- a/bacula/src/qt-console/bat.pro.in +++ b/bacula/src/qt-console/bat.pro.in @@ -18,10 +18,11 @@ TEMPLATE = app TARGET = bat DEPENDPATH += . INCLUDEPATH += . ./console ./restore ./select -INCLUDEPATH += .. -LIBS += -L../lib +INCLUDEPATH += .. ./qwt/include +LIBS += -L../lib LIBS += -lbac LIBS += -lssl -lcrypto +LIBS += -L./qwt/lib -lqwt RESOURCES = main.qrc MOC_DIR = moc OBJECTS_DIR = obj @@ -42,6 +43,7 @@ FORMS += medialist/medialist.ui mediaedit/mediaedit.ui joblist/joblist.ui FORMS += clients/clients.ui storage/storage.ui fileset/fileset.ui FORMS += joblog/joblog.ui jobs/jobs.ui FORMS += help/help.ui +FORMS += jobgraphs/jobplotcontrols.ui HEADERS += mainwin.h bat.h bat_conf.h qstd.h SOURCES += main.cpp bat_conf.cpp mainwin.cpp qstd.cpp @@ -114,6 +116,9 @@ SOURCES += jobs/jobs.cpp HEADERS += restore/restoretree.h SOURCES += restore/restoretree.cpp +## Job Step Graphs +HEADERS += jobgraphs/jobplot.h +SOURCES += jobgraphs/jobplot.cpp # Help dialog HEADERS += help/help.h diff --git a/bacula/src/qt-console/build-depkgs-qt-console b/bacula/src/qt-console/build-depkgs-qt-console new file mode 100755 index 0000000000..4c9230607a --- /dev/null +++ b/bacula/src/qt-console/build-depkgs-qt-console @@ -0,0 +1,168 @@ +#!/bin/sh +# +# This file is driven by the parameters that are defined in +# the file External-qt-console +# + +usage() +{ + echo "usage: $0 [-h] [-C] [] [] ..." + echo " -h Displays this usage" + echo " -C Clobbers (overwrites) the source code by " + echo " reextracting the archive and reapplying the" + echo " patches." + echo "" + echo " Optional dependency, If none are given then all" + echo " of them will be built." + echo "" + echo "Valid dependencies are:" + grep -v '^#' < External-qt-console | cut -d'|' -f1 | cut -d'_' -f1 | tr A-Z a-z | sort -u | awk '{ print " " $1 }' +} + +CLOBBER_SOURCE= + +while getopts "hHC" opt; do + case ${opt} in + H|h|\?) usage;exit 1;; + C) CLOBBER_SOURCE=true;; + esac +done + +[ ${OPTIND} -gt 1 ] && shift `expr ${OPTIND} - 1` + +cwd=`pwd` +cd `dirname $0` +SCRIPT_DIR=`pwd` + +TOP_DIR=`pwd` + +[ ! -e ${TOP_DIR}/depkgs ] && mkdir ${TOP_DIR}/depkgs +cd ${TOP_DIR}/depkgs +DEPPKG_DIR=`pwd` + +OLD_IFS=${IFS};IFS="|"; +while read package url dir mkd; do + case ${package} in + \#*) ;; + *) eval "URL_${package}=${url};DIR_${package}=${dir};MKD_${package}=${mkd}";; + esac +done < ${SCRIPT_DIR}/External-qt-console +IFS=${OLD_IFS};unset OLD_IFS + +get_source() +{ + URL=$1 + SRC_DIR=$2 + MAKE_DIR=$3 + ARCHIVE=`basename ${URL}` +echo "in get_source URL is $URL SRC_DIR is $SRC_DIR MAKE_DIR is $MAKE_DIR ARCHIVE is $ARCHIVE" + + case ${ARCHIVE} in + *.tar.gz) ARCHIVER="tar xzf"; [ -z "${SRC_DIR}" ] && SRC_DIR=`expr "${ARCHIVE}" : '\(.*\)\.tar\.gz'`;; + *.tar.bz2) ARCHIVER="tar xjf"; [ -z "${SRC_DIR}" ] && SRC_DIR=`expr "${ARCHIVE}" : '\(.*\)\.tar\.bz2'`;; + *.zip) ARCHIVER="unzip -q"; [ -z "${SRC_DIR}" ] && SRC_DIR=`expr "${ARCHIVE}" : '\(.*\)\.zip'`;; + *.exe) ARCHIVER=""; [ -z "${SRC_DIR}" ] && SRC_DIR=`expr "${ARCHIVE}" : '\(.*\)\.zip'`;; + *) echo "Unsupported archive type - $ARCHIVE"; exit 1;; + esac + + cd ${DEPPKG_DIR}/src + + if [ ! -e "${ARCHIVE}" ] + then + echo "Downloading ${URL}" + if wget --passive-ftp "${URL}" + then + : + else + echo "Unable to download ${ARCHIVE}" + exit 1 + fi + fi + + [ -z "${ARCHIVER}" ] && return 0 + + if [ ! -e "${SRC_DIR}" -o "${CLOBBER_SOURCE}" = "true" ] + then + rm -rf ${SRC_DIR} + echo "Extracting ${ARCHIVE}" + if [ "${MAKE_DIR}" = "true" ] + then + mkdir ${SRC_DIR} + cd ${SRC_DIR} + ${ARCHIVER} ../${ARCHIVE} > ../${ARCHIVE}.log 2>&1 + else + ${ARCHIVER} ${ARCHIVE} > ${ARCHIVE}.log 2>&1 + cd ${SRC_DIR} + fi + return 0 + fi + + cd ${SRC_DIR} + return 1 +} + +parse_output() +{ + sed -ne '/\\$/N' -e 's/\\\n//' -e 's/\t\+/ /g' -e 's/ \+/ /g' \ + -e '/ error: /p' \ + -e "s%.*Entering directory[ ]\\+.${DEPPKG_DIR}/\\([^ ]\+\).%Entering \\1%p" \ + -e "s%.*Leaving directory[ ]\\+.${DEPPKG_DIR}/\\([^ ]\+.\).%Leaving \\1%p" \ + -e '/gcc \|g\+\+ \|ar /!d' \ + -e 's/ \(\.\.\/\)\+/ /g' \ + -e 's/.* \([^ ]\+\(\.c\|\.cpp\|\.cc\|\.cxx\)\)\( .*\|\)$/Compiling \1/p' \ + -e 's/.* \([^ ]\+\.s\)\( .*\|\)$/Assembling \1/p' \ + -e 's/.*ar [^ ]\+ \([^ ]\+\)\(\( [^ ]\+\.o\)\+\)/Updating \1 -\2/p' \ + -e 's/.* -o \([^ ]\+\)\( .*\|\)$/Linking \1/p' +} + +do_patch() +{ + PATCH_FILE=${SCRIPT_DIR}/patches/$1; shift + + if patch -f -p0 "$@" >>patch.log < ${PATCH_FILE} + then + : + else + echo "Patch failed - Check `pwd`/patch.log" > /dev/tty + exit 1 + fi +} + +do_make() +{ + if make -f "$@" 2>&1 + then + : + else + echo "Make failed - Check `pwd`/make.log" > /dev/tty + exit 1 + fi | tee -a make.log #| parse_output +} + +process_qwt() +{ + get_source "${URL_QWT}" "${DIR_QWT}" "${MKD_QWT}" + echo "Building qwt" + echo "${DEPPKG_DIR}" + pwd + > make.log + echo "unix {" >${TOP_DIR}/depkgs/qwt-5.0.2/qwtconfig.pri + echo " INSTALLBASE = ${TOP_DIR}/qwt" >>${TOP_DIR}/depkgs/qwt-5.0.2/qwtconfig.pri + echo "}" >>${TOP_DIR}/depkgs/qwt-5.0.2/qwtconfig.pri + cat ${TOP_DIR}/qwtconfig.pri >>${TOP_DIR}/depkgs/qwt-5.0.2/qwtconfig.pri + qmake + do_make Makefile + echo "Installing" + do_make Makefile install +} + +if [ "$#" -eq 0 ] +then + process_qwt +else + for dependency in "$@" + do + eval "process_${dependency}" + done + +fi diff --git a/bacula/src/qt-console/console/console.cpp b/bacula/src/qt-console/console/console.cpp index b722e26270..2911e2c4a6 100644 --- a/bacula/src/qt-console/console/console.cpp +++ b/bacula/src/qt-console/console/console.cpp @@ -857,3 +857,41 @@ void Console::consoleReload() QString cmd("reload"); consoleCommand(cmd); } + +/* Function to get a list of volumes */ +void Console::getVolumeList(QStringList &volumeList) +{ + QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media"); + if (mainWin->m_sqlDebug) { + Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data()); + } + QStringList results; + if (sql_cmd(query, results)) { + QString field; + QStringList fieldlist; + /* Iterate through the lines of results. */ + foreach (QString resultline, results) { + fieldlist = resultline.split("\t"); + volumeList.append(fieldlist[0]); + } /* foreach resultline */ + } /* if results from query */ +} + +/* Function to get a list of volumes */ +void Console::getStatusList(QStringList &statusLongList) +{ + QString statusQuery("SELECT JobStatusLong FROM Status"); + if (mainWin->m_sqlDebug) { + Pmsg1(000, "Query cmd : %s\n",statusQuery.toUtf8().data()); + } + QStringList statusResults; + if (sql_cmd(statusQuery, statusResults)) { + QString field; + QStringList fieldlist; + /* Iterate through the lines of results. */ + foreach (QString resultline, statusResults) { + fieldlist = resultline.split("\t"); + statusLongList.append(fieldlist[0]); + } /* foreach resultline */ + } /* if results from statusquery */ +} diff --git a/bacula/src/qt-console/console/console.h b/bacula/src/qt-console/console/console.h index a5878a615e..9c7ca74680 100644 --- a/bacula/src/qt-console/console/console.h +++ b/bacula/src/qt-console/console/console.h @@ -102,6 +102,8 @@ public: void getDirResName(QString &); void startTimer(); void stopTimer(); + void getVolumeList(QStringList &); + void getStatusList(QStringList &); QStringList job_list; QStringList client_list; diff --git a/bacula/src/qt-console/images/graph1.png b/bacula/src/qt-console/images/graph1.png new file mode 100644 index 0000000000..9e12db09df Binary files /dev/null and b/bacula/src/qt-console/images/graph1.png differ diff --git a/bacula/src/qt-console/images/graph1.svg b/bacula/src/qt-console/images/graph1.svg new file mode 100644 index 0000000000..ecde0e9fb6 --- /dev/null +++ b/bacula/src/qt-console/images/graph1.svg @@ -0,0 +1,380 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + System Monitor + 2005-10-10 + + + Andreas Nilsson + + + + + system + monitor + performance + + + + + + Jakub Steiner + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bacula/src/qt-console/jobgraphs/jobplot.cpp b/bacula/src/qt-console/jobgraphs/jobplot.cpp new file mode 100644 index 0000000000..2128bbcc21 --- /dev/null +++ b/bacula/src/qt-console/jobgraphs/jobplot.cpp @@ -0,0 +1,582 @@ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2000-2007 Free Software Foundation Europe e.V. + + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can redistribute it and/or + modify it under the terms of version two of the GNU General Public + License as published by the Free Software Foundation and included + in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of John Walker. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, + Switzerland, email:ftf@fsfeurope.org. +*/ + +/* + * Version $Id: jobplot.cpp 4230 2007-02-21 20:07:37Z kerns $ + * + * JobPlots Class + * + * Dirk Bartley, March 2007 + * + */ + +#include +#include "bat.h" +#include "jobgraphs/jobplot.h" + + +JobPlotPass::JobPlotPass() +{ + use = false; +} + +JobPlotPass& JobPlotPass::operator=(const JobPlotPass &cp) +{ + use = cp.use; + recordLimitCheck = cp.recordLimitCheck; + daysLimitCheck = cp.daysLimitCheck; + recordLimitSpin = cp.recordLimitSpin; + daysLimitSpin = cp.daysLimitSpin; + jobCombo = cp.jobCombo; + clientCombo = cp.clientCombo; + volumeCombo = cp.volumeCombo; + fileSetCombo = cp.fileSetCombo; + purgedCombo = cp.purgedCombo; + levelCombo = cp.levelCombo; + statusCombo = cp.statusCombo; + return *this; +} + +/* + * Constructor for the controls class which inherits QScrollArea and a ui header + */ +JobPlotControls::JobPlotControls() +{ + setupUi(this); +} + +/* + * Constructor, this class does not inherit anything but pages. + */ +JobPlot::JobPlot(QTreeWidgetItem *parentTreeWidgetItem, JobPlotPass &passVals) +{ + setupUserInterface(); + m_name = "Job Plot"; + pgInitialize(parentTreeWidgetItem); + readSplitterSettings(); + QTreeWidgetItem* thisitem = mainWin->getFromHash(this); + thisitem->setIcon(0,QIcon(QString::fromUtf8(":images/graph1.png"))); + m_drawn = false; + + /* this invokes the pass values = operator function */ + m_pass = passVals; + m_closeable = true; + dockPage(); + /* If the values of the controls are predetermined (from joblist), then set + * this class as current window at the front of the stack */ + if (m_pass.use) + setCurrent(); + m_jobPlot->replot(); +} + +/* + * Kill, crush Destroy + */ +JobPlot::~JobPlot() +{ + writeSettings(); + m_pjd.clear(); +} + +/* + * This is called when the page selector has this page selected + */ +void JobPlot::currentStackItem() +{ + if (!m_drawn) { + setupControls(); + reGraph(); + m_drawn=true; + } + +} + +/* + * Slot for the refrehs push button, also called from constructor. + */ +void JobPlot::reGraph() +{ + /* clear m_pjd */ + m_pjd.clear(); + runQuery(); + m_jobPlot->clear(); + addCurve(); + m_jobPlot->replot(); +} + +/* + * Setup the control widgets for the graph, this are the objects from JobPlotControls + */ +void JobPlot::setupControls() +{ + QStringList graphType = QStringList() << /* "Fitted" <<*/ "Sticks" << "Lines" << "Steps" << "None"; + controls->plotTypeCombo->addItems(graphType); + QStringList symbolType = QStringList() << "Ellipse" << "Rect" << "Diamond" << "Triangle" + << "DTrianle" << "UTriangle" << "LTriangle" << "RTriangle" << "Cross" << "XCross" + << "HLine" << "Vline" << "Star1" << "Star2" << "Hexagon" << "None"; + controls->fileSymbolTypeCombo->addItems(symbolType); + controls->byteSymbolTypeCombo->addItems(symbolType); + readControlSettings(); + + controls->fileCheck->setCheckState(Qt::Checked); + controls->byteCheck->setCheckState(Qt::Checked); + connect(controls->plotTypeCombo, SIGNAL(currentIndexChanged(QString)), this, SLOT(setPlotType(QString))); + connect(controls->fileSymbolTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setFileSymbolType(int))); + connect(controls->byteSymbolTypeCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(setByteSymbolType(int))); + connect(controls->fileCheck, SIGNAL(stateChanged(int)), this, SLOT(fileCheckChanged(int))); + connect(controls->byteCheck, SIGNAL(stateChanged(int)), this, SLOT(byteCheckChanged(int))); + connect(controls->refreshButton, SIGNAL(pressed()), this, SLOT(reGraph())); + + controls->clientComboBox->addItem("Any"); + controls->clientComboBox->addItems(m_console->client_list); + + QStringList volumeList; + m_console->getVolumeList(volumeList); + controls->volumeComboBox->addItem("Any"); + controls->volumeComboBox->addItems(volumeList); + controls->jobComboBox->addItem("Any"); + controls->jobComboBox->addItems(m_console->job_list); + controls->levelComboBox->addItem("Any"); + controls->levelComboBox->addItems( QStringList() << "F" << "D" << "I"); + controls->purgedComboBox->addItem("Any"); + controls->purgedComboBox->addItems( QStringList() << "0" << "1"); + controls->fileSetComboBox->addItem("Any"); + controls->fileSetComboBox->addItems(m_console->fileset_list); + QStringList statusLongList; + m_console->getStatusList(statusLongList); + controls->statusComboBox->addItem("Any"); + controls->statusComboBox->addItems(statusLongList); + + if (m_pass.use) { + controls->limitCheckBox->setCheckState(m_pass.recordLimitCheck); + controls->limitSpinBox->setValue(m_pass.recordLimitSpin); + controls->daysCheckBox->setCheckState(m_pass.daysLimitCheck); + controls->daysSpinBox->setValue(m_pass.daysLimitSpin); + int jobIndex = controls->jobComboBox->findText(m_pass.jobCombo, Qt::MatchExactly); + if (jobIndex != -1) + controls->jobComboBox->setCurrentIndex(jobIndex); + int clientIndex = controls->clientComboBox->findText(m_pass.clientCombo, Qt::MatchExactly); + if (clientIndex != -1) + controls->clientComboBox->setCurrentIndex(clientIndex); + int volumeIndex = controls->volumeComboBox->findText(m_pass.volumeCombo, Qt::MatchExactly); + if (volumeIndex != -1) + controls->volumeComboBox->setCurrentIndex(volumeIndex); + int filesetIndex = controls->fileSetComboBox->findText(m_pass.fileSetCombo, Qt::MatchExactly); + if (filesetIndex != -1) + controls->fileSetComboBox->setCurrentIndex(filesetIndex); + int purgedIndex = controls->purgedComboBox->findText(m_pass.purgedCombo, Qt::MatchExactly); + if (purgedIndex != -1) + controls->purgedComboBox->setCurrentIndex(purgedIndex); + int levelIndex = controls->levelComboBox->findText(m_pass.levelCombo, Qt::MatchExactly); + if (levelIndex != -1) + controls->levelComboBox->setCurrentIndex(levelIndex); + int statusIndex = controls->statusComboBox->findText(m_pass.statusCombo, Qt::MatchExactly); + if (statusIndex != -1) + controls->statusComboBox->setCurrentIndex(statusIndex); + } else { + /* Set Defaults for check and spin for limits */ + controls->limitCheckBox->setCheckState(mainWin->m_recordLimitCheck ? Qt::Checked : Qt::Unchecked); + controls->limitSpinBox->setValue(mainWin->m_recordLimitVal); + controls->daysCheckBox->setCheckState(mainWin->m_daysLimitCheck ? Qt::Checked : Qt::Unchecked); + controls->daysSpinBox->setValue(mainWin->m_daysLimitVal); + } +} + +/* + * Setup the control widgets for the graph, this are the objects from JobPlotControls + */ +void JobPlot::runQuery() +{ + /* Set up query */ + QString query(""); + query += "SELECT DISTINCT " + " Job.Starttime AS JobStart," + " Job.Jobfiles AS FileCount," + " Job.JobBytes AS Bytes," + " Job.JobId AS JobId" + " FROM Job" + " LEFT OUTER JOIN Client ON (Client.ClientId=Job.ClientId)" + " LEFT OUTER JOIN FileSet ON (FileSet.FileSetId=Job.FileSetId)" + " LEFT OUTER JOIN Status ON (Job.JobStatus=Status.JobStatus)" + " LEFT OUTER JOIN JobMedia ON (JobMedia.JobId=Job.JobId)" + " LEFT OUTER JOIN Media ON (JobMedia.MediaId=Media.MediaId)"; + QStringList conditions; + int jobIndex = controls->jobComboBox->currentIndex(); + if ((jobIndex != -1) && (controls->jobComboBox->itemText(jobIndex) != "Any")) + conditions.append("Job.Name='" + controls->jobComboBox->itemText(jobIndex) + "'"); + int clientIndex = controls->clientComboBox->currentIndex(); + if ((clientIndex != -1) && (controls->clientComboBox->itemText(clientIndex) != "Any")) + conditions.append("Client.Name='" + controls->clientComboBox->itemText(clientIndex) + "'"); + int volumeIndex = controls->volumeComboBox->currentIndex(); + if ((volumeIndex != -1) && (controls->volumeComboBox->itemText(volumeIndex) != "Any")) + conditions.append("Media.VolumeName='" + controls->volumeComboBox->itemText(volumeIndex) + "'"); + int fileSetIndex = controls->fileSetComboBox->currentIndex(); + if ((fileSetIndex != -1) && (controls->fileSetComboBox->itemText(fileSetIndex) != "Any")) + conditions.append("FileSet.FileSet='" + controls->fileSetComboBox->itemText(fileSetIndex) + "'"); + int purgedIndex = controls->purgedComboBox->currentIndex(); + if ((purgedIndex != -1) && (controls->purgedComboBox->itemText(purgedIndex) != "Any")) + conditions.append("Job.PurgedFiles='" + controls->purgedComboBox->itemText(purgedIndex) + "'"); + int levelIndex = controls->levelComboBox->currentIndex(); + if ((levelIndex != -1) && (controls->levelComboBox->itemText(levelIndex) != "Any")) + conditions.append("Job.Level='" + controls->levelComboBox->itemText(levelIndex) + "'"); + int statusIndex = controls->statusComboBox->currentIndex(); + if ((statusIndex != -1) && (controls->statusComboBox->itemText(statusIndex) != "Any")) + conditions.append("Status.JobStatusLong='" + controls->statusComboBox->itemText(statusIndex) + "'"); + /* If Limit check box For limit by days is checked */ + if (controls->daysCheckBox->checkState() == Qt::Checked) { + QDateTime stamp = QDateTime::currentDateTime().addDays(-controls->daysSpinBox->value()); + QString since = stamp.toString(Qt::ISODate); + conditions.append("Job.Starttime>'" + since + "'"); + } + bool first = true; + foreach (QString condition, conditions) { + if (first) { + query += " WHERE " + condition; + first = false; + } else { + query += " AND " + condition; + } + } + /* Descending */ + query += " ORDER BY Job.Starttime DESC, Job.JobId DESC"; + /* If Limit check box for limit records returned is checked */ + if (controls->limitCheckBox->checkState() == Qt::Checked) { + QString limit; + limit.setNum(controls->limitSpinBox->value()); + query += " LIMIT " + limit; + } + + if (mainWin->m_sqlDebug) { + Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data()); + } + QString resultline; + QStringList results; + if (m_console->sql_cmd(query, results)) { + + QString field; + QStringList fieldlist; + + int row = 0; + /* Iterate through the record returned from the query */ + foreach (resultline, results) { + PlotJobData *plotJobData = new PlotJobData(); + fieldlist = resultline.split("\t"); + int column = 0; + QString statusCode(""); + /* Iterate through fields in the record */ + foreach (field, fieldlist) { + field = field.trimmed(); /* strip leading & trailing spaces */ + if (column == 0) { + plotJobData->dt = QDateTime::fromString(field, mainWin->m_dtformat); + } else if (column == 1) { + plotJobData->files = field.toDouble(); + } else if (column == 2) { + plotJobData->bytes = field.toDouble(); + } + column++; + m_pjd.prepend(plotJobData); + } + row++; + } + } + if ((controls->volumeComboBox->itemText(volumeIndex) != "Any") && (results.count() == 0)){ + /* for context sensitive searches, let the user know if there were no + * * results */ + QMessageBox::warning(this, tr("Bat"), + tr("The Jobs query returned no results.\n" + "Press OK to continue?"), QMessageBox::Ok ); + } +} + +/* + * The user interface that used to be in the ui header. I wanted to have a + * scroll area which is not in designer. + */ +void JobPlot::setupUserInterface() +{ + QSizePolicy sizePolicy(static_cast(1), static_cast(5)); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setVerticalPolicy(QSizePolicy::Ignored); + sizePolicy.setHorizontalPolicy(QSizePolicy::Ignored); + m_gridLayout = new QGridLayout(this); + m_gridLayout->setSpacing(6); + m_gridLayout->setMargin(9); + m_gridLayout->setObjectName(QString::fromUtf8("m_gridLayout")); + m_splitter = new QSplitter(this); + m_splitter->setObjectName(QString::fromUtf8("m_splitter")); + m_splitter->setOrientation(Qt::Horizontal); + m_jobPlot = new QwtPlot(m_splitter); + m_jobPlot->setObjectName(QString::fromUtf8("m_jobPlot")); + m_jobPlot->setSizePolicy(sizePolicy); + m_jobPlot->setMinimumSize(QSize(0, 0)); + QScrollArea *area = new QScrollArea(m_splitter); + area->setObjectName(QString::fromUtf8("area")); + controls = new JobPlotControls(); + area->setWidget(controls); + + m_splitter->addWidget(m_jobPlot); + m_splitter->addWidget(area); + + m_gridLayout->addWidget(m_splitter, 0, 0, 1, 1); +} + +/* + * Add the curves to the plot + */ +void JobPlot::addCurve() +{ + m_jobPlot->setTitle("Files and Bytes backed up"); + m_jobPlot->insertLegend(new QwtLegend(), QwtPlot::RightLegend); + + // Set axis titles + m_jobPlot->enableAxis(QwtPlot::yRight); + m_jobPlot->setAxisTitle(QwtPlot::yRight, "<-- Bytes Kb"); + m_jobPlot->setAxisTitle(m_jobPlot->xBottom, "date of backup -->"); + m_jobPlot->setAxisTitle(m_jobPlot->yLeft, "Number of Files -->"); + m_jobPlot->setAxisScaleDraw(QwtPlot::xBottom, new DateTimeScaleDraw()); + + // Insert new curves + m_fileCurve = new QwtPlotCurve("Files"); + m_fileCurve->setPen(QPen(Qt::red)); + m_fileCurve->setCurveType(m_fileCurve->Yfx); + m_fileCurve->setYAxis(QwtPlot::yLeft); + + m_byteCurve = new QwtPlotCurve("Bytes"); + m_byteCurve->setPen(QPen(Qt::blue)); + m_byteCurve->setCurveType(m_byteCurve->Yfx); + m_byteCurve->setYAxis(QwtPlot::yRight); + setPlotType(controls->plotTypeCombo->currentText()); + setFileSymbolType(controls->fileSymbolTypeCombo->currentIndex()); + setByteSymbolType(controls->byteSymbolTypeCombo->currentIndex()); + + m_fileCurve->attach(m_jobPlot); + m_byteCurve->attach(m_jobPlot); + + // attach data + int size = m_pjd.count(); + double tval[size]; + double fval[size]; + double bval[size]; + int j = 0; + foreach (PlotJobData* plotJobData, m_pjd) { +// printf("%.0f %.0f %s\n", plotJobData->bytes, plotJobData->files, +// plotJobData->dt.toString(mainWin->m_dtformat).toUtf8().data()); + fval[j] = plotJobData->files; + bval[j] = plotJobData->bytes / 1024; + tval[j] = plotJobData->dt.toTime_t(); +// printf("%i %.0f %.0f %.0f\n", j, tval[j], fval[j], bval[j]); + j++; + } + m_fileCurve->setData(tval,fval,size); + m_byteCurve->setData(tval,bval,size); + + for (int year=2000; year<2010; year++) { + for (int month=1; month<=12; month++) { + QString monthBegin; + if (month > 9) { + QTextStream(&monthBegin) << year << "-" << month << "-01 00:00:00"; + } else { + QTextStream(&monthBegin) << year << "-0" << month << "-01 00:00:00"; + } + QDateTime mdt = QDateTime::fromString(monthBegin, mainWin->m_dtformat); + double monbeg = mdt.toTime_t(); + + // ...a vertical line at the first of each month + QwtPlotMarker *mX = new QwtPlotMarker(); + mX->setLabel(mdt.toString("MMM-d")); + mX->setLabelAlignment(Qt::AlignRight|Qt::AlignTop); + mX->setLineStyle(QwtPlotMarker::VLine); + QPen pen(Qt::darkGray); + pen.setStyle(Qt::DashDotDotLine); + mX->setLinePen(pen); + mX->setXValue(monbeg); + mX->attach(m_jobPlot); + } + } +} + +/* + * slot to respond to the plot type combo changing + */ +void JobPlot::setPlotType(QString currentText) +{ + QwtPlotCurve::CurveStyle style; + if (currentText == "Fitted") { + style = QwtPlotCurve::Lines; + m_fileCurve->setCurveAttribute(QwtPlotCurve::Fitted); + m_byteCurve->setCurveAttribute(QwtPlotCurve::Fitted); + } else if (currentText == "Sticks") { + style = QwtPlotCurve::Sticks; + } else if (currentText == "Lines") { + style = QwtPlotCurve::Lines; + m_fileCurve->setCurveAttribute(QwtPlotCurve::Fitted); + m_byteCurve->setCurveAttribute(QwtPlotCurve::Fitted); + } else if (currentText == "Steps") { + style = QwtPlotCurve::Steps; + } else if (currentText == "None") { + style = QwtPlotCurve::NoCurve; + } + m_fileCurve->setStyle(style); + m_byteCurve->setStyle(style); + m_jobPlot->replot(); +} + +/* + * slot to respond to the symbol type combo changing + */ +void JobPlot::setFileSymbolType(int index) +{ + setSymbolType(index, 0); +} + +void JobPlot::setByteSymbolType(int index) +{ + setSymbolType(index, 1); +} +void JobPlot::setSymbolType(int index, int type) +{ + QwtSymbol sym; + sym.setPen(QColor(Qt::black)); + sym.setSize(7); + if (index == 0) { + sym.setStyle(QwtSymbol::Ellipse); + } else if (index == 1) { + sym.setStyle(QwtSymbol::Rect); + } else if (index == 2) { + sym.setStyle(QwtSymbol::Diamond); + } else if (index == 3) { + sym.setStyle(QwtSymbol::Triangle); + } else if (index == 4) { + sym.setStyle(QwtSymbol::DTriangle); + } else if (index == 5) { + sym.setStyle(QwtSymbol::UTriangle); + } else if (index == 6) { + sym.setStyle(QwtSymbol::LTriangle); + } else if (index == 7) { + sym.setStyle(QwtSymbol::RTriangle); + } else if (index == 8) { + sym.setStyle(QwtSymbol::Cross); + } else if (index == 9) { + sym.setStyle(QwtSymbol::XCross); + } else if (index == 10) { + sym.setStyle(QwtSymbol::HLine); + } else if (index == 11) { + sym.setStyle(QwtSymbol::VLine); + } else if (index == 12) { + sym.setStyle(QwtSymbol::Star1); + } else if (index == 13) { + sym.setStyle(QwtSymbol::Star2); + } else if (index == 14) { + sym.setStyle(QwtSymbol::Hexagon); + } + if (type == 0) { + sym.setBrush(QColor(Qt::yellow)); + m_fileCurve->setSymbol(sym); + } + if (type == 1) { + sym.setBrush(QColor(Qt::blue)); + m_byteCurve->setSymbol(sym); + } + m_jobPlot->replot(); +} + +/* + * slot to respond to the file check box changing state + */ +void JobPlot::fileCheckChanged(int newstate) +{ + if (newstate == Qt::Unchecked) { + m_fileCurve->detach(); + m_jobPlot->enableAxis(QwtPlot::yLeft, false); + } else { + m_fileCurve->attach(m_jobPlot); + m_jobPlot->enableAxis(QwtPlot::yLeft); + } + m_jobPlot->replot(); +} + +/* + * slot to respond to the byte check box changing state + */ +void JobPlot::byteCheckChanged(int newstate) +{ + if (newstate == Qt::Unchecked) { + m_byteCurve->detach(); + m_jobPlot->enableAxis(QwtPlot::yRight, false); + } else { + m_byteCurve->attach(m_jobPlot); + m_jobPlot->enableAxis(QwtPlot::yRight); + } + m_jobPlot->replot(); +} + +/* + * Save user settings associated with this page + */ +void JobPlot::writeSettings() +{ + QSettings settings(m_console->m_dir->name(), "bat"); + settings.beginGroup("JobPlot"); + settings.setValue("m_splitterSizes", m_splitter->saveState()); + settings.setValue("fileSymbolTypeCombo", controls->fileSymbolTypeCombo->currentText()); + settings.setValue("byteSymbolTypeCombo", controls->byteSymbolTypeCombo->currentText()); + settings.setValue("plotTypeCombo", controls->plotTypeCombo->currentText()); + settings.endGroup(); +} + +/* + * Read settings values for Controls + */ +void JobPlot::readControlSettings() +{ + QSettings settings(m_console->m_dir->name(), "bat"); + settings.beginGroup("JobPlot"); + int fileSymbolTypeIndex = controls->fileSymbolTypeCombo->findText(settings.value("fileSymbolTypeCombo").toString(), Qt::MatchExactly); + if (fileSymbolTypeIndex == -1) fileSymbolTypeIndex = 2; + controls->fileSymbolTypeCombo->setCurrentIndex(fileSymbolTypeIndex); + int byteSymbolTypeIndex = controls->byteSymbolTypeCombo->findText(settings.value("byteSymbolTypeCombo").toString(), Qt::MatchExactly); + if (byteSymbolTypeIndex == -1) byteSymbolTypeIndex = 3; + controls->byteSymbolTypeCombo->setCurrentIndex(byteSymbolTypeIndex); + int plotTypeIndex = controls->plotTypeCombo->findText(settings.value("plotTypeCombo").toString(), Qt::MatchExactly); + if (plotTypeIndex == -1) plotTypeIndex = 2; + controls->plotTypeCombo->setCurrentIndex(plotTypeIndex); + settings.endGroup(); +} + +/* + * Read and restore user settings associated with this page + */ +void JobPlot::readSplitterSettings() +{ + QSettings settings(m_console->m_dir->name(), "bat"); + settings.beginGroup("JobPlot"); + m_splitter->restoreState(settings.value("m_splitterSizes").toByteArray()); + settings.endGroup(); +} diff --git a/bacula/src/qt-console/jobgraphs/jobplot.h b/bacula/src/qt-console/jobgraphs/jobplot.h new file mode 100644 index 0000000000..6e27deba00 --- /dev/null +++ b/bacula/src/qt-console/jobgraphs/jobplot.h @@ -0,0 +1,151 @@ +#ifndef _JOBPLOT_H_ +#define _JOBPLOT_H_ +/* + Bacula® - The Network Backup Solution + + Copyright (C) 2000-2007 Free Software Foundation Europe e.V. + + The main author of Bacula is Kern Sibbald, with contributions from + many others, a complete list can be found in the file AUTHORS. + This program is Free Software; you can redistribute it and/or + modify it under the terms of version two of the GNU General Public + License as published by the Free Software Foundation and included + in the file LICENSE. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. + + Bacula® is a registered trademark of John Walker. + The licensor of Bacula is the Free Software Foundation Europe + (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich, + Switzerland, email:ftf@fsfeurope.org. +*/ +/* + * Version $Id: jobstep.h 4992 2007-06-07 14:46:43Z kerns $ + * + * Dirk Bartley, March 2007 + */ + +#include +#include "pages.h" +#include "ui_jobplotcontrols.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Structure to hold data items of jobs when and how much. + * If I worked at it I could eliminate this. It's just the way it evolved. + */ +struct PlotJobData +{ + double files; + double bytes; + QDateTime dt; +}; + +/* + * Class for the purpose of having a single object to pass data to the JobPlot + * Constructor. The other option was a constructor with this many passed + * values or some sort of code to parse a list. I liked this best at the time. + */ +class JobPlotPass +{ +public: + JobPlotPass(); + JobPlotPass& operator=(const JobPlotPass&); + bool use; + Qt::CheckState recordLimitCheck; + Qt::CheckState daysLimitCheck; + int recordLimitSpin; + int daysLimitSpin; + QString jobCombo; + QString clientCombo; + QString volumeCombo; + QString fileSetCombo; + QString purgedCombo; + QString levelCombo; + QString statusCombo; +}; + +/* + *Class to Change the display of the time scale to display dates. + */ +class DateTimeScaleDraw : public QwtScaleDraw +{ +public: + virtual QwtText label(double v) const + { + QDateTime dtlabel(QDateTime::fromTime_t((uint)v)); + return dtlabel.toString("M-d-yy"); + } +}; + +/* + * These are the user interface control widgets as a separate class. + * Separately for the purpos of having the controls in a Scroll Area. + */ +class JobPlotControls : public QWidget, public Ui::JobPlotControlsForm +{ + Q_OBJECT + +public: + JobPlotControls(); +}; + +/* + * The main class + */ +class JobPlot : public Pages +{ + Q_OBJECT + +public: + JobPlot(QTreeWidgetItem *parentTreeWidgetItem, JobPlotPass &); + ~JobPlot(); + virtual void currentStackItem(); + +private slots: + void setPlotType(QString); + void setFileSymbolType(int); + void setByteSymbolType(int); + void fileCheckChanged(int); + void byteCheckChanged(int); + void reGraph(); + +private: + void setSymbolType(int, int type); + void addCurve(); + void writeSettings(); + void readSplitterSettings(); + void readControlSettings(); + void setupControls(); + void runQuery(); + bool m_drawn; + JobPlotPass m_pass; + JobPlotControls* controls; + QList m_pjd; + QwtPlotCurve *m_fileCurve; + QwtPlotCurve *m_byteCurve; + /* from the user interface before using scroll area */ + void setupUserInterface(); + QGridLayout *m_gridLayout; + QSplitter *m_splitter; + QwtPlot *m_jobPlot; +}; + +#endif /* _JOBPLOT_H_ */ diff --git a/bacula/src/qt-console/jobgraphs/jobplotcontrols.ui b/bacula/src/qt-console/jobgraphs/jobplotcontrols.ui new file mode 100644 index 0000000000..39492d9f80 --- /dev/null +++ b/bacula/src/qt-console/jobgraphs/jobplotcontrols.ui @@ -0,0 +1,329 @@ + + JobPlotControlsForm + + + + 0 + 0 + 254 + 422 + + + + + 5 + 0 + 0 + 0 + + + + + 0 + 0 + + + + Form + + + + 9 + + + 6 + + + + + Qt::Vertical + + + + 236 + 16 + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 21 + 20 + + + + + + + + 0 + + + 6 + + + + + File Data + + + + + + + By te Data + + + + + + + + + Qt::Horizontal + + + + 21 + 20 + + + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 65 + 20 + + + + Refresh + + + :/images/view-refresh.svg + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5 + 0 + 0 + 0 + + + + 7 + + + + + + + + 5 + 0 + 0 + 0 + + + + 10000 + + + 1 + + + 25 + + + + + + + + + + + + + Status + + + + + + + Level + + + + + + + Purged + + + + + + + + 16777215 + 20 + + + + FileSet + + + + + + + Volume + + + + + + + Client + + + + + + + Job + + + + + + + Days Limit + + + + + + + + 0 + 0 + 0 + 0 + + + + Record Limit + + + + + + + Byte Symbol Type + + + + + + + File Symbol Type + + + + + + + Graph Type + + + + + + + + diff --git a/bacula/src/qt-console/joblist/joblist.cpp b/bacula/src/qt-console/joblist/joblist.cpp index d472797789..a2d2747d0e 100644 --- a/bacula/src/qt-console/joblist/joblist.cpp +++ b/bacula/src/qt-console/joblist/joblist.cpp @@ -37,6 +37,7 @@ #include "joblist.h" #include "restore.h" #include "joblog/joblog.h" +#include "jobgraphs/jobplot.h" /* * Constructor for the class @@ -88,26 +89,14 @@ void JobList::populateTable() /* Can't do this in constructor because not neccesarily conected in constructor */ if (!m_populated) { - clientsComboBox->addItem("Any"); - clientsComboBox->addItems(m_console->client_list); - int clientIndex = clientsComboBox->findText(m_clientName, Qt::MatchExactly); + clientComboBox->addItem("Any"); + clientComboBox->addItems(m_console->client_list); + int clientIndex = clientComboBox->findText(m_clientName, Qt::MatchExactly); if (clientIndex != -1) - clientsComboBox->setCurrentIndex(clientIndex); + clientComboBox->setCurrentIndex(clientIndex); - QString query("SELECT VolumeName AS Media FROM Media ORDER BY Media"); - if (mainWin->m_sqlDebug) { - Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data()); - } - QStringList results, volumeList; - if (m_console->sql_cmd(query, results)) { - QString field; - QStringList fieldlist; - /* Iterate through the lines of results. */ - foreach (QString resultline, results) { - fieldlist = resultline.split("\t"); - volumeList.append(fieldlist[0]); - } /* foreach resultline */ - } /* if results from query */ + QStringList volumeList; + m_console->getVolumeList(volumeList); volumeComboBox->addItem("Any"); volumeComboBox->addItems(volumeList); int volumeIndex = volumeComboBox->findText(m_mediaName, Qt::MatchExactly); @@ -124,27 +113,15 @@ void JobList::populateTable() levelComboBox->addItems( QStringList() << "F" << "D" << "I"); purgedComboBox->addItem("Any"); purgedComboBox->addItems( QStringList() << "0" << "1"); - statusComboBox->addItem("Any"); fileSetComboBox->addItem("Any"); fileSetComboBox->addItems(m_console->fileset_list); int filesetIndex = fileSetComboBox->findText(m_filesetName, Qt::MatchExactly); if (filesetIndex != -1) { fileSetComboBox->setCurrentIndex(filesetIndex); } - QString statusQuery("SELECT JobStatusLong FROM Status"); - if (mainWin->m_sqlDebug) { - Pmsg1(000, "Query cmd : %s\n",query.toUtf8().data()); - } - QStringList statusResults, statusLongList; - if (m_console->sql_cmd(statusQuery, statusResults)) { - QString field; - QStringList fieldlist; - /* Iterate through the lines of results. */ - foreach (QString resultline, statusResults) { - fieldlist = resultline.split("\t"); - statusLongList.append(fieldlist[0]); - } /* foreach resultline */ - } /* if results from statusquery */ + QStringList statusLongList; + m_console->getStatusList(statusLongList); + statusComboBox->addItem("Any"); statusComboBox->addItems(statusLongList); } @@ -169,9 +146,9 @@ void JobList::populateTable() if (m_mediaName != "Any") { conditions.append("Media.VolumeName='" + m_mediaName + "'"); } - int clientIndex = clientsComboBox->currentIndex(); + int clientIndex = clientComboBox->currentIndex(); if (clientIndex != -1) - m_clientName = clientsComboBox->itemText(clientIndex); + m_clientName = clientComboBox->itemText(clientIndex); if (m_clientName != "Any") { conditions.append("Client.Name='" + m_clientName + "'"); } @@ -230,6 +207,9 @@ void JobList::populateTable() m_purgedIndex = headerlist.indexOf("Purged"); m_typeIndex = headerlist.indexOf("Job Type"); m_statusIndex = headerlist.indexOf("Job Status"); + m_startIndex = headerlist.indexOf("Job Starttime"); + m_filesIndex = headerlist.indexOf("Job Files"); + m_bytesIndex = headerlist.indexOf("Job Bytes"); /* Initialize the QTableWidget */ m_checkCurrentWidget = false; @@ -398,6 +378,7 @@ void JobList::createConnections() connect(actionRefreshJobList, SIGNAL(triggered()), this, SLOT(populateTable())); connect(refreshButton, SIGNAL(pressed()), this, SLOT(populateTable())); + connect(graphButton, SIGNAL(pressed()), this, SLOT(graphTable())); /* for the tableItemChanged to maintain m_currentJob */ connect(mp_tableWidget, SIGNAL( currentItemChanged(QTableWidgetItem *, QTableWidgetItem *)), @@ -559,3 +540,25 @@ void JobList::consoleCancelJob() cmd += m_currentJob; consoleCommand(cmd); } + +/* + * Graph this table + */ +void JobList::graphTable() +{ + JobPlotPass pass; + pass.recordLimitCheck = limitCheckBox->checkState(); + pass.daysLimitCheck = daysCheckBox->checkState(); + pass.recordLimitSpin = limitSpinBox->value(); + pass.daysLimitSpin = daysSpinBox->value(); + pass.jobCombo = jobComboBox->currentText(); + pass.clientCombo = clientComboBox->currentText(); + pass.volumeCombo = volumeComboBox->currentText(); + pass.fileSetCombo = fileSetComboBox->currentText(); + pass.purgedCombo = purgedComboBox->currentText(); + pass.levelCombo = levelComboBox->currentText(); + pass.statusCombo = statusComboBox->currentText(); + pass.use = true; + QTreeWidgetItem* pageSelectorTreeWidgetItem = mainWin->getFromHash(this); + new JobPlot(pageSelectorTreeWidgetItem, pass); +} diff --git a/bacula/src/qt-console/joblist/joblist.h b/bacula/src/qt-console/joblist/joblist.h index d3b8e0468c..994e2dea59 100644 --- a/bacula/src/qt-console/joblist/joblist.h +++ b/bacula/src/qt-console/joblist/joblist.h @@ -66,6 +66,7 @@ private slots: void preRestoreFromTime(); void showLogForJob(); void consoleCancelJob(); + void graphTable(); private: void createConnections(); @@ -80,6 +81,9 @@ private: int m_purgedIndex; int m_typeIndex; int m_statusIndex; + int m_startIndex; + int m_bytesIndex; + int m_filesIndex; }; #endif /* _JOBLIST_H_ */ diff --git a/bacula/src/qt-console/joblist/joblist.ui b/bacula/src/qt-console/joblist/joblist.ui index 1b861a0ed4..00a0ab9b94 100644 --- a/bacula/src/qt-console/joblist/joblist.ui +++ b/bacula/src/qt-console/joblist/joblist.ui @@ -5,8 +5,8 @@ 0 0 - 607 - 390 + 545 + 276 @@ -27,7 +27,75 @@ 6 - + + + + 0 + + + 6 + + + + + 0 + + + 3 + + + + + + 65 + 20 + + + + Refresh + + + :/images/view-refresh.svg + + + + + + + + 65 + 20 + + + + Graph + + + :/images/graph1.png + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Ignored + + + + 16 + 20 + + + + + + + 0 @@ -44,14 +112,14 @@ 6 - + - Clients + Status - + @@ -64,14 +132,14 @@ 6 - + - Volume + Purged - + @@ -151,6 +219,12 @@ + + + 16777215 + 20 + + FileSet @@ -161,7 +235,7 @@ - + 0 @@ -178,14 +252,14 @@ 6 - + - Status + Clients - + @@ -198,14 +272,14 @@ 6 - + - Purged + Volume - + @@ -261,64 +335,6 @@ - - - - 0 - - - 6 - - - - - - 65 - 16777215 - - - - Refresh - - - :/images/view-refresh.svg - - - - - - - Qt::Horizontal - - - QSizePolicy::Ignored - - - - 16 - 20 - - - - - - - - Qt::Vertical - - - QSizePolicy::Ignored - - - - 20 - 16 - - - - - - diff --git a/bacula/src/qt-console/jobs/jobs.cpp b/bacula/src/qt-console/jobs/jobs.cpp index 7f13564ccd..58543a9ace 100644 --- a/bacula/src/qt-console/jobs/jobs.cpp +++ b/bacula/src/qt-console/jobs/jobs.cpp @@ -35,10 +35,9 @@ * */ -//#include -//#include #include "bat.h" #include "jobs/jobs.h" +#include "run/run.h" Jobs::Jobs() { @@ -80,6 +79,7 @@ void Jobs::populateTree() << "Client" << "Storage" << "Where" << "Level" << "Type" << "FileSet" << "Catalog" << "Enabled"); + m_typeIndex = headerlist.indexOf("Type"); topItem = new QTreeWidgetItem(mp_treeWidget); topItem->setText(0, "Jobs"); topItem->setData(0, Qt::UserRole, 0); @@ -144,15 +144,8 @@ void Jobs::treeItemChanged(QTreeWidgetItem *currentwidgetitem, QTreeWidgetItem * if (m_checkcurwidget) { /* The Previous item */ if (previouswidgetitem) { /* avoid a segfault if first time */ - int treedepth = previouswidgetitem->data(0, Qt::UserRole).toInt(); - if (treedepth == 1){ - mp_treeWidget->removeAction(actionConsoleListFiles); - mp_treeWidget->removeAction(actionConsoleListVolumes); - mp_treeWidget->removeAction(actionConsoleListNextVolume); - mp_treeWidget->removeAction(actionConsoleEnableJob); - mp_treeWidget->removeAction(actionConsoleDisableJob); - mp_treeWidget->removeAction(actionConsoleCancel); - mp_treeWidget->removeAction(actionJobListQuery); + foreach(QAction* jobAction, mp_treeWidget->actions()) { + mp_treeWidget->removeAction(jobAction); } } @@ -168,6 +161,8 @@ void Jobs::treeItemChanged(QTreeWidgetItem *currentwidgetitem, QTreeWidgetItem * mp_treeWidget->addAction(actionConsoleDisableJob); mp_treeWidget->addAction(actionConsoleCancel); mp_treeWidget->addAction(actionJobListQuery); + if (currentwidgetitem->text(m_typeIndex) == "Backup") + mp_treeWidget->addAction(actionRunJob); } } } @@ -194,6 +189,7 @@ void Jobs::createContextMenu() connect(actionConsoleDisableJob, SIGNAL(triggered()), this, SLOT(consoleDisable())); connect(actionConsoleCancel, SIGNAL(triggered()), this, SLOT(consoleCancel())); connect(actionJobListQuery, SIGNAL(triggered()), this, SLOT(listJobs())); + connect(actionRunJob, SIGNAL(triggered()), this, SLOT(runJob())); } /* @@ -260,3 +256,12 @@ void Jobs::listJobs() QTreeWidgetItem *parentItem = mainWin->getFromHash(this); mainWin->createPageJobList("", "", m_currentlyselected, "", parentItem); } + +/* + * Open a new job run page with the currentley selected "Backup" job + * defaulted In + */ +void Jobs::runJob() +{ + new runPage(m_currentlyselected); +} diff --git a/bacula/src/qt-console/jobs/jobs.h b/bacula/src/qt-console/jobs/jobs.h index bbcc97240c..8c32859e8f 100644 --- a/bacula/src/qt-console/jobs/jobs.h +++ b/bacula/src/qt-console/jobs/jobs.h @@ -60,12 +60,14 @@ private slots: void consoleDisable(); void consoleCancel(); void listJobs(); + void runJob(); private: void createContextMenu(); QString m_currentlyselected; bool m_populated; bool m_checkcurwidget; + int m_typeIndex; }; #endif /* _JOBS_H_ */ diff --git a/bacula/src/qt-console/jobs/jobs.ui b/bacula/src/qt-console/jobs/jobs.ui index 8b0af20e68..21d25d83d0 100644 --- a/bacula/src/qt-console/jobs/jobs.ui +++ b/bacula/src/qt-console/jobs/jobs.ui @@ -147,6 +147,14 @@ Cancel Job Command + + + :/images/run.png + + + RunJob + + diff --git a/bacula/src/qt-console/main.qrc b/bacula/src/qt-console/main.qrc index fee1c35f52..500943e3f8 100644 --- a/bacula/src/qt-console/main.qrc +++ b/bacula/src/qt-console/main.qrc @@ -18,6 +18,7 @@ images/estimate-job.svg images/folder.png images/folder.svg + images/graph1.png images/help-browser.svg images/home.png images/joblog.png diff --git a/bacula/src/qt-console/main.ui b/bacula/src/qt-console/main.ui index 42775e7703..83b05f8348 100644 --- a/bacula/src/qt-console/main.ui +++ b/bacula/src/qt-console/main.ui @@ -139,9 +139,10 @@ - + + @@ -554,6 +555,14 @@ Browse + + + :/images/graph1.png + + + JobPlot + + diff --git a/bacula/src/qt-console/mainwin.cpp b/bacula/src/qt-console/mainwin.cpp index 0dcf4f6015..81deb3f3dc 100644 --- a/bacula/src/qt-console/mainwin.cpp +++ b/bacula/src/qt-console/mainwin.cpp @@ -49,6 +49,7 @@ #include "restore/restoretree.h" #include "help/help.h" #include "jobs/jobs.h" +#include "jobgraphs/jobplot.h" /* * Daemon message callback @@ -60,6 +61,7 @@ void message_callback(int /* type */, char *msg) MainWin::MainWin(QWidget *parent) : QMainWindow(parent) { + m_isClosing = false; m_dtformat = "yyyy-MM-dd HH:mm:ss"; mainWin = this; setupUi(this); /* Setup UI defined by main.ui (designer) */ @@ -144,6 +146,9 @@ void MainWin::createPages() new MediaList(); new Storage(); new restoreTree(); + JobPlotPass pass; + pass.use = false; + new JobPlot(NULL, pass); treeWidget->expandItem(topItem); stackedWidget->setCurrentWidget(m_currentConsole); @@ -227,6 +232,7 @@ void MainWin::createConnections() connect(actionRun, SIGNAL(triggered()), this, SLOT(runButtonClicked())); connect(actionEstimate, SIGNAL(triggered()), this, SLOT(estimateButtonClicked())); connect(actionBrowse, SIGNAL(triggered()), this, SLOT(browseButtonClicked())); + connect(actionJobPlot, SIGNAL(triggered()), this, SLOT(jobPlotButtonClicked())); connect(actionRestore, SIGNAL(triggered()), this, SLOT(restoreButtonClicked())); connect(actionUndock, SIGNAL(triggered()), this, SLOT(undockWindowButton())); connect(actionToggleDock, SIGNAL(triggered()), this, SLOT(toggleDockContextWindow())); @@ -239,16 +245,22 @@ void MainWin::createConnections() */ void MainWin::closeEvent(QCloseEvent *event) { + m_isClosing = true; writeSettings(); + /* close all non console pages, this will call settings in destructors */ + foreach(Pages *page, m_pagehash) { + if (page != page->console()) { + page->console()->setCurrent(); + page->closeStackPage(); + } + } + /* close the console pages and terminate connection */ foreach(Console *console, m_consoleHash){ console->writeSettings(); console->terminate(); + console->closeStackPage(); } event->accept(); - foreach(Pages *page, m_pagehash) { - if (!page->isDocked()) - page->close(); - } } void MainWin::writeSettings() @@ -297,6 +309,8 @@ void MainWin::treeItemClicked(QTreeWidgetItem *item, int /*column*/) */ void MainWin::treeItemChanged(QTreeWidgetItem *currentitem, QTreeWidgetItem *previousitem) { + if (m_isClosing) return; /* if closing the application, do nothing here */ + Pages *previousPage, *nextPage; Console *previousConsole, *nextConsole; @@ -396,7 +410,7 @@ void MainWin::labelButtonClicked() void MainWin::runButtonClicked() { - new runPage(); + new runPage(""); } void MainWin::estimateButtonClicked() @@ -414,6 +428,13 @@ void MainWin::restoreButtonClicked() new prerestorePage(); } +void MainWin::jobPlotButtonClicked() +{ + JobPlotPass pass; + pass.use = false; + new JobPlot(NULL, pass); +} + /* * The user just finished typing a line in the command line edit box */ @@ -501,6 +522,7 @@ void MainWin::toggleDockContextWindow() */ void MainWin::stackItemChanged(int) { + if (m_isClosing) return; /* if closing the application, do nothing here */ Pages* page = (Pages*)stackedWidget->currentWidget(); /* run the virtual function in case this class overrides it */ page->currentStackItem(); diff --git a/bacula/src/qt-console/mainwin.h b/bacula/src/qt-console/mainwin.h index c8682237ab..ddeb55497d 100644 --- a/bacula/src/qt-console/mainwin.h +++ b/bacula/src/qt-console/mainwin.h @@ -94,6 +94,7 @@ public slots: void runButtonClicked(); void estimateButtonClicked(); void browseButtonClicked(); + void jobPlotButtonClicked(); void restoreButtonClicked(); void undockWindowButton(); void treeItemChanged(QTreeWidgetItem *, QTreeWidgetItem *); @@ -118,6 +119,7 @@ private: QStringList m_cmd_history; int m_cmd_last; QTreeWidgetItem *m_firstItem; + bool m_isClosing; }; #include "ui_prefs.h" diff --git a/bacula/src/qt-console/qwtconfig.pri b/bacula/src/qt-console/qwtconfig.pri new file mode 100644 index 0000000000..7422712e75 --- /dev/null +++ b/bacula/src/qt-console/qwtconfig.pri @@ -0,0 +1,88 @@ + +# +# Inserted by build script +# +# unix { +# INSTALLBASE = /usr +#} +# +#win32 { +# INSTALLBASE = C:/Qwt-5.0.2 +#} + +target.path = $$INSTALLBASE/lib +headers.path = $$INSTALLBASE/include +# doc.path = $$INSTALLBASE/doc + +###################################################################### +# qmake internal options +###################################################################### + +CONFIG += qt # Also for Qtopia Core! +CONFIG += warn_on +CONFIG += thread + +###################################################################### +# release/debug mode +# The designer plugin is always built in release mode. +# If want to change this, you have to edit designer/designer.pro. +###################################################################### + +CONFIG += release # release/debug + +###################################################################### +# Build the static/shared libraries. +# If QwtDll is enabled, a shared library is built, otherwise +# it will be a static library. +###################################################################### + +# CONFIG += QwtDll + +###################################################################### +# QwtPlot enables all classes, that are needed to use the QwtPlot +# widget. +###################################################################### + +CONFIG += QwtPlot + +###################################################################### +# QwtWidgets enables all classes, that are needed to use the all other +# widgets (sliders, dials, ...), beside QwtPlot. +###################################################################### + +# CONFIG += QwtWidgets + +###################################################################### +# If you want to display svg imageson the plot canvas, enable the +# line below. Note that Qwt needs the svg+xml, when enabling +# QwtSVGItem. +###################################################################### + +#CONFIG += QwtSVGItem + +###################################################################### +# If you have a commercial license you can use the MathML renderer +# of the Qt solutions package to enable MathML support in Qwt. +# So if you want this, copy qtmmlwidget.h + qtmmlwidget.cpp to +# textengines/mathml and enable the line below. +###################################################################### + +#CONFIG += QwtMathML + +###################################################################### +# If you want to build the Qwt designer plugin, +# enable the line below. +# Otherwise you have to build it from the designer directory. +###################################################################### + +# CONFIG += QwtDesigner + +###################################################################### +# If you want to auto build the examples, enable the line below +# Otherwise you have to build them from the examples directory. +###################################################################### + +# CONFIG += QwtExamples +unix { + INSTALLBASE = /home/kern/bacula/x/src/qt-console/qwt +} diff --git a/bacula/src/qt-console/run/run.cpp b/bacula/src/qt-console/run/run.cpp index 0ea27f91dc..6365b3494d 100644 --- a/bacula/src/qt-console/run/run.cpp +++ b/bacula/src/qt-console/run/run.cpp @@ -40,7 +40,7 @@ /* * Setup all the combo boxes and display the dialog */ -runPage::runPage() +runPage::runPage(const QString &defJob) { QDateTime dt; @@ -74,6 +74,8 @@ runPage::runPage() dockPage(); setCurrent(); this->show(); + if (defJob != "") + jobCombo->setCurrentIndex(jobCombo->findText(defJob, Qt::MatchExactly)); } void runPage::okButtonPushed() diff --git a/bacula/src/qt-console/run/run.h b/bacula/src/qt-console/run/run.h index 91273cca25..ac949c0400 100644 --- a/bacula/src/qt-console/run/run.h +++ b/bacula/src/qt-console/run/run.h @@ -14,7 +14,7 @@ class runPage : public Pages, public Ui::runForm Q_OBJECT public: - runPage(); + runPage(const QString &defJob); public slots: void okButtonPushed();