329 lines
11 KiB
JavaScript
Executable File
329 lines
11 KiB
JavaScript
Executable File
class AremaRest
|
|
{
|
|
fetchPromise(url)
|
|
{
|
|
return fetch(url, {
|
|
method: 'GET',
|
|
headers: {'Accept': 'application/json'}
|
|
})
|
|
.catch(err => {
|
|
console.error('ERROR fetching data: %o', err);
|
|
})
|
|
.then(function (response) {
|
|
return response.json();
|
|
})
|
|
.catch(err => {
|
|
console.error('ERROR converting data to JSON: %o', err);
|
|
})
|
|
.then((data) => {
|
|
//console.log('JSON data: %o', data);
|
|
return this.fixXMLJSON(data);
|
|
})
|
|
.catch(err => {
|
|
console.error('ERROR fixing/enriching JSON: %o', err);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Arema converts XML to JSON, so:
|
|
* <param name="key1">value1</param>
|
|
* <param name="key2">value2</param>
|
|
* becomes
|
|
* "param": [
|
|
* { "@name": "key1", "$": "value1" },
|
|
* { "@name": "key2", "$": "value2" },
|
|
* ]
|
|
* This method tries to fix that (assuming all names/keys are unique).
|
|
*/
|
|
fixXMLJSON(data)
|
|
{
|
|
if (!Array.isArray(data) && typeof data !== 'object') {
|
|
return data;
|
|
}
|
|
for (var i in data) {
|
|
if (!data.hasOwnProperty(i)) {
|
|
continue;
|
|
}
|
|
if (Array.isArray(data[i])) {
|
|
// console.log('Found Array: %o', data[i]);
|
|
var key = false;
|
|
if (data[i][0]['@id']) {
|
|
key = '@id';
|
|
} else if (data[i][0]['@name']) {
|
|
key = '@name';
|
|
}
|
|
if (!key) {
|
|
// This doesn't seem to be the correct structure --> ignore
|
|
//console.log('Ignoring structure: %o', data[i]);
|
|
data[i] = this.fixXMLJSON(data[i]);
|
|
continue;
|
|
}
|
|
var new_assoc = {};
|
|
for (var j=0; j<data[i].length; j++) {
|
|
var record = data[i][j];
|
|
var thiskey = record[key];
|
|
delete record[key];
|
|
if (Object.keys(record).length == 1 && '$' in record) {
|
|
record = record['$'];
|
|
}
|
|
new_assoc[thiskey] = record;
|
|
}
|
|
// console.log('New Array: %o', new_assoc);
|
|
data[i] = this.fixXMLJSON(new_assoc);
|
|
} else {
|
|
data[i] = this.fixXMLJSON(data[i]);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
getJoblist()
|
|
{
|
|
var current_url = new URL(window.location.href);
|
|
var url = 'aremaproxy.php?fetch=joblist&q=' + encodeURIComponent(current_url.searchParams.get('q'));
|
|
return this.fetchPromise(url);
|
|
}
|
|
|
|
getJobDetails(jobId)
|
|
{
|
|
var url = 'aremaproxy.php?fetch=jobdetails&jobid=' + jobId;
|
|
return this.fetchPromise(url);
|
|
}
|
|
|
|
getJobProgress(jobId)
|
|
{
|
|
var url = 'aremaproxy.php?fetch=jobprogress&jobid=' + jobId;
|
|
return this.fetchPromise(url);
|
|
}
|
|
}
|
|
|
|
class JobRow extends React.Component
|
|
{
|
|
|
|
constructor(props)
|
|
{
|
|
super(props);
|
|
this.job_states = {
|
|
2: "Queued",
|
|
3: "Started",
|
|
4: "Dispatched",
|
|
5: "Running",
|
|
6: "Cancelling",
|
|
8: "Waiting for Resource",
|
|
9: "Waiting for Trigger",
|
|
10: "Finished",
|
|
11: "Finished with Problems",
|
|
12: "Finished with Errors",
|
|
13: "Finished but Dependency Error",
|
|
14: "Cancelled",
|
|
20: "Finished with Warnings",
|
|
30: "Skipped"
|
|
};
|
|
|
|
this.state = {
|
|
jobId: props.jobId,
|
|
name: '... fetching details ...',
|
|
created: '',
|
|
created_date: '',
|
|
created_time: '--:--:--',
|
|
state_numeric: -1,
|
|
state: '---',
|
|
current_step: '',
|
|
percent: 0
|
|
};
|
|
}
|
|
|
|
updateDetails()
|
|
{
|
|
var req = new AremaRest();
|
|
req.getJobDetails(this.state.jobId)
|
|
.then((data) => {
|
|
// console.log('updateDetails got data: %o', data);
|
|
var cur = this.state;
|
|
cur.name = data.job.param.title;
|
|
|
|
// Creation time
|
|
var ctime = new Date(data.job.creation_time);
|
|
cur.created = data.job.creation_time;
|
|
cur.created_date = ctime.toLocaleDateString();
|
|
cur.created_time = ctime.toLocaleTimeString();
|
|
|
|
// Status / Progress
|
|
cur.state_numeric = data.job.state;
|
|
cur.state = this.job_states[data.job.state];
|
|
if (data.job.state == 10) {
|
|
cur.percent = 100;
|
|
}
|
|
|
|
this.setState(cur);
|
|
if (data.job.state < 10) {
|
|
// job still running, query progress in 1 second
|
|
setTimeout(() => { this.updateProgress(); }, 1000);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('ERROR processing updateDetails(): %o', err);
|
|
setTimeout(() => { this.updateDetails(); }, 1000);
|
|
});
|
|
}
|
|
|
|
updateProgress()
|
|
{
|
|
var req = new AremaRest();
|
|
req.getJobProgress(this.state.jobId)
|
|
.then((data) => {
|
|
// console.log('updateProgress got data: %o', data);
|
|
var cur = this.state;
|
|
|
|
cur.state_numeric = data.state;
|
|
cur.state = this.job_states[data.state];
|
|
cur.current_step = '';
|
|
|
|
// calculate percentage
|
|
var num_steps = 0;
|
|
var steps_done = 0;
|
|
var overall_percent = 0;
|
|
for (var step in data.job_steps.job_step) {
|
|
if (!data.job_steps.job_step.hasOwnProperty(step)) {
|
|
continue;
|
|
}
|
|
var this_step = data.job_steps.job_step[step];
|
|
//console.log('PCalc: Status: %o, Percent: %o', this_step.state, this_step.percent);
|
|
num_steps++;
|
|
if (this_step.state == 10) {
|
|
overall_percent += 100;
|
|
steps_done++;
|
|
} else {
|
|
overall_percent += parseInt(this_step.percent);
|
|
}
|
|
if (this_step.state == 5) {
|
|
cur.current_step = '↪ ' + step;
|
|
}
|
|
}
|
|
if (cur.current_step.length > 0) {
|
|
// only add step-total if there was info created in the lines above
|
|
cur.current_step += ' (' + (steps_done+1) + '/' + num_steps + ')';
|
|
}
|
|
cur.percent = Math.floor( overall_percent / num_steps );
|
|
//console.log('calculated: %o', cur.percent);
|
|
|
|
this.setState(cur);
|
|
if (data.state < 10) {
|
|
// job still running, update progress in 5 seconds
|
|
setTimeout(() => { this.updateProgress(); }, 5000);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
console.error('ERROR processing updateProgress(): %o', err);
|
|
setTimeout(() => { this.updateProgress(); }, 1000);
|
|
});
|
|
}
|
|
|
|
componentDidMount()
|
|
{
|
|
this.updateDetails();
|
|
}
|
|
|
|
render()
|
|
{
|
|
//console.log('Obj: %o', this);
|
|
var now = new Date();
|
|
var date_str = '';
|
|
if (now.toLocaleDateString() != this.state.created_date) {
|
|
date_str = this.state.created_date + "\n";
|
|
}
|
|
return React.createElement('tr', { className: 'state'+this.state.state_numeric },
|
|
React.createElement('td', null, this.state.jobId),
|
|
React.createElement('td', null, this.state.name, React.createElement('div', { className: 'current-step' }, this.state.current_step)),
|
|
React.createElement('td', { style: { textAlign: 'center', verticalAlign: 'middle', whiteSpace: 'nowrap' } }, date_str + this.state.created_time),
|
|
React.createElement('td', { style: { verticalAlign: 'middle' } },
|
|
React.createElement('span', { className: 'uk-label state'+this.state.state_numeric }, this.state.state)
|
|
),
|
|
React.createElement('td', { style: { verticalAlign: 'middle' } },
|
|
React.createElement('progress', { className: 'uk-progress uk-progress-striped uk-active', value: this.state.percent, max: 100 }, this.state.percent + '%' )
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
class JobList extends React.Component
|
|
{
|
|
constructor(props)
|
|
{
|
|
super(props);
|
|
this.state = {
|
|
jobIds: []
|
|
};
|
|
}
|
|
|
|
updateJobs()
|
|
{
|
|
var req = new AremaRest();
|
|
req.getJoblist()
|
|
.then((data) => {
|
|
// console.log('updateJobs got data: %o', data);
|
|
this.setState({
|
|
jobIds: data.jobs.job
|
|
});
|
|
setTimeout(() => { this.updateJobs(); }, 11000);
|
|
})
|
|
.catch(err => {
|
|
console.error('ERROR processing updateJobs(): %o', err);
|
|
setTimeout(() => { this.updateJobs(); }, 2000);
|
|
});
|
|
}
|
|
|
|
componentDidMount()
|
|
{
|
|
this.updateJobs();
|
|
window.setJobList = (data) => {
|
|
this.setState(data);
|
|
}
|
|
window.appendJobList = (data) => {
|
|
var list = this.state;
|
|
// console.log('Current state: %o', list);
|
|
for (var i=0; i<data.length; i++) {
|
|
list.jobIds.push(data[i]);
|
|
}
|
|
|
|
this.setState(list);
|
|
}
|
|
}
|
|
|
|
render()
|
|
{
|
|
var rows = this.state.jobIds.map(function(jobId) {
|
|
return React.createElement(JobRow, { key: jobId, jobId: jobId })
|
|
});
|
|
return React.createElement('div', {},
|
|
//React.createElement('button', { onClick: this.updateJobs.bind(this) }, 'Update'),
|
|
React.createElement('table', { className: 'uk-table uk-table-divider' },
|
|
React.createElement('thead', null,
|
|
React.createElement('tr', null,
|
|
React.createElement('th', null, 'ID'),
|
|
React.createElement('th', null, 'Name'),
|
|
React.createElement('th', null, 'Created'),
|
|
React.createElement('th', null, 'Status'),
|
|
React.createElement('th', null, 'Progress')
|
|
)
|
|
),
|
|
React.createElement('tbody', null, rows)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", function(event) {
|
|
|
|
ReactDOM.render(
|
|
React.createElement(JobList),
|
|
document.getElementById('joblist')
|
|
);
|
|
|
|
var current_url = new URL(window.location.href);
|
|
if (current_url.searchParams.get('q')) {
|
|
document.forms[0].elements.q.value = current_url.searchParams.get('q');
|
|
}
|
|
|
|
});
|