@@ -9,7 +9,9 @@ export const networkService = { | |||
getStatusesByNetworkId, | |||
getNodesByNetworkId, | |||
stopNetwork, | |||
deleteNetwork | |||
deleteNetwork, | |||
requestNetworkKeys, | |||
retrieveNetworkKeys | |||
}; | |||
function getAllNetworks(userId, messages, errors) { | |||
@@ -46,3 +48,11 @@ function stopNetwork(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)); | |||
} | |||
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 = { | |||
loading: { | |||
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, | |||
error: null, | |||
@@ -14,7 +15,9 @@ const state = { | |||
nearestRegions: null, | |||
newNodeNotification: null, | |||
networkStatuses: {}, | |||
networkNodes: null | |||
networkNodes: null, | |||
networkKeysRequested: null, | |||
networkKeys: null | |||
}; | |||
const actions = { | |||
@@ -70,7 +73,7 @@ const actions = { | |||
}, | |||
stopNetwork({ commit }, {userId, networkId, messages, errors}) { | |||
commit('stopNetworkRequest', id); | |||
commit('stopNetworkRequest', networkId); | |||
networkService.stopNetwork(userId, networkId, messages, errors) | |||
.then( | |||
network => commit('stopNetworkSuccess', network), | |||
@@ -79,7 +82,7 @@ const actions = { | |||
}, | |||
deleteNetwork({ commit }, {userId, networkId, messages, errors}) { | |||
commit('deleteNetworkRequest', id); | |||
commit('deleteNetworkRequest', networkId); | |||
networkService.deleteNetwork(userId, networkId, messages, errors) | |||
.then( | |||
network => commit('deleteNetworkSuccess', network), | |||
@@ -95,6 +98,24 @@ const actions = { | |||
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 = { | |||
@@ -200,6 +221,30 @@ const mutations = { | |||
getNearestRegionsFailure(state, error) { | |||
state.loading.nearestRegions = false; | |||
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"> | |||
<h4>{{network.name}}.{{network.domainName}} - <i>{{messages['msg_network_state_'+network.state]}}</i></h4> | |||
<div v-if="stats"> | |||
<!-- adapted from: https://code-boxx.com/simple-vanilla-javascript-progress-bar/ --> | |||
<div class="progress-wrap"> | |||
<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> | |||
@@ -38,6 +39,54 @@ | |||
</table> | |||
</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> | |||
</template> | |||
@@ -48,17 +97,25 @@ | |||
export default { | |||
data() { | |||
return { | |||
user: util.currentUser(), | |||
networkId: this.$route.params.id, | |||
stats: null, | |||
refresher: null | |||
refresher: null, | |||
restoreKeyCode: null, | |||
restoreKeyPassword: null | |||
}; | |||
}, | |||
computed: { | |||
...mapState('networks', ['network', 'newNodeNotification', 'networkStatuses', 'networkNodes']), | |||
...mapState('networks', [ | |||
'network', 'newNodeNotification', 'networkStatuses', 'networkNodes', 'networkKeysRequested', 'networkKeys' | |||
]), | |||
...mapState('system', ['messages']) | |||
}, | |||
methods: { | |||
...mapActions('networks', ['getNetworkById', 'deleteNetwork', 'getStatusesByNetworkId', 'getNodesByNetworkId']), | |||
...mapActions('networks', [ | |||
'getNetworkById', 'deleteNetwork', 'getStatusesByNetworkId', 'getNodesByNetworkId', | |||
'stopNetwork', 'deleteNetwork', 'requestNetworkKeys' | |||
]), | |||
...mapGetters('networks', ['loading']), | |||
refreshStatus (userId) { | |||
this.getNetworkById({userId: userId, networkId: this.networkId, messages: this.messages, errors: this.errors}); | |||
@@ -78,6 +135,44 @@ | |||
}, | |||
startStatusRefresher (user) { | |||
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 () { | |||
@@ -93,10 +188,12 @@ | |||
// console.log('watch.networkNodes: received: '+JSON.stringify(nodes)); | |||
}, | |||
networkStatuses (stats) { | |||
console.log('received stats: '+JSON.stringify(stats)); | |||
if (stats) { | |||
// adapted from: https://code-boxx.com/simple-vanilla-javascript-progress-bar/ | |||
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 | |||
if (this.stats.percent === 100) { | |||
clearInterval(this.refresher); | |||
@@ -10,27 +10,14 @@ | |||
<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_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> | |||
</thead> | |||
<tbody> | |||
<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['tz_name_'+network.timezone] || network.timezone}}</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> | |||
</tbody> | |||
</table> | |||
@@ -52,9 +39,7 @@ | |||
export default { | |||
computed: { | |||
...mapState({ | |||
networks: state => state.networks.networks | |||
}), | |||
...mapState('networks', ['networks']), | |||
...mapState('system', ['messages']) | |||
}, | |||
created () { | |||
@@ -63,6 +48,17 @@ | |||
methods: { | |||
...mapActions('networks', ['getAllNetworks', 'stopNetwork', 'deleteNetwork']), | |||
...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> |
@@ -228,7 +228,7 @@ | |||
accountPlan: { | |||
name: '', | |||
domain: '', | |||
locale: '', | |||
locale: util.currentUser().locale, | |||
timezone: '', | |||
plan: 'bubble', | |||
footprint: 'Worldwide', | |||
@@ -42,7 +42,7 @@ | |||
errors: this.errors | |||
}); | |||
} else { | |||
this.$router.push({path:'/me/profile', params: {'action': 'invalid'}}); | |||
this.$router.push({path:'/me/profile', params: {action: 'invalid'}}); | |||
} | |||
}, | |||
watch: { | |||
@@ -70,7 +70,7 @@ | |||
if (isAuthenticator(type)) { | |||
console.log('submitVerification: sending authenticator code: '+code); | |||
this.sendAuthenticatorCode({ | |||
uuid: this.username, | |||
userId: this.username, | |||
code: code, | |||
verifyOnly: false, | |||
messages: this.messages, | |||