From 9b654bb11c2648c38ecf41f5a40777fa071bbf69 Mon Sep 17 00:00:00 2001 From: "clairebontempo@gmail.com" Date: Thu, 8 Dec 2022 14:26:27 -0800 Subject: [PATCH 1/8] rename download service file --- ui/app/services/{download-csv.js => download.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/app/services/{download-csv.js => download.js} (100%) diff --git a/ui/app/services/download-csv.js b/ui/app/services/download.js similarity index 100% rename from ui/app/services/download-csv.js rename to ui/app/services/download.js From 5d0a20e2fa31f732f19d3af581150461a819c764 Mon Sep 17 00:00:00 2001 From: "clairebontempo@gmail.com" Date: Thu, 8 Dec 2022 14:34:56 -0800 Subject: [PATCH 2/8] rename file again --- ui/app/services/{download.js => download-file.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/app/services/{download.js => download-file.js} (100%) diff --git a/ui/app/services/download.js b/ui/app/services/download-file.js similarity index 100% rename from ui/app/services/download.js rename to ui/app/services/download-file.js From b727d121c785aa441f1cfc0279235c525730eced Mon Sep 17 00:00:00 2001 From: "clairebontempo@gmail.com" Date: Thu, 8 Dec 2022 16:56:12 -0800 Subject: [PATCH 3/8] add download-file service to engine --- ui/app/app.js | 3 ++- ui/app/templates/components/toolbar-download-button.hbs | 2 -- ui/lib/pki/addon/engine.js | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 ui/app/templates/components/toolbar-download-button.hbs diff --git a/ui/app/app.js b/ui/app/app.js index 1f17e4ce6672..487d39573a2b 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -52,14 +52,15 @@ export default class App extends Application { dependencies: { services: [ 'auth', + 'download-file', 'flash-messages', 'namespace', 'path-help', 'router', + 'secret-mount-path', 'store', 'version', 'wizard', - 'secret-mount-path', ], externalRoutes: { secrets: 'vault.cluster.secrets.backends', diff --git a/ui/app/templates/components/toolbar-download-button.hbs b/ui/app/templates/components/toolbar-download-button.hbs deleted file mode 100644 index b7fd647a7d5b..000000000000 --- a/ui/app/templates/components/toolbar-download-button.hbs +++ /dev/null @@ -1,2 +0,0 @@ -{{@actionText}} - \ No newline at end of file diff --git a/ui/lib/pki/addon/engine.js b/ui/lib/pki/addon/engine.js index ad42b0c9dddc..b9f440fa89e2 100644 --- a/ui/lib/pki/addon/engine.js +++ b/ui/lib/pki/addon/engine.js @@ -13,14 +13,15 @@ export default class PkiEngine extends Engine { dependencies = { services: [ 'auth', + 'download-file', 'flash-messages', 'namespace', 'path-help', 'router', + 'secret-mount-path', 'store', 'version', 'wizard', - 'secret-mount-path', ], externalRoutes: ['secrets'], }; From d7b91ff332433b53c7f3532d129018fbb327ca0b Mon Sep 17 00:00:00 2001 From: "clairebontempo@gmail.com" Date: Wed, 14 Dec 2022 09:42:00 -0800 Subject: [PATCH 4/8] refactor download button to use service --- ui/app/app.js | 1 + ui/app/services/download.ts | 10 ++++------ .../core/addon/components/download-button.js | 19 +++++++------------ ui/lib/kmip/addon/engine.js | 1 + 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/ui/app/app.js b/ui/app/app.js index ae8e2af5b297..d0fde1b6c239 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -34,6 +34,7 @@ export default class App extends Application { dependencies: { services: [ 'auth', + 'download', 'flash-messages', 'namespace', 'path-help', diff --git a/ui/app/services/download.ts b/ui/app/services/download.ts index a608ea32c108..0672c819a685 100644 --- a/ui/app/services/download.ts +++ b/ui/app/services/download.ts @@ -1,15 +1,14 @@ import Service from '@ember/service'; export default class DownloadService extends Service { + // some browsers (ex. Firefox) require the filename has an explicit extension, always include it in the filename + download(filename: string, mimetype: string, content: string) { const { document, URL } = window; const downloadElement = document.createElement('a'); + const data = new File([content], filename, { type: mimetype }); downloadElement.download = filename; - downloadElement.href = URL.createObjectURL( - new Blob([content], { - type: mimetype, - }) - ); + downloadElement.href = URL.createObjectURL(data); document.body.appendChild(downloadElement); downloadElement.click(); URL.revokeObjectURL(downloadElement.href); @@ -22,7 +21,6 @@ export default class DownloadService extends Service { // namespacelonglonglong4/,,191,171,20\n // namespacelonglonglong4/,auth/method/uMGBU,35,20,15\n' csv(filename: string, content: string) { - // even though Blob type 'text/csv' is specified below, some browsers (ex. Firefox) require the filename has an explicit extension const formattedFilename = `${filename?.replace(/\s+/g, '-')}.csv` || 'vault-data.csv'; this.download(formattedFilename, 'text/csv', content); return formattedFilename; diff --git a/ui/lib/core/addon/components/download-button.js b/ui/lib/core/addon/components/download-button.js index 39737574f4e1..75602f56c985 100644 --- a/ui/lib/core/addon/components/download-button.js +++ b/ui/lib/core/addon/components/download-button.js @@ -1,9 +1,11 @@ -import { action } from '@ember/object'; import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; /** * @module DownloadButton * DownloadButton components are an action button used to download data. Both the action text and icon are yielded. - * + * * NOTE: when using in an engine, remember to add the 'download' service to its dependencies (in /engine.js) and map to it in /app.js + * [ember-docs](https://ember-engines.com/docs/services) * @example * ```js * Date: Wed, 14 Dec 2022 12:29:45 -0800 Subject: [PATCH 5/8] refactor download service --- ui/app/services/download.ts | 44 ++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/ui/app/services/download.ts b/ui/app/services/download.ts index 0672c819a685..ef3e74b26892 100644 --- a/ui/app/services/download.ts +++ b/ui/app/services/download.ts @@ -1,18 +1,43 @@ import Service from '@ember/service'; +interface Extensions { + csv: string; + hcl: string; + sentinel: string; + json: string; + pem: string; + txt: string; +} + +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types +const EXTENSION_TO_MIME: Extensions = { + csv: 'txt/csv', + hcl: 'text/plain', + sentinel: 'text/plain', + json: 'application/json', + pem: 'application/x-pem-file', + txt: 'text/plain', +}; + export default class DownloadService extends Service { - // some browsers (ex. Firefox) require the filename has an explicit extension, always include it in the filename + download(filename: string, extension: string, content: string) { + // append extension to filename + const formattedFilename = + `${filename?.replace(/\s+/g, '-')}.${extension}` || + `vault-data-${new Date().toISOString()}.${extension}`; + const mimetype = EXTENSION_TO_MIME[extension as keyof Extensions] || 'text/plain'; - download(filename: string, mimetype: string, content: string) { + // commence download const { document, URL } = window; const downloadElement = document.createElement('a'); - const data = new File([content], filename, { type: mimetype }); - downloadElement.download = filename; + const data = new File([content], formattedFilename, { type: mimetype }); + downloadElement.download = formattedFilename; downloadElement.href = URL.createObjectURL(data); document.body.appendChild(downloadElement); downloadElement.click(); URL.revokeObjectURL(downloadElement.href); downloadElement.remove(); + return formattedFilename; } // SAMPLE CSV FORMAT ('content' argument) @@ -20,15 +45,4 @@ export default class DownloadService extends Service { // 'Namespace path,Authentication method,Total clients,Entity clients,Non-entity clients\n // namespacelonglonglong4/,,191,171,20\n // namespacelonglonglong4/,auth/method/uMGBU,35,20,15\n' - csv(filename: string, content: string) { - const formattedFilename = `${filename?.replace(/\s+/g, '-')}.csv` || 'vault-data.csv'; - this.download(formattedFilename, 'text/csv', content); - return formattedFilename; - } - - pem(filename: string, content: string) { - const formattedFilename = `${filename?.replace(/\s+/g, '-')}.pem` || 'vault-cert.pem'; - this.download(formattedFilename, 'application/x-pem-file', content); - return formattedFilename; - } } From 7bef73f75fdb8bd71fbcc6f48712e61cf225025a Mon Sep 17 00:00:00 2001 From: "clairebontempo@gmail.com" Date: Wed, 14 Dec 2022 15:48:42 -0800 Subject: [PATCH 6/8] finish refactor download service, make arg order consistent: filename, content, extension --- ui/app/services/download.ts | 17 ++++++++-- ui/app/templates/vault/cluster/init.hbs | 3 +- .../templates/vault/cluster/policy/show.hbs | 2 +- .../core/addon/components/download-button.js | 34 ++++++++++--------- ui/lib/kmip/addon/templates/configuration.hbs | 2 +- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/ui/app/services/download.ts b/ui/app/services/download.ts index ef3e74b26892..7cfbb19b41e2 100644 --- a/ui/app/services/download.ts +++ b/ui/app/services/download.ts @@ -20,11 +20,13 @@ const EXTENSION_TO_MIME: Extensions = { }; export default class DownloadService extends Service { - download(filename: string, extension: string, content: string) { - // append extension to filename + download(filename: string, content: string, extension: string) { + // replace spaces with hyphens, append extension to filename const formattedFilename = `${filename?.replace(/\s+/g, '-')}.${extension}` || `vault-data-${new Date().toISOString()}.${extension}`; + + // map extension to MIME type or use default const mimetype = EXTENSION_TO_MIME[extension as keyof Extensions] || 'text/plain'; // commence download @@ -45,4 +47,15 @@ export default class DownloadService extends Service { // 'Namespace path,Authentication method,Total clients,Entity clients,Non-entity clients\n // namespacelonglonglong4/,,191,171,20\n // namespacelonglonglong4/,auth/method/uMGBU,35,20,15\n' + csv(filename: string, content: string) { + this.download(filename, content, 'csv'); + } + + pem(filename: string, content: string) { + this.download(filename, content, 'pem'); + } + + miscExtension(filename: string, content: string, extension: string) { + this.download(filename, content, extension); + } } diff --git a/ui/app/templates/vault/cluster/init.hbs b/ui/app/templates/vault/cluster/init.hbs index a998a3df30d0..4b15cc81edd7 100644 --- a/ui/app/templates/vault/cluster/init.hbs +++ b/ui/app/templates/vault/cluster/init.hbs @@ -91,9 +91,8 @@ {{/if}} diff --git a/ui/app/templates/vault/cluster/policy/show.hbs b/ui/app/templates/vault/cluster/policy/show.hbs index 7ea4179e246f..7606b4af9626 100644 --- a/ui/app/templates/vault/cluster/policy/show.hbs +++ b/ui/app/templates/vault/cluster/policy/show.hbs @@ -25,9 +25,9 @@ Download policy diff --git a/ui/lib/core/addon/components/download-button.js b/ui/lib/core/addon/components/download-button.js index 75602f56c985..365b7f6253de 100644 --- a/ui/lib/core/addon/components/download-button.js +++ b/ui/lib/core/addon/components/download-button.js @@ -1,6 +1,7 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; +import errorMessage from 'vault/utils/error-message'; /** * @module DownloadButton * DownloadButton components are an action button used to download data. Both the action text and icon are yielded. @@ -20,38 +21,39 @@ import { inject as service } from '@ember/service'; * Download * * ``` - * @param {string} data - data to download + * @param {string} [filename] - name of file that prefixes the ISO timestamp generated at download + * @param {string} [data] - data to download + * @param {string} [extension='txt'] - file extension, the download service uses this to determine the mimetype * @param {boolean} [stringify=false] - argument to stringify the data before passing to the File constructor - * @param {string} [filename] - name of file that prefixes the ISO timestamp generated when download - * @param {string} [mime='text/plain'] - media type to be downloaded - * @param {string} [extension='txt'] - file extension */ export default class DownloadButton extends Component { @service download; - - get extension() { - return this.args.extension || 'txt'; - } - - get mime() { - return this.args.mime || 'text/plain'; - } + @service flashMessages; get filename() { - const defaultFilename = `${new Date().toISOString()}.${this.extension}`; - return this.args.filename ? this.args.filename + '-' + defaultFilename : defaultFilename; + const timestamp = new Date().toISOString(); + return this.args.filename ? this.args.filename + '-' + timestamp : timestamp; } - get data() { + get content() { if (this.args.stringify) { return JSON.stringify(this.args.data, null, 2); } return this.args.data; } + get extension() { + return this.args.extension || 'txt'; + } + @action handleDownload() { - this.download.download(this.filename, this.mime, this.data); + try { + this.download.miscExtension(this.filename, this.content, this.extension); + this.flashMessages.info(`Downloading ${this.filename}`); + } catch (error) { + this.flashMessages.danger(errorMessage(error, 'There was a problem downloading. Please try again.')); + } } } diff --git a/ui/lib/kmip/addon/templates/configuration.hbs b/ui/lib/kmip/addon/templates/configuration.hbs index cf88f5084d08..cf134fb710dc 100644 --- a/ui/lib/kmip/addon/templates/configuration.hbs +++ b/ui/lib/kmip/addon/templates/configuration.hbs @@ -4,9 +4,9 @@ {{#if this.model}} Download CA cert From 5f9d07bed537bff4a078f97b6c479e81e831807e Mon Sep 17 00:00:00 2001 From: "clairebontempo@gmail.com" Date: Wed, 14 Dec 2022 17:18:47 -0800 Subject: [PATCH 7/8] add download button to key details --- ui/lib/pki/addon/components/page/pki-key-details.hbs | 6 ++++++ ui/lib/pki/addon/routes/keys/create.js | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/lib/pki/addon/components/page/pki-key-details.hbs b/ui/lib/pki/addon/components/page/pki-key-details.hbs index 3a85f73a6ddd..2552c91c77e5 100644 --- a/ui/lib/pki/addon/components/page/pki-key-details.hbs +++ b/ui/lib/pki/addon/components/page/pki-key-details.hbs @@ -10,6 +10,12 @@ Delete
+ {{#if @key.privateKey}} + + Download private key + + + {{/if}} Edit key diff --git a/ui/lib/pki/addon/routes/keys/create.js b/ui/lib/pki/addon/routes/keys/create.js index 94849704e2a4..6874caa48a09 100644 --- a/ui/lib/pki/addon/routes/keys/create.js +++ b/ui/lib/pki/addon/routes/keys/create.js @@ -5,7 +5,6 @@ import { withConfirmLeave } from 'core/decorators/confirm-leave'; @withConfirmLeave() export default class PkiKeysCreateRoute extends PkiKeysIndexRoute { @service store; - @service secretMountPath; model() { return this.store.createRecord('pki/key'); From 48c8c92a1a8a1f89e98f9a81a6b6025b6acb099d Mon Sep 17 00:00:00 2001 From: "clairebontempo@gmail.com" Date: Thu, 15 Dec 2022 09:42:39 -0800 Subject: [PATCH 8/8] fix flaky test? --- ui/tests/acceptance/oidc-auth-method-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/tests/acceptance/oidc-auth-method-test.js b/ui/tests/acceptance/oidc-auth-method-test.js index d9f70b2e9c2e..8e1ba6dfcd66 100644 --- a/ui/tests/acceptance/oidc-auth-method-test.js +++ b/ui/tests/acceptance/oidc-auth-method-test.js @@ -91,6 +91,7 @@ module('Acceptance | oidc auth method', function (hooks) { cancelTimers(); }, 50); await click('[data-test-auth-submit]'); + await waitUntil(() => find('.nav-user-button button')); await click('.nav-user-button button'); await click('#logout'); assert