Skip to content
Snippets Groups Projects
Commit 3d9fe230 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Automatic reload of node states (#3827); bugfix of missing size after upload and other minor issues

parent 8e505442
Branches
No related tags found
No related merge requests found
Pipeline #1710 passed
......@@ -48,8 +48,8 @@ public class NodeInfo {
this.sticky = isSticky(node);
this.busy = isBusy(node);
this.listOfFiles = isListOfFiles(node);
this.writable = NodeUtils.checkIfWritable(node, user.getName(), user.getGroups());
this.deletable = writable && !sticky;
this.writable = NodeUtils.checkIfWritable(node, user.getName(), user.getGroups()) && !busy;
this.deletable = writable && !sticky && !asyncTrans;
}
private String getPath(Node node) {
......
......@@ -21,6 +21,7 @@
import { mapState } from 'vuex'
import TopMenu from './components/TopMenu.vue'
import client from 'api-client'
import nodesReloader from './nodesReloader.js'
export default {
name: 'App',
......@@ -34,6 +35,13 @@ export default {
this.$store.dispatch('loadJobs');
this.$store.dispatch('loadUserInfo');
setInterval(client.keepalive, 60000);
setInterval(nodesReloader.checkNodes, 1000);
let self = this;
setInterval(function() {
if (self.$router.currentRoute.path !== '/jobs') {
self.$store.dispatch('checkJobs');
}
}, 1000);
}
}
</script>
......
......@@ -46,7 +46,7 @@ function escapePath(path) {
}
export default {
getNode(path) {
getNode(path, loading) {
let url = BASE_API_URL + 'nodes/' + escapePath(path);
return apiRequest({
method: 'GET',
......@@ -55,7 +55,7 @@ export default {
headers: {
'Cache-Control': 'no-cache'
}
}, true, true);
}, (typeof loading !== 'undefined') ? loading : true, true);
},
loadJobs() {
let url = BASE_API_URL + 'jobs';
......@@ -124,11 +124,11 @@ export default {
uploadFile(url, file) {
let formData = new FormData();
formData.append('file', file);
axios.put(url, formData, {
return axios.put(url, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
});
},
deleteNodes(paths) {
let url = BASE_API_URL + 'delete';
......
<template>
<div>
<b-button variant="primary" class="float-right" @click="loadJobs">Reload</b-button>
<h3>Async recall jobs</h3>
<table class="table b-table table-striped table-hover">
<thead>
......
import store from './store.js'
import client from 'api-client'
let lastResultTime = null;
let times = 0;
function checkNodes() {
let busyNodes = document.getElementsByClassName('node-busy').length > 0;
let jobsInProgress = store.state.jobs.filter(j => j.phase === 'EXECUTING').length > 0;
if (!busyNodes && !jobsInProgress) {
// reset state
lastResultTime = null;
times = 0;
return;
}
if (lastResultTime !== null) {
// first 10 times check every second, then check every ten seconds
let offset = times < 10 ? 1000 : 10000;
let now = new Date().getTime();
if (now - lastResultTime < offset) {
return;
}
}
if (!store.state.nodesLoading) {
let path = store.state.path;
store.commit('setNodesLoading', true);
client.getNode(path, false)
.then(res => {
// check that path didn't change in meantime by user action
if (path === store.state.path) {
let resHasBusyNodes = res.htmlTable.includes('node-busy');
if ((!busyNodes && resHasBusyNodes) || (busyNodes && !resHasBusyNodes)) {
store.dispatch('setNodes', res);
} else {
times++;
lastResultTime = new Date().getTime();
}
}
})
.finally(() => {
store.commit('setNodesLoading', false);
});
}
}
export default {
checkNodes
}
......@@ -20,10 +20,12 @@ export default new Vuex.Store({
state: {
path: '',
loading: true,
nodesLoading: false,
asyncButtonEnabled: false,
deleteButtonEnabled: false,
jobs: [],
jobsLoading: true,
lastJobsCheckTime: null,
user: 'anonymous',
nodesToDelete: [],
writable: false,
......@@ -43,6 +45,9 @@ export default new Vuex.Store({
}
state.path = value;
},
setNodesLoading(state, value) {
state.nodesLoading = value;
},
setAsyncButtonEnabled(state, value) {
state.asyncButtonEnabled = value;
},
......@@ -76,8 +81,14 @@ export default new Vuex.Store({
actions: {
setPath({ state, commit, dispatch }, path) {
commit('setPath', path);
commit('setNodesLoading', true);
client.getNode(state.path)
.then(res => {
dispatch('setNodes', res);
})
.finally(() => commit('setNodesLoading', false));
},
setNodes({ commit, dispatch }, res) {
commit('setWritable', res.writable);
document.getElementById('nodes').outerHTML = res.htmlTable;
let checkboxes = document.querySelectorAll('#nodes input[type="checkbox"]');
......@@ -87,13 +98,12 @@ export default new Vuex.Store({
});
}
dispatch('computeButtonsVisibility');
});
},
computeButtonsVisibility({ commit }) {
commit('setAsyncButtonEnabled', document.querySelectorAll('#nodes input.async:checked').length > 0);
commit('setDeleteButtonEnabled', document.querySelectorAll('#nodes input.deletable:checked').length > 0);
},
startAsyncRecallJob({ commit }) {
startAsyncRecallJob({ state, commit, dispatch }) {
let asyncCheckboxes = document.querySelectorAll('#nodes input.async:checked');
let paths = [];
for (let i = 0; i < asyncCheckboxes.length; i++) {
......@@ -101,15 +111,45 @@ export default new Vuex.Store({
}
client.startAsyncRecallJob(paths)
.then(job => {
main.showInfo('Job started');
main.showInfo('Job queued');
commit('addJob', job);
// Reload current node
dispatch('setPath', state.path);
});
},
loadJobs({ commit }) {
checkJobs({ state, dispatch }) {
if (state.jobs.filter(j => j.phase === 'QUEUED' || j.phase === 'PENDING' || j.phase === 'EXECUTING').length > 0 &&
!state.jobsLoading && (state.lastJobsCheckTime !== null && new Date().getTime() - state.lastJobsCheckTime > 5000)) {
dispatch('loadJobs');
}
},
loadJobs({ state, commit }) {
commit('setJobsLoading', true);
client.loadJobs()
.then(jobs => commit('setJobs', jobs))
.finally(() => commit('setJobsLoading', false));
.then(jobs => {
for (let previousJob of state.jobs) {
for (let newJob of jobs) {
if (newJob.id === previousJob.id && newJob.phase !== previousJob.phase) {
switch (newJob.phase) {
case 'EXECUTING':
main.showInfo('Job started');
break;
case 'COMPLETED':
main.showInfo('Job completed');
break;
case 'ERROR':
main.showError('Job failed');
break;
}
}
}
}
commit('setJobs', jobs);
})
.finally(() => {
commit('setJobsLoading', false);
state.lastJobsCheckTime = new Date().getTime();
});
},
loadUserInfo({ commit }) {
client.getUserInfo()
......@@ -129,14 +169,15 @@ export default new Vuex.Store({
}
client.prepareForUpload(state.path, names)
.then(uploadUrls => {
let uploads = [];
for (let i = 0; i < files.length; i++) {
client.uploadFile(uploadUrls[i], files[i]);
uploads.push(client.uploadFile(uploadUrls[i], files[i]));
}
})
.then(() => {
// Reload current node
Promise.all(uploads).then(() => {
// Reload current node when all uploads completed
dispatch('setPath', state.path);
});
});
},
deleteNodes({ state, dispatch }) {
client.deleteNodes(state.nodesToDelete)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment