kris/download_account
içindeki 22 işleme master
ile birleştirdi
@@ -69,6 +69,7 @@ export const router = new Router({ | |||
{ path: '/me', component: ProfilePage }, | |||
{ path: '/me/policy', component: PolicyPage }, | |||
{ path: '/me/download/:uuid', redirect: r => ({ path: '/me/policy', query: { download: r.params.uuid } }) }, | |||
{ path: '/me/action', component: ActionPage }, | |||
{ path: '/me/changePassword', component: ChangePasswordPage }, | |||
{ path: '/me/setPassword/:code', component: SetPasswordPage }, | |||
@@ -155,18 +155,20 @@ export const util = { | |||
return function (response) { | |||
return response.text().then(text => { | |||
if (!response.ok) { | |||
let errData = JSON.parse('' + text) || text; | |||
if (Array.isArray(errData)) errData = errData[0]; | |||
if (response.status === 404) { | |||
// todo: show nicer error message | |||
const errData = JSON.parse(''+text); | |||
console.log('handleCrudResponse: received 404: ' + text); | |||
console.log('handlePlaintextResponse: received 404: ' + (errData.resource || errData)); | |||
} else if (response.status === 422) { | |||
const errData = JSON.parse(''+text); | |||
console.log('handleCrudResponse: received 422, error: ' + text); | |||
console.log('handlePlaintextResponse: received 422, error: ' | |||
+ ((errData.message + ": " + errData.invalidValue) || errData)); | |||
util.setValidationErrors(errData, messages, errors); | |||
} | |||
const error = (data && data.message) || response.statusText; | |||
const error = errData.message || errData || response.statusText; | |||
return Promise.reject(error); | |||
} | |||
return text; | |||
@@ -174,6 +176,28 @@ export const util = { | |||
} | |||
}, | |||
handleDataToDownloadAsFile: function(fileName, mimeType) { | |||
return function(data) { | |||
// Original taken from: https://javascript.info/blob#blob-as-url | |||
const uri = URL.createObjectURL(new Blob([data], {type: mimeType})); | |||
try { | |||
util.downloadURI(uri, fileName); | |||
} catch(err) { | |||
return Promise.reject(err); | |||
} finally { | |||
URL.revokeObjectURL(uri); | |||
} | |||
return 'ok'; | |||
}; | |||
}, | |||
downloadURI: function(uri, name) { | |||
const link = document.createElement("a"); | |||
link.download = name; | |||
link.href = uri; | |||
link.click(); | |||
}, | |||
setValidationErrors: function(data, messages, errors, enableTotpModal) { | |||
const errs = []; | |||
for (let i=0; i<data.length; i++) { | |||
@@ -12,6 +12,8 @@ export const userService = { | |||
register, | |||
searchAccounts, | |||
getMe, | |||
requestAccountDownload, | |||
downloadAccount, | |||
getUserById, | |||
getPolicyByUserId, | |||
updatePolicyByUserId, | |||
@@ -91,6 +93,26 @@ function getMe(messages, errors) { | |||
).then(util.handleCrudResponse(messages, errors)); | |||
} | |||
function requestAccountDownload(messages, errors) { | |||
return fetch(`${config.apiUrl}/me/download`, util.postWithAuth()).then(util.handlePlaintextResponse(messages, errors)); | |||
} | |||
function downloadAccount(token, messages, errors) { | |||
const fileName = (util.currentUser().name || util.currentUser().uuid || token) + ".json"; | |||
return fetch(`${config.apiUrl}/me/download/${token}`, util.postWithAuth()) | |||
.then(util.handlePlaintextResponse(messages, errors)) | |||
.then(text => { | |||
try { | |||
return JSON.stringify(JSON.parse(text, (_, v) => typeof v === 'string' ? (JSON.parse(v) || v) : v), | |||
null, '\t'); | |||
} catch(err) { | |||
Promise.reject(text || err); | |||
} | |||
}) | |||
.then(util.handleDataToDownloadAsFile(fileName, 'application/json')); | |||
} | |||
function getUserById(userId, messages, errors) { | |||
return fetch(`${config.apiUrl}/users/${userId}`, util.getWithAuth()).then(util.handleCrudResponse(messages, errors)); | |||
} | |||
@@ -227,4 +249,4 @@ function handleAuthResponse(messages, errors) { | |||
return data; | |||
}); | |||
}; | |||
} | |||
} |
@@ -19,7 +19,9 @@ const defaultStatus = { | |||
authenticating: false, | |||
sendingVerification: false, | |||
sendingResetPasswordMessage: false, | |||
registrationError: null | |||
registrationError: null, | |||
requestAccountDownloadRequestSent: false, | |||
downloadingAccount: false | |||
}; | |||
const state = { | |||
@@ -179,6 +181,18 @@ const actions = { | |||
}, | |||
error => commit('resendVerificationCodeFailure', error) | |||
); | |||
}, | |||
requestAccountDownload({ commit }, {messages, errors}) { | |||
commit('requestAccountDownloadRequest'); | |||
userService.requestAccountDownload(messages, errors) | |||
.then(ok => commit('requestAccountDownloadSuccess'), | |||
error => commit('requestAccountDownloadFailure', error)); | |||
}, | |||
downloadAccount({ commit }, {token, messages, errors}) { | |||
commit('downloadAccountRequest'); | |||
userService.downloadAccount(token, messages, errors) | |||
.then(ok => commit('downloadAccountSuccess'), | |||
error => commit('downloadAccountFailure', error)); | |||
} | |||
}; | |||
@@ -367,6 +381,33 @@ const mutations = { | |||
resendVerificationCodeFailure(state, error) { | |||
state.status = Object.assign({}, state.status, {sendingVerification: false}); | |||
state.actionStatus = { error: error, type: 'verify' }; | |||
}, | |||
requestAccountDownloadRequest(state) { | |||
state.status = Object.assign({}, state.status, | |||
{ downloadingAccount: false, requestAccountDownloadRequestSent: false }); | |||
state.actionStatus = { requesting: true, type: 'requestDownload' }; | |||
}, | |||
requestAccountDownloadSuccess(state) { | |||
state.status = Object.assign({}, state.status, { requestAccountDownloadRequestSent: true }); | |||
state.actionStatus = { success: true, type: 'requestDownload' }; | |||
}, | |||
requestAccountDownloadFailure(state, error) { | |||
state.actionStatus = { error: error, type: 'requestDownload' }; | |||
console.log('requestAccountDownloadFailure: ' + JSON.stringify(error)); | |||
}, | |||
downloadAccountRequest(state) { | |||
state.status = Object.assign({}, state.status, { downloadingAccount: true }); | |||
state.actionStatus = { requesting: true, type: 'download' }; | |||
}, | |||
downloadAccountSuccess(state) { | |||
state.status = Object.assign({}, state.status, { downloadingAccount: false }); | |||
state.actionStatus = { success: true, type: 'download' }; | |||
}, | |||
downloadAccountFailure(state, error) { | |||
state.actionStatus = { error: error, type: 'download' }; | |||
console.log('downloadAccountFailure: ' + JSON.stringify(error)); | |||
} | |||
}; | |||
@@ -34,7 +34,7 @@ | |||
<hr/> | |||
<button v-if="vpnConfBase64" @click="downloadURI('data:text/plain;base64,'+vpnConfBase64, 'vpn.conf')">{{messages.message_device_vpn_download_conf}}</button> | |||
<button v-if="vpnConfBase64" @click="util.downloadURI('data:text/plain;base64,'+vpnConfBase64, 'vpn.conf')">{{messages.message_device_vpn_download_conf}}</button> | |||
<div v-if="errors.has('deviceVpnConf')" class="invalid-feedback d-block">{{ errors.first('deviceVpnConf') }}</div> | |||
<hr/> | |||
@@ -225,14 +225,6 @@ | |||
}, | |||
hideDeviceHelp () { this.displayDeviceHelp = {}; }, | |||
downloadURI(uri, name) { // adapted from https://stackoverflow.com/a/15832662/1251543 | |||
let link = document.createElement("a"); | |||
link.download = name; | |||
link.href = uri; | |||
document.body.appendChild(link); | |||
link.click(); | |||
document.body.removeChild(link); | |||
}, | |||
mitmOn () { | |||
this.mitmLoading = true; | |||
this.errors.clear(); | |||
@@ -1,6 +1,14 @@ | |||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | |||
<template> | |||
<div> | |||
<div v-if="showDownloadMessages && status.downloadingAccount"> | |||
<div v-if="actionStatus.requesting" :class="`alert alert-info`">{{ messages.downloading_notice }}</div> | |||
<div v-if="!actionStatus.requesting && actionStatus.type === 'download' && actionStatus.error" | |||
class="invalid-feedback d-block alert alert-danger"> | |||
{{ messages.downloading_failed }} | |||
</div> | |||
</div> | |||
<div v-if="inboundAction" :class="`alert ${inboundAction.alertType}`"> | |||
{{messages['message_inbound_'+inboundAction.actionType]}} | |||
{{messages['message_inbound_'+inboundAction.status]}} | |||
@@ -10,6 +18,27 @@ | |||
<h2>{{messages.form_title_account_policy}}<span v-if="this.me === false"> - {{this.userId}}</span></h2> | |||
<form @submit.prevent="updatePolicy"> | |||
<span v-if="me && currentUser.name != 'root'"> | |||
<hr/> | |||
<div class="form-group"> | |||
<label htmlFor="downloadAccountBtn">{{messages.field_label_account_download}}</label> | |||
<button class="btn btn-primary" name="downloadAccountBtn" :disabled="actionStatus.requesting" | |||
v-on:click="clickRequestAccountDownload()"> | |||
{{ messages.button_label_account_download }} | |||
</button> | |||
<img v-show="loading()" :src="loadingImgSrc" /> | |||
<div v-if="showDownloadMessages && !actionStatus.requesting"> | |||
<div v-if="actionStatus.type === 'requestDownload' && actionStatus.error" | |||
class="invalid-feedback d-block alert alert-danger"> | |||
{{ actionStatus.error }} | |||
</div> | |||
<div v-if="status.requestAccountDownloadRequestSent" class="alert alert-info"> | |||
{{messages.field_label_account_download_requested_notice}} | |||
</div> | |||
</div> | |||
</div> | |||
</span> | |||
<hr/> | |||
<div class="form-group"> | |||
<label htmlFor="deletionPolicy">{{messages.field_label_policy_account_deletion}}</label> | |||
@@ -388,11 +417,12 @@ | |||
verifyingContact: null, | |||
inboundAction: null, | |||
watchedPolicy: null, | |||
showDownloadMessages: false, | |||
loadingImgSrc: loadingImgSrc | |||
} | |||
}, | |||
computed: { | |||
...mapState('account', ['actionStatus']), | |||
...mapState('account', ['actionStatus', 'status']), | |||
...mapState('system', [ | |||
'messages', 'accountDeletionOptions', 'timeDurationOptions', 'timeDurationOptionsReversed', | |||
'contactTypes', 'detectedLocale', 'countries' | |||
@@ -427,9 +457,12 @@ | |||
} | |||
}, | |||
methods: { | |||
...mapActions('account', ['approveAction', 'denyAction', 'sendAuthenticatorCode', 'resendVerificationCode']), | |||
...mapActions('account', [ | |||
'approveAction', 'denyAction', 'sendAuthenticatorCode', 'resendVerificationCode', | |||
'requestAccountDownload', 'downloadAccount' | |||
]), | |||
...mapActions('users', [ | |||
'getPolicyByUserId', 'updatePolicyByUserId', 'addPolicyContactByUserId', 'removePolicyContactByUserId', | |||
'getPolicyByUserId', 'updatePolicyByUserId', 'addPolicyContactByUserId', 'removePolicyContactByUserId' | |||
]), | |||
...mapGetters('users', ['loading']), | |||
isAuthenticator(val) { return window.isAuthenticator(val); }, | |||
@@ -521,6 +554,12 @@ | |||
errors: this.errors | |||
}); | |||
}, | |||
clickRequestAccountDownload() { | |||
this.errors.clear(); | |||
this.showDownloadMessages = true; | |||
this.requestAccountDownload({ messages: this.messages, errors: this.errors }); | |||
return false; // do not follow the click | |||
}, | |||
startVerifyContact(contact) { | |||
// console.log('startVerifyContact: '+JSON.stringify(contact)); | |||
this.verifyingContact = contact.uuid; | |||
@@ -654,6 +693,14 @@ | |||
// console.log('PolicyPage.created: $route.params='+JSON.stringify(this.$route.query)); | |||
this.inboundAction = util.setInboundAction(this.$route); | |||
this.newContactSmsCountry = countryFromLocale(this.detectedLocale); | |||
this.showDownloadMessages = false; | |||
if (this.$route.query.hasOwnProperty('download')) { | |||
this.showDownloadMessages = true; | |||
this.downloadAccount({ | |||
token: this.$route.query.download, messages: this.messages, errors: this.errors | |||
}); | |||
} | |||
} | |||
}; | |||
</script> | |||
</script> |