diff --git a/ui/app/adapters/config-ui/message.js b/ui/app/adapters/config-ui/message.js
index eb5fbd3b96c2..6aa3afbed2a5 100644
--- a/ui/app/adapters/config-ui/message.js
+++ b/ui/app/adapters/config-ui/message.js
@@ -14,4 +14,14 @@ export default class MessageAdapter extends ApplicationAdapter {
const { authenticated } = query;
return super.query(store, type, { authenticated, list: true });
}
+
+ queryRecord(store, type, id) {
+ return this.ajax(`${this.buildURL(type)}/${id}`, 'GET');
+ }
+
+ updateRecord(store, type, snapshot) {
+ return this.ajax(`${this.buildURL(type)}/${snapshot.record.id}`, 'POST', {
+ data: this.serialize(snapshot.record),
+ });
+ }
}
diff --git a/ui/app/models/config-ui/message.js b/ui/app/models/config-ui/message.js
index 6bff9d42752d..5f01062b357f 100644
--- a/ui/app/models/config-ui/message.js
+++ b/ui/app/models/config-ui/message.js
@@ -4,8 +4,7 @@
*/
import Model, { attr } from '@ember-data/model';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
-import { isAfter, format, addDays, startOfDay } from 'date-fns';
-import { datetimeLocalStringFormat, parseAPITimestamp } from 'core/utils/date-formatters';
+import { isAfter, addDays, startOfDay } from 'date-fns';
import { withModelValidations } from 'vault/decorators/model-validations';
import { withFormFields } from 'vault/decorators/model-form-fields';
@@ -77,7 +76,7 @@ export default class MessageModel extends Model {
editType: 'dateTimeLocal',
label: 'Message starts',
subText: 'Defaults to 12:00 a.m. the following day (local timezone).',
- defaultValue: format(addDays(startOfDay(new Date() || this.startTime), 1), datetimeLocalStringFormat),
+ defaultValue: addDays(startOfDay(new Date() || this.startTime), 1).toISOString(),
})
startTime;
@attr('date', { editType: 'yield', label: 'Message expires' }) endTime;
@@ -90,7 +89,7 @@ export default class MessageModel extends Model {
// date helpers
get isStartTimeAfterToday() {
- return isAfter(parseAPITimestamp(this.startTime), new Date());
+ return isAfter(this.startTime, new Date());
}
// capabilities
diff --git a/ui/app/serializers/config-ui/message.js b/ui/app/serializers/config-ui/message.js
index 9bf65ff062da..2635175b4e80 100644
--- a/ui/app/serializers/config-ui/message.js
+++ b/ui/app/serializers/config-ui/message.js
@@ -3,23 +3,42 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import { encodeString } from 'core/utils/b64';
+import { decodeString, encodeString } from 'core/utils/b64';
import ApplicationSerializer from '../application';
export default class MessageSerializer extends ApplicationSerializer {
- primaryKey = 'id';
+ attrs = {
+ link: { serialize: false },
+ active: { serialize: false },
+ };
- serialize() {
+ normalizeResponse(store, primaryModelClass, payload, id, requestType) {
+ if (requestType === 'queryRecord') {
+ const transformed = {
+ ...payload.data,
+ message: decodeString(payload.data.message),
+ link_title: payload.data.link.title,
+ link_href: payload.data.link.href,
+ };
+ delete transformed.link;
+ return super.normalizeResponse(store, primaryModelClass, transformed, id, requestType);
+ }
+ return super.normalizeResponse(store, primaryModelClass, payload, id, requestType);
+ }
+
+ serialize(snapshot) {
const json = super.serialize(...arguments);
json.message = encodeString(json.message);
json.link = {
- title: json.link_title,
- href: json.link_href,
+ title: json?.link_title || '',
+ href: json?.link_href || '',
};
-
- delete json.link_title;
- delete json.link_href;
-
+ // using the snapshot startTime and endTime since the json start and end times are null when
+ // it gets to the serialize function.
+ json.start_time = snapshot.record.startTime;
+ json.end_time = snapshot.record.endTime;
+ delete json?.link_title;
+ delete json?.link_href;
return json;
}
diff --git a/ui/app/styles/helper-classes/layout.scss b/ui/app/styles/helper-classes/layout.scss
index 83f778ef3ccf..60c85718f600 100644
--- a/ui/app/styles/helper-classes/layout.scss
+++ b/ui/app/styles/helper-classes/layout.scss
@@ -50,6 +50,11 @@
visibility: hidden;
}
+// overflow
+.is-overflow-hidden {
+ overflow: hidden;
+}
+
// width and height
.is-fullwidth {
width: 100%;
@@ -59,6 +64,10 @@
width: 75%;
}
+.is-two-thirds-width {
+ width: 66%;
+}
+
.is-auto-width {
width: auto;
}
@@ -75,6 +84,10 @@
height: 125px;
}
+.is-calc-large-height {
+ height: calc($desktop * 0.66);
+}
+
// float
.is-pulled-left {
float: left !important;
diff --git a/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs b/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs
index 39e6f0f61f58..1a734e5af5aa 100644
--- a/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs
+++ b/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.hbs
@@ -5,7 +5,7 @@
\ No newline at end of file
+
+
+{{#if this.showMessagePreviewModal}}
+ {{#if (eq @message.type "modal")}}
+
+
+ {{@message.title}}
+
+
+ {{@message.message}}
+ {{#if @message.linkHref}}
+
+ {{@message.linkTitle}}
+
+ {{/if}}
+
+
+
+
+
+ {{else}}
+
+ {{/if}}
+{{/if}}
\ No newline at end of file
diff --git a/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.js b/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.js
index 49cf1089c476..2bfa880594a9 100644
--- a/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.js
+++ b/ui/lib/config-ui/addon/components/messages/page/create-and-edit-message-form.js
@@ -21,11 +21,13 @@ import { inject as service } from '@ember/service';
export default class MessagesList extends Component {
@service router;
+ @service store;
@service flashMessages;
@tracked errorBanner = '';
@tracked modelValidations;
@tracked invalidFormMessage;
+ @tracked showMessagePreviewModal = false;
willDestroy() {
super.willDestroy();
@@ -36,15 +38,6 @@ export default class MessagesList extends Component {
}
}
- get breadcrumbs() {
- const authenticated =
- this.args.message.authenticated === undefined ? true : this.args.message.authenticated;
- return [
- { label: 'Messages', route: 'messages.index', query: { authenticated } },
- { label: 'Create Message' },
- ];
- }
-
@task
*save(event) {
event.preventDefault();
@@ -55,17 +48,9 @@ export default class MessagesList extends Component {
if (isValid) {
const { isNew } = this.args.message;
-
- // We do these checks here since there could be a scenario where startTime and endTime are strings.
- // The model expects these attrs to be a date object, so we will need to update these attrs to be in
- // date object format.
- if (typeof this.args.message.startTime === 'string')
- this.args.message.startTime = new Date(this.args.message.startTime);
- if (typeof this.args.message.endTime === 'string')
- this.args.message.endTime = new Date(this.args.message.endTime);
-
- const { id } = yield this.args.message.save();
- this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} the message.`);
+ const { id, title } = yield this.args.message.save();
+ this.flashMessages.success(`Successfully ${isNew ? 'created' : 'updated'} ${title} message.`);
+ this.store.clearDataset('config-ui/message');
this.router.transitionTo('vault.cluster.config-ui.messages.message.details', id);
}
} catch (error) {
diff --git a/ui/lib/config-ui/addon/components/messages/page/list.hbs b/ui/lib/config-ui/addon/components/messages/page/list.hbs
index 8ec009d2084c..2cf43921ac09 100644
--- a/ui/lib/config-ui/addon/components/messages/page/list.hbs
+++ b/ui/lib/config-ui/addon/components/messages/page/list.hbs
@@ -22,7 +22,7 @@
{{#if @messages.length}}
- {{#each this.getMessages as |message|}}
+ {{#each this.formattedMessages as |message|}}
{
let badgeDisplayText = '';
-
if (message.active) {
if (message.endTime) {
badgeDisplayText = `Active until ${dateFormat([message.endTime, 'MMM d, yyyy hh:mm aaa'], {
@@ -68,5 +69,7 @@ export default class MessagesList extends Component {
*deleteMessage(message) {
this.store.clearDataset('config-ui/message');
yield message.destroyRecord(message.id);
+ this.router.transitionTo('vault.cluster.config-ui.messages');
+ this.flashMessages.success(`Successfully deleted ${message.title}.`);
}
}
diff --git a/ui/lib/config-ui/addon/components/messages/preview-image.hbs b/ui/lib/config-ui/addon/components/messages/preview-image.hbs
new file mode 100644
index 000000000000..c30c8deaba11
--- /dev/null
+++ b/ui/lib/config-ui/addon/components/messages/preview-image.hbs
@@ -0,0 +1,40 @@
+{{!
+ Copyright (c) HashiCorp, Inc.
+ SPDX-License-Identifier: BUSL-1.1
+~}}
+
+
+
+
+ {{@message.title}}
+
+ {{@message.message}}
+ {{#if @message.linkHref}}
+
+ {{@message.linkTitle}}
+
+ {{/if}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/lib/config-ui/addon/routes/messages/create.js b/ui/lib/config-ui/addon/routes/messages/create.js
index f6361394810d..40102e0b7c7b 100644
--- a/ui/lib/config-ui/addon/routes/messages/create.js
+++ b/ui/lib/config-ui/addon/routes/messages/create.js
@@ -22,4 +22,13 @@ export default class MessagesCreateRoute extends Route {
authenticated,
});
}
+
+ setupController(controller, resolvedModel) {
+ super.setupController(controller, resolvedModel);
+
+ controller.breadcrumbs = [
+ { label: 'Messages', route: 'messages', query: { authenticated: !!resolvedModel.authenticated } },
+ { label: 'Create Message' },
+ ];
+ }
}
diff --git a/ui/lib/config-ui/addon/routes/messages/message/edit.js b/ui/lib/config-ui/addon/routes/messages/message/edit.js
index 44146b493524..6d56a14e027f 100644
--- a/ui/lib/config-ui/addon/routes/messages/message/edit.js
+++ b/ui/lib/config-ui/addon/routes/messages/message/edit.js
@@ -4,5 +4,23 @@
*/
import Route from '@ember/routing/route';
+import { inject as service } from '@ember/service';
-export default class MessagesMessageEditRoute extends Route {}
+export default class MessagesMessageEditRoute extends Route {
+ @service store;
+
+ model() {
+ const { id } = this.paramsFor('messages.message');
+
+ return this.store.queryRecord('config-ui/message', id);
+ }
+
+ setupController(controller, resolvedModel) {
+ super.setupController(controller, resolvedModel);
+
+ controller.breadcrumbs = [
+ { label: 'Messages', route: 'messages', query: { authenticated: resolvedModel.authenticated } },
+ { label: 'Edit Message' },
+ ];
+ }
+}
diff --git a/ui/lib/config-ui/addon/templates/messages/create.hbs b/ui/lib/config-ui/addon/templates/messages/create.hbs
index 6b7f2b648b33..e78c5a7bfe5c 100644
--- a/ui/lib/config-ui/addon/templates/messages/create.hbs
+++ b/ui/lib/config-ui/addon/templates/messages/create.hbs
@@ -3,4 +3,4 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/lib/config-ui/addon/templates/messages/message/edit.hbs b/ui/lib/config-ui/addon/templates/messages/message/edit.hbs
index de0a22cde6fe..e78c5a7bfe5c 100644
--- a/ui/lib/config-ui/addon/templates/messages/message/edit.hbs
+++ b/ui/lib/config-ui/addon/templates/messages/message/edit.hbs
@@ -3,5 +3,4 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-Message Edit
-{{outlet}}
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/public/images/custom-messages-dashboard.png b/ui/public/images/custom-messages-dashboard.png
new file mode 100644
index 000000000000..767c3c5d4b0c
Binary files /dev/null and b/ui/public/images/custom-messages-dashboard.png differ
diff --git a/ui/public/images/custom-messages-login.png b/ui/public/images/custom-messages-login.png
new file mode 100644
index 000000000000..ad29bdf37f23
Binary files /dev/null and b/ui/public/images/custom-messages-login.png differ
diff --git a/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js b/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js
index d375357d4cf4..e45173bab06d 100644
--- a/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js
+++ b/ui/tests/integration/components/config-ui/messages/page/create-and-edit-message-test.js
@@ -19,6 +19,12 @@ const PAGE = {
button: (buttonName) => `[data-test-button="${buttonName}"]`,
inlineErrorMessage: `[data-test-inline-error-message]`,
fieldVaildation: (fieldName) => `[data-test-field-validation="${fieldName}"]`,
+ modal: (name) => `[data-test-modal="${name}"]`,
+ modalTitle: (title) => `[data-test-modal-title="${title}"]`,
+ modalBody: '[data-test-modal-body]',
+ modalButton: (name) => `[data-test-modal-button="${name}"]`,
+ alertTitle: (name) => `[data-test-alert-title="${name}"]`,
+ alertDescription: (name) => `[data-test-alert-description="${name}"]`,
};
module('Integration | Component | messages/page/create-and-edit-message', function (hooks) {
@@ -112,6 +118,8 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
message: 'Blah blah blah. Some super long message.',
start_time: '2023-12-12T08:00:00.000Z',
end_time: '2023-12-21T08:00:00.000Z',
+ link_title: 'Learn more',
+ link_href: 'www.learnmore.com',
});
this.message = this.store.peekRecord('config-ui/message', 'hhhhh-iiii-lllll-dddd');
await render(hbs``, {
@@ -129,7 +137,9 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
assert.dom(PAGE.input('title')).hasValue('Hello world');
assert.dom(PAGE.input('message')).hasValue('Blah blah blah. Some super long message.');
assert.dom(PAGE.input('linkTitle')).exists();
+ assert.dom(PAGE.input('linkTitle')).hasValue('Learn more');
assert.dom(PAGE.input('linkHref')).exists();
+ assert.dom(PAGE.input('linkHref')).hasValue('www.learnmore.com');
await click('#specificDate');
assert
.dom(PAGE.input('startTime'))
@@ -138,4 +148,43 @@ module('Integration | Component | messages/page/create-and-edit-message', functi
.dom(PAGE.input('endTime'))
.hasValue(format(new Date(this.message.endTime), datetimeLocalStringFormat));
});
+
+ test('it should show a preview image modal when preview is clicked', async function (assert) {
+ await render(hbs``, {
+ owner: this.engine,
+ });
+ await fillIn(PAGE.input('title'), 'Awesome custom message title');
+ await fillIn(
+ PAGE.input('message'),
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pulvinar mattis nunc sed blandit libero volutpat sed cras ornare.'
+ );
+ await click(PAGE.button('preview'));
+ assert.dom(PAGE.modal('preview modal')).doesNotExist();
+ assert.dom(PAGE.modal('preview image')).exists();
+ assert.dom(PAGE.alertTitle('Awesome custom message title')).hasText('Awesome custom message title');
+ assert
+ .dom(PAGE.alertDescription('Awesome custom message title'))
+ .hasText(
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pulvinar mattis nunc sed blandit libero volutpat sed cras ornare.'
+ );
+ assert.dom('img').hasAttribute('src', '/ui/images/custom-messages-dashboard.png');
+ await click(PAGE.modalButton('Close'));
+ await click('#unauthenticated');
+ await click(PAGE.button('preview'));
+ assert.dom('img').hasAttribute('src', '/ui/images/custom-messages-login.png');
+ });
+
+ test('it should show a preview modal when preview is clicked', async function (assert) {
+ await render(hbs``, {
+ owner: this.engine,
+ });
+ await click(PAGE.radio('modal'));
+ await fillIn(PAGE.input('title'), 'Preview modal title');
+ await fillIn(PAGE.input('message'), 'Some preview modal message thats super long.');
+ await click(PAGE.button('preview'));
+ assert.dom(PAGE.modal('preview modal')).exists();
+ assert.dom(PAGE.modal('preview image')).doesNotExist();
+ assert.dom(PAGE.modalTitle('Preview modal title')).hasText('Preview modal title');
+ assert.dom(PAGE.modalBody).hasText('Some preview modal message thats super long.');
+ });
});