@@ -9,7 +9,9 @@ export const networkService = { | |||||
getStatusesByNetworkId, | getStatusesByNetworkId, | ||||
getNodesByNetworkId, | getNodesByNetworkId, | ||||
stopNetwork, | stopNetwork, | ||||
deleteNetwork | |||||
deleteNetwork, | |||||
requestNetworkKeys, | |||||
retrieveNetworkKeys | |||||
}; | }; | ||||
function getAllNetworks(userId, messages, errors) { | function getAllNetworks(userId, messages, errors) { | ||||
@@ -46,3 +48,11 @@ function stopNetwork(userId, networkId, messages, errors) { | |||||
function deleteNetwork(userId, networkId, messages, errors) { | function deleteNetwork(userId, networkId, messages, errors) { | ||||
return fetch(`${config.apiUrl}/users/${userId}/plans/${networkId}`, util.deleteWithAuth()).then(util.handleCrudResponse(messages, errors)); | return fetch(`${config.apiUrl}/users/${userId}/plans/${networkId}`, util.deleteWithAuth()).then(util.handleCrudResponse(messages, errors)); | ||||
} | } | ||||
function requestNetworkKeys(userId, networkId, messages, errors) { | |||||
return fetch(`${config.apiUrl}/users/${userId}/networks/${networkId}/actions/keys`, util.getWithAuth()).then(util.handleCrudResponse(messages, errors)); | |||||
} | |||||
function retrieveNetworkKeys(userId, networkId, code, password, messages, errors) { | |||||
return fetch(`${config.apiUrl}/users/${userId}/networks/${networkId}/actions/keys/${code}`, util.postWithAuth({name: 'password', value: password})).then(util.handleCrudResponse(messages, errors)); | |||||
} |
@@ -5,7 +5,8 @@ import { util } from '../_helpers'; | |||||
const state = { | const state = { | ||||
loading: { | loading: { | ||||
networks: false, network: false, deleting: false, | networks: false, network: false, deleting: false, | ||||
nearestRegions: false, startingNetwork: false, networkStatuses: false, networkNodes: false | |||||
nearestRegions: false, startingNetwork: false, networkStatuses: false, networkNodes: false, | |||||
requestNetworkKeys: false, retrieveNetworkKeys: false | |||||
}, | }, | ||||
creating: null, | creating: null, | ||||
error: null, | error: null, | ||||
@@ -14,7 +15,9 @@ const state = { | |||||
nearestRegions: null, | nearestRegions: null, | ||||
newNodeNotification: null, | newNodeNotification: null, | ||||
networkStatuses: {}, | networkStatuses: {}, | ||||
networkNodes: null | |||||
networkNodes: null, | |||||
networkKeysRequested: null, | |||||
networkKeys: null | |||||
}; | }; | ||||
const actions = { | const actions = { | ||||
@@ -70,7 +73,7 @@ const actions = { | |||||
}, | }, | ||||
stopNetwork({ commit }, {userId, networkId, messages, errors}) { | stopNetwork({ commit }, {userId, networkId, messages, errors}) { | ||||
commit('stopNetworkRequest', id); | |||||
commit('stopNetworkRequest', networkId); | |||||
networkService.stopNetwork(userId, networkId, messages, errors) | networkService.stopNetwork(userId, networkId, messages, errors) | ||||
.then( | .then( | ||||
network => commit('stopNetworkSuccess', network), | network => commit('stopNetworkSuccess', network), | ||||
@@ -79,7 +82,7 @@ const actions = { | |||||
}, | }, | ||||
deleteNetwork({ commit }, {userId, networkId, messages, errors}) { | deleteNetwork({ commit }, {userId, networkId, messages, errors}) { | ||||
commit('deleteNetworkRequest', id); | |||||
commit('deleteNetworkRequest', networkId); | |||||
networkService.deleteNetwork(userId, networkId, messages, errors) | networkService.deleteNetwork(userId, networkId, messages, errors) | ||||
.then( | .then( | ||||
network => commit('deleteNetworkSuccess', network), | network => commit('deleteNetworkSuccess', network), | ||||
@@ -95,6 +98,24 @@ const actions = { | |||||
error => commit('getNearestRegionsFailure', error) | error => commit('getNearestRegionsFailure', error) | ||||
); | ); | ||||
}, | }, | ||||
requestNetworkKeys({ commit }, {userId, networkId, messages, errors}) { | |||||
commit('requestNetworkKeysRequest'); | |||||
networkService.requestNetworkKeys(userId, networkId, messages, errors) | |||||
.then( | |||||
ok => commit('requestNetworkKeysSuccess', networkId), | |||||
error => commit('requestNetworkKeysFailure', error) | |||||
); | |||||
}, | |||||
retrieveNetworkKeys({ commit }, {userId, networkId, code, password, messages, errors}) { | |||||
commit('retrieveNetworkKeysRequest'); | |||||
networkService.retrieveNetworkKeys(userId, networkId, code, password, messages, errors) | |||||
.then( | |||||
keys => commit('retrieveNetworkKeysSuccess', keys), | |||||
error => commit('retrieveNetworkKeysFailure', error) | |||||
); | |||||
} | |||||
}; | }; | ||||
const mutations = { | const mutations = { | ||||
@@ -200,6 +221,30 @@ const mutations = { | |||||
getNearestRegionsFailure(state, error) { | getNearestRegionsFailure(state, error) { | ||||
state.loading.nearestRegions = false; | state.loading.nearestRegions = false; | ||||
state.error = { error }; | state.error = { error }; | ||||
}, | |||||
requestNetworkKeysRequest(state) { | |||||
state.loading.requestNetworkKeys = true; | |||||
}, | |||||
requestNetworkKeysSuccess(state, networkId) { | |||||
state.loading.requestNetworkKeys = false; | |||||
state.networkKeysRequested = networkId; | |||||
}, | |||||
requestNetworkKeysFailure(state, error) { | |||||
state.loading.requestNetworkKeys = false; | |||||
state.error = { error }; | |||||
}, | |||||
retrieveNetworkKeysRequest(state) { | |||||
state.loading.retrieveNetworkKeys = true; | |||||
}, | |||||
retrieveNetworkKeysSuccess(state, keys) { | |||||
state.loading.retrieveNetworkKeys = false; | |||||
state.networkKeys = keys; | |||||
}, | |||||
retrieveNetworkKeysFailure(state, error) { | |||||
state.loading.retrieveNetworkKeys = false; | |||||
state.error = { error }; | |||||
} | } | ||||
}; | }; | ||||
@@ -2,6 +2,7 @@ | |||||
<div v-if="network"> | <div v-if="network"> | ||||
<h4>{{network.name}}.{{network.domainName}} - <i>{{messages['msg_network_state_'+network.state]}}</i></h4> | <h4>{{network.name}}.{{network.domainName}} - <i>{{messages['msg_network_state_'+network.state]}}</i></h4> | ||||
<div v-if="stats"> | <div v-if="stats"> | ||||
<!-- adapted from: https://code-boxx.com/simple-vanilla-javascript-progress-bar/ --> | |||||
<div class="progress-wrap"> | <div class="progress-wrap"> | ||||
<div class="progress-bar" :style="'width: '+stats.percent+'%'" :id="'progressBar_'+networkId"></div> | <div class="progress-bar" :style="'width: '+stats.percent+'%'" :id="'progressBar_'+networkId"></div> | ||||
<div class="progress-text">{{messages.label_percent.parseMessage(this, {percent: stats.percent})}}</div> | <div class="progress-text">{{messages.label_percent.parseMessage(this, {percent: stats.percent})}}</div> | ||||
@@ -38,6 +39,54 @@ | |||||
</table> | </table> | ||||
</div> | </div> | ||||
<div v-if="network.state === 'running'"> | |||||
<button @click="requestRestoreKey()">{{messages.link_network_action_request_keys}}</button> | |||||
<div v-if="errors.has('networkKeys')" class="invalid-feedback d-block">{{ errors.first('networkKeys') }}</div> | |||||
<div v-if="networkKeysRequested && networkKeysRequested === networkId">{{messages.message_network_action_keys_requested}}</div> | |||||
<h5>{{messages.message_network_action_retrieve_keys}}</h5> | |||||
<form @submit.prevent="retrieveRestoreKey()"> | |||||
<div class="form-group"> | |||||
<label for="restoreKeyCode">{{messages.field_network_key_download_code}}</label> | |||||
<input type="text" v-model="restoreKeyCode" v-validate="'required'" name="restoreKeyCode" class="form-control" :class="{ 'is-invalid': errors.has('retrieveNetworkKeys') }" /> | |||||
<div v-if="errors.has('retrieveNetworkKeys')" class="invalid-feedback">{{ errors.first('retrieveNetworkKeys') }}</div> | |||||
</div> | |||||
<div class="form-group"> | |||||
<label for="restoreKeyPassword">{{messages.field_network_key_download_password}}</label> | |||||
<input type="text" v-model="restoreKeyPassword" v-validate="'required'" name="restoreKeyPassword" class="form-control" :class="{ 'is-invalid': errors.has('password') }" /> | |||||
<div v-if="errors.has('password')" class="invalid-feedback">{{ errors.first('password') }}</div> | |||||
</div> | |||||
<button>{{messages.button_label_retrieve_keys}}</button> | |||||
<div v-if="networkKeys"> | |||||
<hr/> | |||||
<h4><b>{{messages.message_network_keys}}</b></h4>: {{networkKeys}} | |||||
<hr/> | |||||
{{messages.message_network_keys_description}} | |||||
</div> | |||||
</form> | |||||
</div> | |||||
<div v-if="network.state === 'stopped'"> | |||||
<!-- todo: add button to restart network in restore mode --> | |||||
</div> | |||||
<div> | |||||
<hr/> | |||||
<div class="text-danger"><h4>{{messages.title_network_danger_zone}}</h4></div> | |||||
<div v-if="errors.has('node')" class="invalid-feedback d-block">{{ errors.first('node') }}</div> | |||||
<div v-if="network.state === 'running'" style="border: 2px solid #000;"> | |||||
<hr/> | |||||
<button @click="stopNet()" class="text-danger">{{messages.link_network_action_stop}}</button> | |||||
{{messages.link_network_action_stop_description}} | |||||
</div> | |||||
<div v-else></div> | |||||
<hr/> | |||||
<div style="border: 2px solid #000;"> | |||||
<button @click="deleteNet()" class="text-danger">{{messages.link_network_action_delete}}</button> | |||||
{{messages.link_network_action_delete_description}} | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
@@ -48,17 +97,25 @@ | |||||
export default { | export default { | ||||
data() { | data() { | ||||
return { | return { | ||||
user: util.currentUser(), | |||||
networkId: this.$route.params.id, | networkId: this.$route.params.id, | ||||
stats: null, | stats: null, | ||||
refresher: null | |||||
refresher: null, | |||||
restoreKeyCode: null, | |||||
restoreKeyPassword: null | |||||
}; | }; | ||||
}, | }, | ||||
computed: { | computed: { | ||||
...mapState('networks', ['network', 'newNodeNotification', 'networkStatuses', 'networkNodes']), | |||||
...mapState('networks', [ | |||||
'network', 'newNodeNotification', 'networkStatuses', 'networkNodes', 'networkKeysRequested', 'networkKeys' | |||||
]), | |||||
...mapState('system', ['messages']) | ...mapState('system', ['messages']) | ||||
}, | }, | ||||
methods: { | methods: { | ||||
...mapActions('networks', ['getNetworkById', 'deleteNetwork', 'getStatusesByNetworkId', 'getNodesByNetworkId']), | |||||
...mapActions('networks', [ | |||||
'getNetworkById', 'deleteNetwork', 'getStatusesByNetworkId', 'getNodesByNetworkId', | |||||
'stopNetwork', 'deleteNetwork', 'requestNetworkKeys' | |||||
]), | |||||
...mapGetters('networks', ['loading']), | ...mapGetters('networks', ['loading']), | ||||
refreshStatus (userId) { | refreshStatus (userId) { | ||||
this.getNetworkById({userId: userId, networkId: this.networkId, messages: this.messages, errors: this.errors}); | this.getNetworkById({userId: userId, networkId: this.networkId, messages: this.messages, errors: this.errors}); | ||||
@@ -78,6 +135,44 @@ | |||||
}, | }, | ||||
startStatusRefresher (user) { | startStatusRefresher (user) { | ||||
this.refresher = setInterval(() => this.refreshStatus(user.uuid), 5000); | this.refresher = setInterval(() => this.refreshStatus(user.uuid), 5000); | ||||
}, | |||||
stopNet () { | |||||
this.errors.clear(); | |||||
this.stopNetwork({ | |||||
userId: this.user.uuid, | |||||
networkId: this.networkId, | |||||
messages: this.messages, | |||||
errors: this.errors | |||||
}); | |||||
}, | |||||
deleteNet () { | |||||
this.errors.clear(); | |||||
this.deleteNetwork({ | |||||
userId: this.user.uuid, | |||||
networkId: this.networkId, | |||||
messages: this.messages, | |||||
errors: this.errors | |||||
}); | |||||
}, | |||||
requestRestoreKey () { | |||||
this.errors.clear(); | |||||
this.requestNetworkKeys({ | |||||
userId: this.user.uuid, | |||||
networkId: this.networkId, | |||||
messages: this.messages, | |||||
errors: this.errors | |||||
}); | |||||
}, | |||||
retrieveRestoreKey () { | |||||
this.errors.clear(); | |||||
this.retrieveNetworkKeys({ | |||||
userId: this.user.uuid, | |||||
networkId: this.networkId, | |||||
code: this.restoreKeyCode, | |||||
password: this.restoreKeyPassword, | |||||
messages: this.messages, | |||||
errors: this.errors | |||||
}); | |||||
} | } | ||||
}, | }, | ||||
created () { | created () { | ||||
@@ -93,10 +188,12 @@ | |||||
// console.log('watch.networkNodes: received: '+JSON.stringify(nodes)); | // console.log('watch.networkNodes: received: '+JSON.stringify(nodes)); | ||||
}, | }, | ||||
networkStatuses (stats) { | networkStatuses (stats) { | ||||
console.log('received stats: '+JSON.stringify(stats)); | |||||
if (stats) { | if (stats) { | ||||
// adapted from: https://code-boxx.com/simple-vanilla-javascript-progress-bar/ | |||||
if (!stats.hasOwnProperty(this.networkId)) return; | if (!stats.hasOwnProperty(this.networkId)) return; | ||||
if (stats[this.networkId].length === 0) { | |||||
clearInterval(this.refresher); | |||||
return; | |||||
} | |||||
this.stats = stats[this.networkId][0]; // todo: when we have multiple nodes, this will need to be changed | this.stats = stats[this.networkId][0]; // todo: when we have multiple nodes, this will need to be changed | ||||
if (this.stats.percent === 100) { | if (this.stats.percent === 100) { | ||||
clearInterval(this.refresher); | clearInterval(this.refresher); | ||||
@@ -10,27 +10,14 @@ | |||||
<th nowrap="nowrap">{{messages.label_field_networks_locale}}</th> | <th nowrap="nowrap">{{messages.label_field_networks_locale}}</th> | ||||
<th nowrap="nowrap">{{messages.label_field_networks_timezone}}</th> | <th nowrap="nowrap">{{messages.label_field_networks_timezone}}</th> | ||||
<th nowrap="nowrap">{{messages.label_field_networks_object_state}}</th> | <th nowrap="nowrap">{{messages.label_field_networks_object_state}}</th> | ||||
<th nowrap="nowrap">{{messages.label_field_networks_action_view}}</th> | |||||
<th nowrap="nowrap">{{messages.label_field_networks_action_stop}}</th> | |||||
<th nowrap="nowrap">{{messages.label_field_networks_action_delete}}</th> | |||||
</tr> | </tr> | ||||
</thead> | </thead> | ||||
<tbody> | <tbody> | ||||
<tr v-for="network in networks" :key="network.uuid"> | <tr v-for="network in networks" :key="network.uuid"> | ||||
<td>{{network.name}}.{{network.domainName}}</td> | |||||
<td><router-link :to="{ path: '/bubble/'+ network.name }">{{network.name}}.{{network.domainName}}</router-link></td> | |||||
<td nowrap="nowrap">{{messages['locale_'+network.locale] || network.locale}}</td> | <td nowrap="nowrap">{{messages['locale_'+network.locale] || network.locale}}</td> | ||||
<td nowrap="nowrap">{{messages['tz_name_'+network.timezone] || network.timezone}}</td> | <td nowrap="nowrap">{{messages['tz_name_'+network.timezone] || network.timezone}}</td> | ||||
<td>{{messages['msg_network_state_'+network.state]}}</td> | <td>{{messages['msg_network_state_'+network.state]}}</td> | ||||
<td><router-link :to="{ path: '/bubble/'+ network.name }">{{messages.table_row_networks_action_view}}</router-link></td> | |||||
<td v-if="network.state === 'running'"> | |||||
<a @click="stopNetwork(network.uuid)" class="text-danger">{{messages.table_row_networks_action_stop}}</a> | |||||
</td> | |||||
<td v-else></td> | |||||
<td> | |||||
<a @click="deleteNetwork(network.uuid)" class="text-danger">{{messages.table_row_networks_action_delete}}</a> | |||||
</td> | |||||
</tr> | </tr> | ||||
</tbody> | </tbody> | ||||
</table> | </table> | ||||
@@ -52,9 +39,7 @@ | |||||
export default { | export default { | ||||
computed: { | computed: { | ||||
...mapState({ | |||||
networks: state => state.networks.networks | |||||
}), | |||||
...mapState('networks', ['networks']), | |||||
...mapState('system', ['messages']) | ...mapState('system', ['messages']) | ||||
}, | }, | ||||
created () { | created () { | ||||
@@ -63,6 +48,17 @@ | |||||
methods: { | methods: { | ||||
...mapActions('networks', ['getAllNetworks', 'stopNetwork', 'deleteNetwork']), | ...mapActions('networks', ['getAllNetworks', 'stopNetwork', 'deleteNetwork']), | ||||
...mapGetters('networks', ['loading']) | ...mapGetters('networks', ['loading']) | ||||
}, | |||||
watch: { | |||||
networks (nets) { | |||||
if (nets && nets.length) { | |||||
if (nets.length === 0) { | |||||
this.$router.replace({path: '/bubble/new'}); | |||||
} else if (nets.length === 1 && util.currentUser().admin !== true) { | |||||
this.$router.replace({path: '/bubble/' + nets[0].name}); | |||||
} | |||||
} | |||||
} | |||||
} | } | ||||
}; | }; | ||||
</script> | </script> |
@@ -228,7 +228,7 @@ | |||||
accountPlan: { | accountPlan: { | ||||
name: '', | name: '', | ||||
domain: '', | domain: '', | ||||
locale: '', | |||||
locale: util.currentUser().locale, | |||||
timezone: '', | timezone: '', | ||||
plan: 'bubble', | plan: 'bubble', | ||||
footprint: 'Worldwide', | footprint: 'Worldwide', | ||||
@@ -42,7 +42,7 @@ | |||||
errors: this.errors | errors: this.errors | ||||
}); | }); | ||||
} else { | } else { | ||||
this.$router.push({path:'/me/profile', params: {'action': 'invalid'}}); | |||||
this.$router.push({path:'/me/profile', params: {action: 'invalid'}}); | |||||
} | } | ||||
}, | }, | ||||
watch: { | watch: { | ||||
@@ -70,7 +70,7 @@ | |||||
if (isAuthenticator(type)) { | if (isAuthenticator(type)) { | ||||
console.log('submitVerification: sending authenticator code: '+code); | console.log('submitVerification: sending authenticator code: '+code); | ||||
this.sendAuthenticatorCode({ | this.sendAuthenticatorCode({ | ||||
uuid: this.username, | |||||
userId: this.username, | |||||
code: code, | code: code, | ||||
verifyOnly: false, | verifyOnly: false, | ||||
messages: this.messages, | messages: this.messages, | ||||