Bläddra i källkod

Add support for downloading account data (#2)

Replace loading img src in one new place

Merge branch 'master' into kris/download_account

# Conflicts:
#	src/account/profile/PolicyPage.vue

Rename require with request

Shorthen redirect route definition

Properly show request account download error

Take just first error in case of a list

Reset download messages on page load

Reuse same download URI method

Show download account file status on page

Better indent

Use state's new property instead of status' property

Beautify JSON before downloading it as file

Force download of account data json as file

Load account data as JSON on request in background

Remove not needed status flag

Hide download account button for root and other users

Fix error and description line for new account download button

Better log line on HTTP 422 error

Fix log lines' prefix

Remove not needed comma

Ass initial support for downloading account data

Use proper available errData instead of data

Co-authored-by: Kristijan Mitrovic <kmitrovic@itekako.com>
Reviewed-on: https://git.bubblev.org/bubblev/bubble-web/pulls/2
pull/4/head
Kristijan Mitrovic 4 år sedan
committed by jonathan
förälder
incheckning
8582fc2745
6 ändrade filer med 147 tillägg och 20 borttagningar
  1. +1
    -0
      src/_helpers/router.js
  2. +29
    -5
      src/_helpers/util.js
  3. +23
    -1
      src/_services/user.service.js
  4. +42
    -1
      src/_store/account.module.js
  5. +1
    -9
      src/account/DevicesPage.vue
  6. +51
    -4
      src/account/profile/PolicyPage.vue

+ 1
- 0
src/_helpers/router.js Visa fil

@@ -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 },


+ 29
- 5
src/_helpers/util.js Visa fil

@@ -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++) {


+ 23
- 1
src/_services/user.service.js Visa fil

@@ -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;
});
};
}
}

+ 42
- 1
src/_store/account.module.js Visa fil

@@ -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));
}
};



+ 1
- 9
src/account/DevicesPage.vue Visa fil

@@ -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();


+ 51
- 4
src/account/profile/PolicyPage.vue Visa fil

@@ -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>

Laddar…
Avbryt
Spara