upload new attachments
This commit is contained in:
parent
89b1639dda
commit
0574c538e7
|
@ -739,8 +739,20 @@
|
||||||
"message": "No attachments.",
|
"message": "No attachments.",
|
||||||
"description": "No attachments."
|
"description": "No attachments."
|
||||||
},
|
},
|
||||||
|
"attachmentSaved": {
|
||||||
|
"message": "The attachment has been saved.",
|
||||||
|
"description": "The attachment has been saved."
|
||||||
|
},
|
||||||
"file": {
|
"file": {
|
||||||
"message": "File",
|
"message": "File",
|
||||||
"description": "File"
|
"description": "File"
|
||||||
|
},
|
||||||
|
"selectFile": {
|
||||||
|
"message": "Select a file.",
|
||||||
|
"description": "Select a file."
|
||||||
|
},
|
||||||
|
"fileTooLarge": {
|
||||||
|
"message": "Maximum file size is 100 MB.",
|
||||||
|
"description": "Maximum file size is 100 MB."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,4 +60,4 @@ var AttachmentData = function (response) {
|
||||||
this.fileName = response.fileName;
|
this.fileName = response.fileName;
|
||||||
this.size = response.size;
|
this.size = response.size;
|
||||||
this.sizeName = response.sizeName;
|
this.sizeName = response.sizeName;
|
||||||
};
|
};
|
||||||
|
|
|
@ -145,7 +145,7 @@
|
||||||
templateUrl: 'app/vault/views/vaultAttachments.html',
|
templateUrl: 'app/vault/views/vaultAttachments.html',
|
||||||
controller: 'vaultAttachmentsController',
|
controller: 'vaultAttachmentsController',
|
||||||
data: { authorize: true },
|
data: { authorize: true },
|
||||||
params: { animation: null, fromView: true, login: null, from: 'vault' }
|
params: { animation: null, fromView: true, from: 'vault' }
|
||||||
})
|
})
|
||||||
|
|
||||||
.state('passwordGenerator', {
|
.state('passwordGenerator', {
|
||||||
|
|
|
@ -1,15 +1,44 @@
|
||||||
angular
|
angular
|
||||||
.module('bit.vault')
|
.module('bit.vault')
|
||||||
|
|
||||||
.controller('vaultAttachmentsController', function ($scope, $state, $stateParams, loginService, folderService,
|
.controller('vaultAttachmentsController', function ($scope, $state, $stateParams, loginService, $q, toastr,
|
||||||
cryptoService, $q, toastr, SweetAlert, utilsService, $analytics, i18nService) {
|
SweetAlert, utilsService, $analytics, i18nService) {
|
||||||
$scope.i18n = i18nService;
|
$scope.i18n = i18nService;
|
||||||
$scope.login = $stateParams.login;
|
|
||||||
utilsService.initListSectionItemListeners($(document), angular);
|
utilsService.initListSectionItemListeners($(document), angular);
|
||||||
|
|
||||||
|
loginService.get($stateParams.id, function (login) {
|
||||||
|
$q.when(login.decrypt()).then(function (model) {
|
||||||
|
$scope.login = model;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$scope.submitPromise = null;
|
$scope.submitPromise = null;
|
||||||
$scope.submit = function () {
|
$scope.submit = function () {
|
||||||
$scope.close(true);
|
var files = document.getElementById('file').files;
|
||||||
|
if (!files || !files.length) {
|
||||||
|
toastr.error(i18nService.selectFile, i18nService.errorsOccurred);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files[0].size > 104857600) { // 100 MB
|
||||||
|
toastr.error(i18nService.fileTooLarge, i18nService.errorsOccurred);
|
||||||
|
return deferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.submitPromise = $q.when(loginService.saveAttachmentWithServer($scope.login, files[0])).then(function (login) {
|
||||||
|
$q.when(login.decrypt()).then(function (model) {
|
||||||
|
$scope.login = model;
|
||||||
|
});
|
||||||
|
$analytics.eventTrack('Added Attachment');
|
||||||
|
toastr.success(i18nService.attachmentSaved);
|
||||||
|
}, function (err) {
|
||||||
|
if (err) {
|
||||||
|
toastr.error(err);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toastr.error(i18nService.errorsOccurred);
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.delete = function (attachment) {
|
$scope.delete = function (attachment) {
|
||||||
|
@ -22,6 +51,10 @@ angular
|
||||||
}, function (confirmed) {
|
}, function (confirmed) {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
$q.when(loginService.deleteAttachmentWithServer($stateParams.id, attachment.id)).then(function () {
|
$q.when(loginService.deleteAttachmentWithServer($stateParams.id, attachment.id)).then(function () {
|
||||||
|
var index = $scope.login.attachments.indexOf(attachment);
|
||||||
|
if (index > -1) {
|
||||||
|
$scope.login.attachments.splice(index, 1);
|
||||||
|
}
|
||||||
$analytics.eventTrack('Deleted Attachment');
|
$analytics.eventTrack('Deleted Attachment');
|
||||||
toastr.success(i18nService.deletedAttachment);
|
toastr.success(i18nService.deletedAttachment);
|
||||||
});
|
});
|
||||||
|
@ -29,29 +62,14 @@ angular
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.close = function (allOut) {
|
$scope.close = function () {
|
||||||
if (!allOut) {
|
$state.go('editLogin', {
|
||||||
$state.go('editLogin', {
|
loginId: $stateParams.id,
|
||||||
loginId: $stateParams.id,
|
animation: 'out-slide-down',
|
||||||
animation: 'out-slide-down',
|
from: $stateParams.from,
|
||||||
from: $stateParams.from,
|
fromView: $stateParams.fromView
|
||||||
fromView: $stateParams.fromView
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if ($stateParams.fromView) {
|
|
||||||
$state.go('viewLogin', {
|
|
||||||
loginId: $stateParams.id,
|
|
||||||
animation: 'out-slide-down',
|
|
||||||
from: $stateParams.from
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$state.go('tabs.vault', {
|
|
||||||
animation: 'out-slide-down'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -71,7 +71,6 @@ angular
|
||||||
$scope.attachments = function () {
|
$scope.attachments = function () {
|
||||||
$state.go('attachments', {
|
$state.go('attachments', {
|
||||||
id: loginId,
|
id: loginId,
|
||||||
login: $scope.login,
|
|
||||||
animation: 'in-slide-up',
|
animation: 'in-slide-up',
|
||||||
from: from,
|
from: from,
|
||||||
fromView: fromView
|
fromView: fromView
|
||||||
|
|
|
@ -1,40 +1,43 @@
|
||||||
<div class="header">
|
<form name="theForm" ng-submit="submit()" bit-form="submitPromise">
|
||||||
<div class="left">
|
<div class="header">
|
||||||
<a href="" ng-click="close()">{{i18n.close}}</a>
|
<div class="left">
|
||||||
</div>
|
<a href="" ng-click="close()">{{i18n.close}}</a>
|
||||||
<div class="right">
|
|
||||||
<a href="" ng-click="submit()">{{i18n.submit}}</a>
|
|
||||||
</div>
|
|
||||||
<div class="title">{{i18n.attachments}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<div class="list list-no-selection">
|
|
||||||
<div class="list-section">
|
|
||||||
<div class="list-section-items">
|
|
||||||
<div class="list-section-item" ng-if="!login.attachments.length">
|
|
||||||
{{i18n.noAttachments}}
|
|
||||||
</div>
|
|
||||||
<div class="list-section-item" ng-repeat="attachment in login.attachments">
|
|
||||||
<a href="#" stop-click ng-click="download(attachment)">{{attachment.fileName}}</a>
|
|
||||||
<i class="fa fa-spin fa-spinner text-muted no-animation" ng-if="attachment.downloading"></i>
|
|
||||||
<span class="btn-list no-padding" stop-prop stop-click title="{{i18n.deleteAttachment}}"
|
|
||||||
ng-click="delete(attachment)">
|
|
||||||
<i class="fa fa-lg fa-trash"></i>
|
|
||||||
</span>
|
|
||||||
<small class="item-sub-label">{{attachment.sizeName}}</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section">
|
<div class="right">
|
||||||
<div class="list-section-header">
|
<button type="submit" class="btn btn-link" ng-show="!theForm.$loading">{{i18n.submit}}</button>
|
||||||
{{i18n.newAttachment}}
|
<i class="fa fa-spinner fa-lg fa-spin no-animation" ng-show="theForm.$loading"></i>
|
||||||
|
</div>
|
||||||
|
<div class="title">{{i18n.attachments}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="list list-no-selection">
|
||||||
|
<div class="list-section">
|
||||||
|
<div class="list-section-items">
|
||||||
|
<div class="list-section-item" ng-if="!login.attachments.length">
|
||||||
|
{{i18n.noAttachments}}
|
||||||
|
</div>
|
||||||
|
<div class="list-section-item" ng-repeat="attachment in login.attachments">
|
||||||
|
<a href="#" stop-click ng-click="download(attachment)">{{attachment.fileName}}</a>
|
||||||
|
<i class="fa fa-spin fa-spinner text-muted no-animation" ng-if="attachment.downloading"></i>
|
||||||
|
<span class="btn-list no-padding" stop-prop stop-click title="{{i18n.deleteAttachment}}"
|
||||||
|
ng-click="delete(attachment)">
|
||||||
|
<i class="fa fa-lg fa-trash"></i>
|
||||||
|
</span>
|
||||||
|
<small class="item-sub-label">{{attachment.sizeName}}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-section-items">
|
<div class="list-section">
|
||||||
<div class="list-section-item">
|
<div class="list-section-header">
|
||||||
<label for="file" class="item-label">{{i18n.file}}</label>
|
{{i18n.newAttachment}}
|
||||||
<input type="file" id="file" name="file" />
|
</div>
|
||||||
|
<div class="list-section-items">
|
||||||
|
<div class="list-section-item">
|
||||||
|
<label for="file" class="item-label">{{i18n.file}}</label>
|
||||||
|
<input type="file" id="file" name="file" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|
|
@ -403,6 +403,47 @@ function initApiService() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ApiService.prototype.postCipherAttachment = function (id, formData, success, error) {
|
||||||
|
var self = this;
|
||||||
|
handleTokenState(self).then(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: self.baseUrl + '/ciphers/' + id + '/attachment?' + token,
|
||||||
|
data: formData,
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (response) {
|
||||||
|
success(new CipherResponse(response));
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, false, self);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function (jqXHR) {
|
||||||
|
handleError(error, jqXHR, true, self);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
ApiService.prototype.deleteCipherAttachment = function (id, attachmentId, success, error) {
|
||||||
|
var self = this;
|
||||||
|
handleTokenState(self).then(function (token) {
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: self.baseUrl + '/ciphers/' + id + '/attachment/' + attachmentId + '/delete?' + token,
|
||||||
|
dataType: 'text',
|
||||||
|
success: function (response) {
|
||||||
|
success();
|
||||||
|
},
|
||||||
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
handleError(error, jqXHR, false, self);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, function (jqXHR) {
|
||||||
|
handleError(error, jqXHR, true, self);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
function handleError(errorCallback, jqXHR, tokenError, self) {
|
function handleError(errorCallback, jqXHR, tokenError, self) {
|
||||||
|
|
|
@ -322,7 +322,7 @@ function initLoginService() {
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
handleError(response, deferred)
|
handleError(response, deferred);
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
|
@ -354,6 +354,93 @@ function initLoginService() {
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
LoginService.prototype.saveAttachmentWithServer = function (login, unencryptedFile) {
|
||||||
|
var deferred = Q.defer();
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var key, encFileName;
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.readAsArrayBuffer(unencryptedFile);
|
||||||
|
reader.onload = function (evt) {
|
||||||
|
self.cryptoService.getOrgKey(login.organizationId).then(function (theKey) {
|
||||||
|
key = theKey;
|
||||||
|
return self.cryptoService.encrypt(unencryptedFile.name, key);
|
||||||
|
}).then(function (fileName) {
|
||||||
|
encFileName = fileName;
|
||||||
|
return self.cryptoService.encryptToBytes(evt.target.result, key);
|
||||||
|
}).then(function (encData) {
|
||||||
|
var fd = new FormData();
|
||||||
|
var blob = new Blob([encData], { type: 'application/octet-stream' });
|
||||||
|
fd.append('data', blob, encFileName.encryptedString);
|
||||||
|
|
||||||
|
self.apiService.postCipherAttachment(login.id, fd,
|
||||||
|
function (response) {
|
||||||
|
self.userService.getUserId(function (userId) {
|
||||||
|
var data = new LoginData(response, userId);
|
||||||
|
self.upsert(data, function () {
|
||||||
|
deferred.resolve(new Login(data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function (response) {
|
||||||
|
handleError(response, deferred);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
reader.onerror = function (evt) {
|
||||||
|
deferred.reject('Error reading file.');
|
||||||
|
};
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
|
LoginService.prototype.deleteAttachment = function (id, attachmentId, callback) {
|
||||||
|
if (!callback || typeof callback !== 'function') {
|
||||||
|
throw 'callback function required';
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.userService.getUserId(function (userId) {
|
||||||
|
var loginsKey = 'sites_' + userId;
|
||||||
|
|
||||||
|
chrome.storage.local.get(loginsKey, function (obj) {
|
||||||
|
var logins = obj[loginsKey];
|
||||||
|
if (logins && id in logins && logins[id].attachments) {
|
||||||
|
for (var i = 0; i < logins[id].attachments.length; i++) {
|
||||||
|
if (logins[id].attachments[i].id === attachmentId) {
|
||||||
|
logins[id].attachments.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj[loginsKey] = logins;
|
||||||
|
chrome.storage.local.set(obj, function () {
|
||||||
|
self.decryptedLoginCache = null;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
LoginService.prototype.deleteAttachmentWithServer = function (id, attachmentId) {
|
||||||
|
var deferred = Q.defer();
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
self.apiService.deleteCipherAttachment(id, attachmentId, function () {
|
||||||
|
self.deleteAttachment(id, attachmentId, function () {
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
}, function (response) {
|
||||||
|
handleError(response, deferred);
|
||||||
|
});
|
||||||
|
|
||||||
|
return deferred.promise;
|
||||||
|
};
|
||||||
|
|
||||||
function handleError(error, deferred) {
|
function handleError(error, deferred) {
|
||||||
deferred.reject(error);
|
deferred.reject(error);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue