--- /dev/null
+<?php
+/*
+ * Bacula(R) - The Network Backup Solution
+ * Baculum - Bacula web interface
+ *
+ * Copyright (C) 2013-2020 Kern Sibbald
+ *
+ * The main author of Baculum is Marcin Haba.
+ * The original author of Bacula is Kern Sibbald, with contributions
+ * from many others, a complete list can be found in the file AUTHORS.
+ *
+ * You may use this file and others of this release according to the
+ * license defined in the LICENSE file, which includes the Affero General
+ * Public License, v3.0 ("AGPLv3") and some additional permissions and
+ * terms pursuant to its AGPLv3 Section 7.
+ *
+ * This notice must be preserved when any source code is
+ * conveyed and/or propagated.
+ *
+ * Bacula(R) is a registered trademark of Kern Sibbald.
+ */
+
+Prado::using('Application.:Web.Class.WebModule');
+
+/**
+ * Module responsible for providing information about job.
+ *
+ * @author Marcin Haba <marcin.haba@bacula.pl>
+ * @category Module
+ * @package Baculum Web
+ */
+class JobInfo extends WebModule {
+
+ const RESOURCE_PATTERN = '/(?P<resource>\S+(?=:))?:?(\s+((?P<directive>\S+)=(?P<value>[\s\S]*?(?=\s\S+=.+|$))))/';
+
+ public function parseResourceDirectives(array $show_out) {
+ $result = [];
+ $resource = [];
+ $res = null;
+ for ($i = 1; $i < count($show_out); $i++) {
+ if (preg_match_all(self::RESOURCE_PATTERN, $show_out[$i], $match) > 0) {
+ if (!empty($match['resource'][0])) {
+ if (count($resource) == 1) {
+ /**
+ * Check key to not overwrite already existing resource
+ * because in some cases there can be for example two
+ * Autochanger resources: one from Pool and second from NextPool.
+ */
+ if (!key_exists($res, $result)) {
+ $result = array_merge($result, $resource);
+ }
+ $resource = [];
+ }
+ $res = strtolower($match['resource'][0]);
+ }
+ if (!key_exists($res, $resource)) {
+ $resource[$res] = [];
+ }
+ for ($j = 0; $j < count($match['directive']); $j++) {
+ $directive = strtolower($match['directive'][$j]);
+ $value = $match['value'][$j];
+ $resource[$res][$directive] = $value;
+ }
+ }
+ }
+ if (count($resource) == 1) {
+ $result = array_merge($result, $resource);
+ }
+ return $result;
+ }
+}
+?>
series: null,
pie: null,
graph_options: {
- colors: ['#63c422', '#d70808', '#FFFF66', 'orange', 'blue'],
+ colors: ['#63c422', '#d70808', '#FFFF66', 'orange', '#2980B9'],
HtmlText: false,
fontColor: '#000000',
grid: {
verticalLines : false,
horizontalLines : false,
outlineWidth: 0,
+ color: 'black'
},
xaxis: { showLabels : false,},
yaxis: { showLabels : false },
explode : 6,
labelFormatter: PieGraph.pie_label_formatter,
shadowSize: 4,
- fillOpacity: 1,
sizeRatio: 0.77
},
mouse: {
margin: 0
}
},
- initialize: function(jobs, container_id) {
- this.jobs = jobs;
- this.container = document.getElementById(container_id);
+ initialize: function(prop) {
+ this.jobs = prop.jobs;
+ this.title = prop.hasOwnProperty('title') ? prop.title : null;
+ this.container = document.getElementById(prop.container_id);
this.series = this.prepare_series();
this.draw_grah();
},
jobs_count = this.jobs[label].length;
serie = {
data: [[0, jobs_count]],
- label: label + ' (' + jobs_count.toString() + ')'
+ label: label + ' (' + jobs_count.toString() + ')',
+ pie: {
+ explode: 12
+ }
}
series.push(serie);
}
return series;
},
draw_grah: function() {
- this.pie = Flotr.draw(this.container, this.series, this.graph_options);
+ var graph_options = $.extend({}, this.graph_options);
+ if (this.title) {
+ graph_options.title = this.title;
+ }
+ this.pie = Flotr.draw(this.container, this.series, graph_options);
}
});
if (this.pie != null) {
this.pie.pie.destroy();
}
- this.pie = new GraphPieClass(this.stats.jobs_summary, this.ids.pie_summary);
+ this.pie = new GraphPieClass({
+ jobs: this.stats.jobs_summary,
+ container_id: this.ids.pie_summary
+ });
}
}
msgid "deleted"
msgstr "deleted"
+
+msgid "last %days days"
+msgstr "last %days days"
msgid "deleted"
msgstr "deleted"
+
+msgid "last %days days"
+msgstr "last %days days"
msgid "deleted"
msgstr "skasowany"
+
+msgid "last %days days"
+msgstr "ostatnie %days dni"
msgid "deleted"
msgstr "deleted"
+
+msgid "last %days days"
+msgstr "last %days days"
const USE_CACHE = true;
+ const DEFAULT_JOB_PRIORITY = 10;
+
public function loadRunJobModal($sender, $param) {
$this->RunJobModal->loadData();
}
$level = trim($jobdata->level);
$params['level'] = !empty($level) ? $level : 'F'; // Admin job has empty level
$job_show = $this->getModule('api')->get(
- array('jobs', $jobid, 'show'), null, true, self::USE_CACHE
+ ['jobs', $jobid, 'show'],
+ null,
+ true,
+ self::USE_CACHE
)->output;
+ $job_info = $this->getModule('job_info')->parseResourceDirectives($job_show);
if ($jobdata->filesetid > 0) {
$params['filesetid'] = $jobdata->filesetid;
} else {
- $params['fileset'] = RunJob::getResourceName('fileset', $job_show);
+ $params['fileset'] = key_exists('fileset', $job_info) ? $job_info['fileset']['name'] : '';
}
$params['clientid'] = $jobdata->clientid;
- $params['storage'] = RunJob::getResourceName('storage', $job_show);
- if (empty($params['storage'])) {
- $params['storage'] = RunJob::getResourceName('autochanger', $job_show);
- }
- if ($jobdata->poolid > 0) {
+ $storage = key_exists('storage', $job_info) ? $job_info['storage']['name'] : null;
+ $autochanger = key_exists('autochanger', $job_info) ? $job_info['autochanger']['name'] : null;
+ $params['storage'] = $storage ?: $autochanger;
+
+ /**
+ * For 'c' type (Copy Job) and 'g' type (Migration Job) the in job table in poolid property is written
+ * write pool, not read pool. Here in 'pool' property is set read pool and from this reason for 'c'
+ * and 'g' types the pool cannot be taken from job table.
+ */
+ if ($jobdata->poolid > 0 && $jobdata->type != 'c' && $jobdata->type != 'g') {
$params['poolid'] = $jobdata->poolid;
} else {
- $params['pool'] = RunJob::getResourceName('pool', $job_show);
+ $params['pool'] = key_exists('pool', $job_info) ? $job_info['pool']['name'] : '';
}
- $job_attr = RunJob::getJobAttr($job_show);
- $params['priority'] = key_exists('priority', $job_attr) ? $job_attr['priority'] : 10;
- $params['accurate'] = (key_exists('accurate', $job_attr) && $job_attr['accurate'] == 1);
+ $params['priority'] = key_exists('job', $job_info) ? $job_info['job']['priority'] : self::DEFAULT_JOB_PRIORITY;
+ $accurate = key_exists('job', $job_info) && key_exists('accurate', $job_info['job']) ? $job_info['job']['accurate'] : 0;
+ $params['accurate'] = ($accurate == 1);
$result = $this->getModule('api')->create(array('jobs', 'run'), $params);
if ($result->error === 0) {
const JOB_LEVEL = 'JobLevel';
const JOB_TYPE = 'JobType';
const CLIENTID = 'ClientId';
+ const JOB_INFO = 'JobInfo';
const USE_CACHE = true;
const SORT_ASC = 0;
const SORT_DESC = 1;
- const RESOURCE_SHOW_PATTERN = '/^\s+--> %resource: name=(.+?(?=\s\S+\=.+)|.+$)/i';
-
public $is_running = false;
public $allow_graph_mode = false;
public $allow_list_files_mode = false;
public function onInit($param) {
parent::onInit($param);
+ $this->JobConfig->attachEventHandler('OnSave', [$this, 'reloadJobInfo']);
+ $job_name = $this->getJobName();
$this->RunJobModal->setJobId($this->getJobId());
- $this->RunJobModal->setJobName($this->getJobName());
+ $this->RunJobModal->setJobName($job_name);
$this->FileList->setJobId($this->getJobId());
+ if ($this->IsCallBack || $this->IsPostBack) {
+ return;
+ }
+ $this->setJobInfo($job_name);
}
public function onLoad($param) {
return $this->getViewState(self::JOB_LEVEL);
}
+ /**
+ * Set job information from show job output.
+ *
+ * @return none
+ */
+ public function setJobInfo($job_name) {
+ $job_show = $this->getModule('api')->get(
+ array('jobs', 'show', '?name='. rawurlencode($job_name)),
+ null,
+ true,
+ false
+ );
+ if ($job_show->error == 0) {
+ $job_info = $this->getModule('job_info')->parseResourceDirectives($job_show->output);
+ $this->setViewState(self::JOB_INFO, $job_info);
+ }
+ }
+
+ /**
+ * Get job information.
+ *
+ * @return array job information
+ */
+ public function getJobInfo() {
+ return $this->getViewState(self::JOB_INFO, []);
+ }
+
+
+ /**
+ * Reload job information.
+ *
+ * @param mixed $param save event parameter
+ * @return none
+ */
+ public function reloadJobInfo($param) {
+ $job_name = $this->getJobName();
+ $this->setJobInfo($job_name);
+ }
+
/**
* Refresh job log page and load latest logs.
*
}
}
- public function getResourceName($resource, $jobshow) {
- $resource_name = null;
- $pattern = str_replace('%resource', $resource, self::RESOURCE_SHOW_PATTERN);
- for ($i = 0; $i < count($jobshow); $i++) {
- if (preg_match($pattern, $jobshow[$i], $match) === 1) {
- $resource_name = $match[1];
- break;
- }
- }
- return $resource_name;
- }
-
public function loadFileSetConfig($sender, $param) {
if (!empty($_SESSION['dir'])) {
- $jobshow = $this->getModule('api')->get(array(
- 'jobs', $this->getJobId(), 'show'
- ))->output;
- $fileset = $this->getResourceName('fileset', $jobshow);
- $this->FileSetConfig->setComponentName($_SESSION['dir']);
- $this->FileSetConfig->setResourceName($fileset);
- $this->FileSetConfig->setLoadValues(true);
- $this->FileSetConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ $job_info = $this->getJobInfo();
+ if (key_exists('fileset', $job_info)) {
+ $this->FileSetConfig->setComponentName($_SESSION['dir']);
+ $this->FileSetConfig->setResourceName($job_info['fileset']['name']);
+ $this->FileSetConfig->setLoadValues(true);
+ $this->FileSetConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ }
}
}
public function loadScheduleConfig($sender, $param) {
if (!empty($_SESSION['dir'])) {
- $jobshow = $this->getModule('api')->get(array(
- 'jobs', $this->getJobId(), 'show'
- ))->output;
- $schedule = $this->getResourceName('schedule', $jobshow);
- $this->ScheduleConfig->setComponentName($_SESSION['dir']);
- $this->ScheduleConfig->setResourceName($schedule);
- $this->ScheduleConfig->setLoadValues(true);
- $this->ScheduleConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ $job_info = $this->getJobInfo();
+ if (key_exists('schedule', $job_info)) {
+ $this->ScheduleConfig->setComponentName($_SESSION['dir']);
+ $this->ScheduleConfig->setResourceName($job_info['schedule']['name']);
+ $this->ScheduleConfig->setLoadValues(true);
+ $this->ScheduleConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ }
}
}
</div>
<div class="w3-container tab_item" id="job_actions">
<com:TActiveLinkButton
- CssClass="w3-button w3-green w3-margin-bottom"
+ CssClass="w3-button w3-green"
OnClick="loadRunJobModal"
Attributes.onclick="document.getElementById('run_job').style.display='block'"
>
<prop:Text><%=Prado::localize('Run job')%> <i class="fa fa-undo"></i></prop:Text>
</com:TActiveLinkButton>
<com:Application.Web.Portlets.RunJob ID="RunJobModal" />
+ <div id="job_graph_container">
+ <div>
+ <div id="jobs_summary_graph"></div>
+ </div>
+ <div>
+ <div id="job_size_graph" style="height: 390px"></div>
+ </div>
+ <div>
+ <div id="job_files_graph" style="height: 390px"></div>
+ </div>
+ </div>
+ <script>
+var oJobGraphs = {
+ ids: {
+ jobs_summary_graph : 'jobs_summary_graph',
+ job_size_graph : 'job_size_graph',
+ job_files_graph : 'job_files_graph',
+ job_actions: 'job_actions'
+ },
+ graphs: {
+ job_summary: null,
+ job_size: null,
+ job_files: null
+ },
+ colors: {
+ F: '#63c422',
+ I: '#2980B9',
+ D: '#D68910',
+ O: 'red'
+ },
+ graph_options: {
+ legend: {
+ show: true,
+ noColumns: 9,
+ labelBoxHeight: 10,
+ fontColor: '#000000',
+ position : 'ne'
+ },
+ bars: {
+ show: true,
+ fill: true,
+ horizontal : false,
+ shadowSize : 0
+ },
+ xaxis: {
+ mode : 'time',
+ timeMode: 'local',
+ labelsAngle : 45,
+ autoscale: true,
+ color: 'black',
+ showLabels: true
+ },
+ yaxis: {
+ color: 'black',
+ min: 0
+ },
+ lines: {
+ show: true,
+ lineWidth: 0,
+ fill: true,
+ steps: true
+ },
+ selection: {
+ mode : 'x'
+ },
+ grid: {
+ color: '#000000',
+ outlineWidth: 0
+ },
+ HtmlText: false
+ },
+ txt: {
+ job_summary: {
+ graph_title: '<%[ Job status summary ]%>'
+ },
+ job_size: {
+ graph_title: '<%[ Job size / Time ]%> - <%[ last %days days ]%>'.replace('%days', 30),
+ xaxis_title: '<%[ Time ]%>',
+ yaxis_title: '<%[ Job size ]%>'
+ },
+ job_files: {
+ graph_title: '<%[ Job files / Time ]%> - <%[ last %days days ]%>'.replace('%days', 30),
+ xaxis_title: '<%[ Time ]%>',
+ yaxis_title: '<%[ Files count ]%>'
+ },
+ },
+ initialized: false,
+ extended_graph_jt: ['B'],
+ job_info: <%=json_encode($this->getJobInfo())%>,
+ init: function() {
+ this.set_events();
+ },
+ update: function() {
+ if (!$('#' + this.ids.job_actions).is(':visible') || oData.jobs.length == 0) {
+ // do update only if tab with graphs is opened and if there are finished jobs
+ return;
+ }
+
+ // job summary pie graph
+ this.prepare_job_summary();
+
+ if (this.display_extended_graphs()) {
+ // job size - last 30 days
+ this.prepare_job_size();
+
+ // job files - last 30 days
+ this.prepare_job_files();
+ }
+
+ if (!this.initialized) {
+ /**
+ * Initialization and events has to be done when graphs already exists.
+ * From this reason it is done at the end of update and only once.
+ */
+ this.initialized = true;
+ this.init();
+ }
+ },
+ display_extended_graphs: function() {
+ var disp_ext_graphs = false;
+ if (this.job_info.hasOwnProperty('job') && this.job_info.job.hasOwnProperty('jobtype')) {
+ job_type = String.fromCharCode(this.job_info.job.jobtype);
+ disp_ext_graphs = (this.extended_graph_jt.indexOf(job_type) !== -1);
+ }
+ return disp_ext_graphs;
+ },
+ set_events: function() {
+ if (!this.display_extended_graphs()) {
+ return;
+ }
+
+ var select_area = function(area) {
+ var opts = {
+ xaxis : {
+ min : area.x1,
+ max : area.x2,
+ mode : 'time',
+ timeMode: 'local',
+ labelsAngle : 45,
+ color: 'black',
+ autoscale: true
+ },
+ yaxis : {
+ min : area.y1,
+ max : area.y2,
+ color: 'black',
+ autoscale: true
+ }
+ };
+ return opts;
+ };
+
+ // JOB SIZE
+
+ var job_size_select_cb = function(area) {
+ var opts = select_area(area);
+ this.prepare_job_size(opts);
+ }.bind(this);
+
+ var job_size_graph_container = document.getElementById(this.ids.job_size_graph);
+
+ // set Flotr-specific select area event for job size graph
+ Flotr.EventAdapter.observe(job_size_graph_container, 'flotr:select', job_size_select_cb);
+
+ // set Flotr-specific click area event (zoom reset) for job size graph
+ Flotr.EventAdapter.observe(job_size_graph_container, 'flotr:click', function () {
+ this.prepare_job_size();
+ }.bind(this));
+
+ // JOB FILES
+
+ var job_files_select_cb = function(area) {
+ var opts = select_area(area);
+ this.prepare_job_files(opts);
+ }.bind(this);
+
+ var job_files_graph_container = document.getElementById(this.ids.job_files_graph);
+
+ // set Flotr-specific select area event for job files graph
+ Flotr.EventAdapter.observe(job_files_graph_container, 'flotr:select', job_files_select_cb);
+
+ // set Flotr-specific click area event (zoom reset) for job files graph
+ Flotr.EventAdapter.observe(job_files_graph_container, 'flotr:click', function (e) {
+ this.prepare_job_files();
+ }.bind(this));
+ },
+ prepare_job_objs: function(jobs, graph_type) {
+ var job;
+ var job_objs = [];
+ for (var i = 0; i < jobs.length; i++) {
+ if (jobs[i].jobstatus == 'R' || jobs[i].jobstatus == 'C' || jobs[i].endtime === null) {
+ continue;
+ }
+ job = new JobClass(jobs[i], graph_type);
+ job_objs.push(job);
+ }
+ return job_objs;
+ },
+ prepare_job_summary: function() {
+ this.destroy_job_summary();
+
+ Statistics.grab_statistics(oData, JobStatus.get_states());
+ this.graphs.job_summary = new GraphPieClass({
+ jobs: Statistics.jobs_summary,
+ container_id: this.ids.jobs_summary_graph,
+ title: this.txt.job_summary.graph_title
+ });
+ },
+ prepare_job_size: function(opts) {
+ var options = {
+ title: this.txt.job_size.graph_title,
+ xaxis: {
+ title: this.txt.job_size.xaxis_title
+ },
+ yaxis: {
+ title: this.txt.job_size.yaxis_title,
+ tickFormatter: function(val, axis_opts) {
+ return Units.get_formatted_size(val);
+ }
+ }
+ };
+ var jobs = this.prepare_job_objs(oData.jobs, 'job_size');
+ var container = document.getElementById(this.ids.job_size_graph);
+ opts = $.extend(true, opts || {}, options);
+ this.graphs.job_size = this.prepare_job_graph(jobs, container, opts);
+ },
+ prepare_job_files: function(opts) {
+ var options = {
+ title: this.txt.job_files.graph_title,
+ xaxis: {
+ title: this.txt.job_files.xaxis_title
+ },
+ yaxis: {
+ title: this.txt.job_files.yaxis_title,
+ tickFormatter: function(val, axis_opts) {
+ return parseInt(val, 10);
+ }
+ }
+ };
+ var jobs = this.prepare_job_objs(oData.jobs, 'job_files');
+ var container = document.getElementById(this.ids.job_files_graph);
+ opts = $.extend(true, opts || {}, options);
+ this.graphs.job_files = this.prepare_job_graph(jobs, container, opts);
+ },
+ prepare_job_graph: function(jobs, container, opts) {
+ var now = (new Date()).getTime();
+ var options = $.extend(true, this.graph_options, {
+ xaxis: {
+ min: (now - 2592000000),
+ max: now
+ },
+ yaxis: {
+ autoscale: true,
+ min: 0,
+ max: null
+ }
+ });
+ if (opts) {
+ options = $.extend(true, options, opts);
+ }
+
+ var series_uniq = {};
+ for (var i = 0; i < jobs.length; i++) {
+ if(jobs[i].start_stamp < this.graph_options.xaxis.min || jobs[i].end_stamp > this.graph_options.xaxis.max) {
+ continue;
+ }
+ if (series_uniq.hasOwnProperty(jobs[i].job.level) == false) {
+ series_uniq[jobs[i].job.level] = [];
+ }
+ series_uniq[jobs[i].job.level].push(jobs[i].start_point, jobs[i].end_point, [null, null]);
+
+ }
+ var serie, series = [], label;
+ for (var key in series_uniq) {
+ serie = [];
+ for (var i = 0; i < series_uniq[key].length; i++) {
+ serie.push(series_uniq[key][i]);
+ }
+ label = JobLevel.get_level(key);
+ var color = this.colors.O;
+ if (this.colors.hasOwnProperty(key)) {
+ color = this.colors[key];
+ }
+ series.push({
+ data: serie,
+ label: label,
+ color: color
+ });
+ }
+ return this.draw_graph(container, series, options);
+ },
+ draw_graph: function(container, series, opts) {
+ return Flotr.draw(
+ container,
+ series,
+ opts
+ );
+ },
+ destroy_job_summary: function() {
+ if (this.graphs.job_summary) {
+ this.graphs.job_summary.pie.destroy();
+ }
+ }
+};
+ </script>
</div>
<div class="w3-container tab_item" id="job_config" style="display: none">
<com:Application.Web.Portlets.BaculaConfigDirectives
}
};
$(function() {
- MonitorCallsInterval.push(function() { oJobHistoryList.init(); });
+ MonitorCallsInterval.push(function() {
+ oJobGraphs.update();
+ oJobHistoryList.init();
+ });
});
</script>
</div>
* Bacula(R) - The Network Backup Solution
* Baculum - Bacula web interface
*
- * Copyright (C) 2013-2019 Kern Sibbald
+ * Copyright (C) 2013-2020 Kern Sibbald
*
* The main author of Baculum is Marcin Haba.
* The original author of Bacula is Kern Sibbald, with contributions
class JobView extends BaculumWebPage {
const JOB_NAME = 'JobName';
-
- const USE_CACHE = true;
-
- const RESOURCE_SHOW_PATTERN = '/^\s+--> %resource: name=(.+?(?=\s\S+\=.+)|.+$)/i';
-
- private $jobdata;
- public $is_running = false;
- public $fileset;
- public $schedule;
+ const JOB_INFO = 'JobInfo';
public function onInit($param) {
parent::onInit($param);
+ $this->JobConfig->attachEventHandler('OnSave', [$this, 'reloadJobInfo']);
if ($this->IsCallBack || $this->IsPostBack) {
return;
}
$this->setJobName($job_name);
$this->Schedules->setJob($job_name);
$this->Schedules->setDays(90);
+ $this->setJobInfo($job_name);
}
/**
return $this->getViewState(self::JOB_NAME);
}
+ /**
+ * Set job information from show job output.
+ *
+ * @return none
+ */
+ public function setJobInfo($job_name) {
+ $job_show = $this->getModule('api')->get(
+ array('jobs', 'show', '?name='. rawurlencode($job_name)),
+ null,
+ true,
+ false
+ );
+ if ($job_show->error == 0) {
+ $job_info = $this->getModule('job_info')->parseResourceDirectives($job_show->output);
+ $this->setViewState(self::JOB_INFO, $job_info);
+ }
+ }
+
+ /**
+ * Get job information.
+ *
+ * @return array job information
+ */
+ public function getJobInfo() {
+ return $this->getViewState(self::JOB_INFO, []);
+ }
+
+ /**
+ * Reload job information.
+ *
+ * @param mixed $param save event parameter
+ * @return none
+ */
+ public function reloadJobInfo($param) {
+ if ($this->Request->contains('job')) {
+ $this->setJobInfo($this->Request['job']);
+ }
+ }
+
public function loadRunJobModal($sender, $param) {
$this->RunJobModal->loadData();
}
}
}
- public function getResourceName($resource, $jobshow) {
- $resource_name = null;
- $pattern = str_replace('%resource', $resource, self::RESOURCE_SHOW_PATTERN);
- for ($i = 0; $i < count($jobshow); $i++) {
- if (preg_match($pattern, $jobshow[$i], $match) === 1) {
- $resource_name = $match[1];
- break;
- }
- }
- return $resource_name;
- }
-
public function loadFileSetConfig($sender, $param) {
if (!empty($_SESSION['dir'])) {
- $jobshow = $this->getModule('api')->get(array(
- 'jobs', 'show', '?name=' . rawurlencode($this->getJobName())
- ))->output;
- $fileset = $this->getResourceName('fileset', $jobshow);
- $this->FileSetConfig->setComponentName($_SESSION['dir']);
- $this->FileSetConfig->setResourceName($fileset);
- $this->FileSetConfig->setLoadValues(true);
- $this->FileSetConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ $job_info = $this->getJobInfo();
+ if (key_exists('fileset', $job_info)) {
+ $this->FileSetConfig->setComponentName($_SESSION['dir']);
+ $this->FileSetConfig->setResourceName($job_info['fileset']['name']);
+ $this->FileSetConfig->setLoadValues(true);
+ $this->FileSetConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ }
}
}
public function loadScheduleConfig($sender, $param) {
if (!empty($_SESSION['dir'])) {
- $jobshow = $this->getModule('api')->get(array(
- 'jobs', 'show', '?name=' . rawurlencode($this->getJobName())
- ))->output;
- $schedule = $this->getResourceName('schedule', $jobshow);
- $this->ScheduleConfig->setComponentName($_SESSION['dir']);
- $this->ScheduleConfig->setResourceName($schedule);
- $this->ScheduleConfig->setLoadValues(true);
- $this->ScheduleConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ $job_info = $this->getJobInfo();
+ if (key_exists('schedule', $job_info)) {
+ $this->ScheduleConfig->setComponentName($_SESSION['dir']);
+ $this->ScheduleConfig->setResourceName($job_info['schedule']['name']);
+ $this->ScheduleConfig->setLoadValues(true);
+ $this->ScheduleConfig->raiseEvent('OnDirectiveListLoad', $this, null);
+ } else {
+ $this->ScheduleConfig->unloadDirectives();
+ }
}
}
<module id="user_role" class="Application.Web.Class.WebUserRoles" />
<module id="auth" class="System.Security.TAuthManager" UserManager="users" LoginPage="LoginPage" />
<module id="users" class="Application.Web.Class.WebUserManager" UserClass="Application.Web.Class.WebUser" />
+ <!-- data modules -->
+ <module id="job_info" class="Application.Web.Class.JobInfo" />
</modules>
</configuration>
const USE_CACHE = true;
- const RESOURCE_SHOW_PATTERN = '/^\s+--> %resource: name=(.+?(?=\s\S+\=.+)|.+$)/i';
- const JOB_SHOW_PATTERN = '/^Job:\sname=(?P<jobname>.+)\sJobType=\d+\slevel=(?P<level>\w+)?\sPriority=(?P<priority>\d+)/i';
- const ACCURATE_PATTERN = '/^\s+Accurate=(?P<accurate>\d)/i';
-
const DEFAULT_JOB_PRIORITY = 10;
public $job_to_verify = array('C', 'O', 'd', 'A');
$jobid = $this->getJobId();
$jobname = $this->getJobName();
$jobdata = null;
+ $job_info = [];
+
if ($jobid > 0) {
- $jobdata = $this->getModule('api')->get(array('jobs', $jobid), null, true, self::USE_CACHE)->output;
+ $jobdata = $this->getModule('api')->get(
+ ['jobs', $jobid],
+ null,
+ true,
+ self::USE_CACHE
+ );
+ if ($jobdata->error == 0) {
+ $jobname = $jobdata->name;
+ }
+ }
+
+ if (!empty($jobname)) {
$job_show = $this->getModule('api')->get(
- array('jobs', 'show', '?name='. rawurlencode($jobdata->name)),
+ ['jobs', 'show', '?name='. rawurlencode($jobname)],
null,
true,
self::USE_CACHE
- )->output;
- $jobdata->storage = $this->getResourceName('(?:storage|autochanger)', $job_show);
+ );
+ if ($job_show->error == 0) {
+ $job_info = $this->getModule('job_info')->parseResourceDirectives($job_show->output);
+ }
+ }
+
+ if ($jobid > 0) {
+ $storage = key_exists('storage', $job_info) ? $job_info['storage']['name'] : null;
+ $autochanger = key_exists('autochanger', $job_info) ? $job_info['autochanger']['name'] : null;
+ $jobdata->storage = $storage ?: $autochanger;
$this->getPage()->getCallbackClient()->show('run_job_storage_from_config_info');
} elseif (!empty($jobname)) {
$jobdata = new stdClass;
)->output;
$levels = $this->getModule('misc')->getJobLevels();
$levels_flip = array_flip($levels);
- $job_attr = $this->getJobAttr($job_show);
- if (!empty($job_attr['level'])) {
- $jobdata->level = $levels_flip[$job_attr['level']];
+ if (key_exists('job', $job_info) && !empty($job_info['job']['level'])) {
+ $jobdata->level = $levels_flip[$job_info['job']['level']];
}
- $jobdata->client = $this->getResourceName('client', $job_show);
- $jobdata->fileset = $this->getResourceName('fileset', $job_show);
- $jobdata->pool = $this->getResourceName('pool', $job_show);
- $jobdata->storage = $this->getResourceName('(?:storage|autochanger)', $job_show);
- $jobdata->priorjobid = $job_attr['priority'];
- $jobdata->accurate = (key_exists('accurate', $job_attr) && $job_attr['accurate'] == 1);
+ $client = key_exists('client', $job_info) ? $job_info['client']['name'] : null;
+ $fileset = key_exists('fileset', $job_info) ? $job_info['fileset']['name'] : null;
+ $pool = key_exists('pool', $job_info) ? $job_info['pool']['name'] : null;
+ $storage = key_exists('storage', $job_info) ? $job_info['storage']['name'] : null;
+ $autochanger = key_exists('autochanger', $job_info) ? $job_info['autochanger']['name'] : null;
+ $priority = key_exists('job', $job_info) ? $job_info['job']['priority'] : self::DEFAULT_JOB_PRIORITY;
+ $accurate = key_exists('job', $job_info) && key_exists('accurate', $job_info['job']) ? $job_info['job']['accurate'] : 0;
+ $jobdata->client = $client;
+ $jobdata->fileset = $fileset;
+ $jobdata->pool = $pool;
+ $jobdata->storage = $storage ?: $autochanger;
+ $jobdata->priorjobid = $priority;
+ $jobdata->accurate = ($accurate == 1);
} else {
$jobs = array();
$job_list = $this->getModule('api')->get(array('jobs', 'resnames'), null, true, self::USE_CACHE)->output;
return $verifyVals;
}
- public function getResourceName($resource, $jobshow) {
- $resource_name = null;
- $pattern = str_replace('%resource', $resource, self::RESOURCE_SHOW_PATTERN);
- for ($i = 0; $i < count($jobshow); $i++) {
- if (preg_match($pattern, $jobshow[$i], $match) === 1) {
- $resource_name = $match[1];
- break;
- }
- }
- return $resource_name;
- }
-
- public function getJobAttr($jobshow) {
- $attr = array();
- for ($i = 0; $i < count($jobshow); $i++) {
- if (preg_match(self::JOB_SHOW_PATTERN, $jobshow[$i], $match) === 1) {
- $attr['jobname'] = $match['jobname'];
- $attr['level'] = $match['level'];
- $attr['priority'] = $match['priority'];
- }
- if (preg_match(self::ACCURATE_PATTERN, $jobshow[$i], $match) === 1) {
- $attr['accurate'] = $match['accurate'];
- break;
- }
- }
- return $attr;
- }
-
-
public function estimate($sender, $param) {
$params = array();
$jobid = $this->getJobId();
z-index: 0;
}
-#jobs_summary_graph canvas:nth-child(2) {
- position: static !important;
-}
-
.monospace {
font-family: "Courier New", Courier, monospace !important;
}
.field_invalid {
border: 1px solid red !important;
}
+
+#job_graph_container {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+#job_graph_container > div {
+ width: 420px;
+ margin: 10px;
+}