[ui, helios] Toast Component (#16099)
* Template and styles * @type to @color on flash messages * Notifications service as wrapper * Test cases updated for new notifs
This commit is contained in:
parent
0e1b554299
commit
93574ce085
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
ui: Restyles "toast" notifications in the web UI with the Helios Design System
|
||||||
|
```
|
|
@ -9,6 +9,7 @@ import classic from 'ember-classic-decorator';
|
||||||
@tagName('')
|
@tagName('')
|
||||||
export default class Title extends Component {
|
export default class Title extends Component {
|
||||||
@service router;
|
@service router;
|
||||||
|
@service notifications;
|
||||||
|
|
||||||
job = null;
|
job = null;
|
||||||
title = null;
|
title = null;
|
||||||
|
@ -34,12 +35,10 @@ export default class Title extends Component {
|
||||||
try {
|
try {
|
||||||
const job = this.job;
|
const job = this.job;
|
||||||
yield job.purge();
|
yield job.purge();
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Job Purged',
|
title: 'Job Purged',
|
||||||
message: `You have purged ${this.job.name}`,
|
message: `You have purged ${this.job.name}`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
this.router.transitionTo('jobs');
|
this.router.transitionTo('jobs');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { inject as service } from '@ember/service';
|
||||||
import { alias } from '@ember/object/computed';
|
import { alias } from '@ember/object/computed';
|
||||||
|
|
||||||
export default class PolicyEditorComponent extends Component {
|
export default class PolicyEditorComponent extends Component {
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
@service router;
|
@service router;
|
||||||
@service store;
|
@service store;
|
||||||
|
|
||||||
|
@ -41,22 +41,19 @@ export default class PolicyEditorComponent extends Component {
|
||||||
|
|
||||||
await this.policy.save();
|
await this.policy.save();
|
||||||
|
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Policy Saved',
|
title: 'Policy Saved',
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (shouldRedirectAfterSave) {
|
if (shouldRedirectAfterSave) {
|
||||||
this.router.transitionTo('policies.policy', this.policy.id);
|
this.router.transitionTo('policies.policy', this.policy.id);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Error creating Policy ${this.policy.name}`,
|
title: `Error creating Policy ${this.policy.name}`,
|
||||||
message: error,
|
message: error,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ const EMPTY_KV = {
|
||||||
const invalidKeyCharactersRegex = new RegExp(/[^_\p{Letter}\p{Number}]/gu);
|
const invalidKeyCharactersRegex = new RegExp(/[^_\p{Letter}\p{Number}]/gu);
|
||||||
|
|
||||||
export default class VariableFormComponent extends Component {
|
export default class VariableFormComponent extends Component {
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
@service router;
|
@service router;
|
||||||
@service store;
|
@service store;
|
||||||
|
|
||||||
|
@ -240,23 +240,20 @@ export default class VariableFormComponent extends Component {
|
||||||
this.args.model.setAndTrimPath();
|
this.args.model.setAndTrimPath();
|
||||||
await this.args.model.save({ adapterOptions: { overwrite } });
|
await this.args.model.save({ adapterOptions: { overwrite } });
|
||||||
|
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Variable saved',
|
title: 'Variable saved',
|
||||||
message: `${this.path} successfully saved`,
|
message: `${this.path} successfully saved`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
this.removeExitHandler();
|
this.removeExitHandler();
|
||||||
this.router.transitionTo('variables.variable', this.args.model.id);
|
this.router.transitionTo('variables.variable', this.args.model.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifyConflict(this)(error);
|
notifyConflict(this)(error);
|
||||||
if (!this.hasConflict) {
|
if (!this.hasConflict) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Error saving ${this.path}`,
|
title: `Error saving ${this.path}`,
|
||||||
message: error,
|
message: error,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default class ApplicationController extends Controller {
|
||||||
@service config;
|
@service config;
|
||||||
@service system;
|
@service system;
|
||||||
@service token;
|
@service token;
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {KeyboardService}
|
* @type {KeyboardService}
|
||||||
|
|
|
@ -24,7 +24,7 @@ export default class ClientController extends Controller.extend(
|
||||||
Sortable,
|
Sortable,
|
||||||
Searchable
|
Searchable
|
||||||
) {
|
) {
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
|
|
||||||
queryParams = [
|
queryParams = [
|
||||||
{
|
{
|
||||||
|
@ -316,21 +316,18 @@ export default class ClientController extends Controller.extend(
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
await this.model.addMeta({ [key]: value });
|
await this.model.addMeta({ [key]: value });
|
||||||
|
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Metadata added',
|
title: 'Metadata added',
|
||||||
message: `${key} successfully saved`,
|
message: `${key} successfully saved`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 3000,
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error =
|
const error =
|
||||||
messageFromAdapterError(err) || 'Could not save new dynamic metadata';
|
messageFromAdapterError(err) || 'Could not save new dynamic metadata';
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Error saving Metadata`,
|
title: `Error saving Metadata`,
|
||||||
message: error,
|
message: error,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { tracked } from '@glimmer/tracking';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
|
|
||||||
export default class JobsRunTemplatesManageController extends Controller {
|
export default class JobsRunTemplatesManageController extends Controller {
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
get templates() {
|
get templates() {
|
||||||
|
@ -27,19 +27,16 @@ export default class JobsRunTemplatesManageController extends Controller {
|
||||||
@task(function* (model) {
|
@task(function* (model) {
|
||||||
try {
|
try {
|
||||||
yield model.destroyRecord();
|
yield model.destroyRecord();
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Job template deleted',
|
title: 'Job template deleted',
|
||||||
message: `${model.path} successfully deleted`,
|
message: `${model.path} successfully deleted`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Job template could not be deleted.`,
|
title: `Job template could not be deleted.`,
|
||||||
message: err,
|
message: err,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default class JobsRunTemplatesNewController extends Controller {
|
||||||
@service system;
|
@service system;
|
||||||
@tracked templateName = null;
|
@tracked templateName = null;
|
||||||
@tracked templateNamespace = 'default';
|
@tracked templateNamespace = 'default';
|
||||||
|
@service notifications;
|
||||||
|
|
||||||
get namespaceOptions() {
|
get namespaceOptions() {
|
||||||
const namespaces = this.store
|
const namespaces = this.store
|
||||||
|
@ -60,22 +61,18 @@ export default class JobsRunTemplatesNewController extends Controller {
|
||||||
try {
|
try {
|
||||||
await this.model.save({ adapterOptions: { overwrite } });
|
await this.model.save({ adapterOptions: { overwrite } });
|
||||||
|
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Job template saved',
|
title: 'Job template saved',
|
||||||
message: `${this.templateName} successfully saved`,
|
message: `${this.templateName} successfully saved`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.router.transitionTo('jobs.run.templates');
|
this.router.transitionTo('jobs.run.templates');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Job template cannot be saved.',
|
title: 'Job template cannot be saved.',
|
||||||
message: e,
|
message: e,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { tracked } from '@glimmer/tracking';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
|
|
||||||
export default class JobsRunTemplatesController extends Controller {
|
export default class JobsRunTemplatesController extends Controller {
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
@service router;
|
@service router;
|
||||||
@service system;
|
@service system;
|
||||||
|
|
||||||
|
@ -34,22 +34,18 @@ export default class JobsRunTemplatesController extends Controller {
|
||||||
try {
|
try {
|
||||||
await this.model.save({ adapterOptions: { overwrite } });
|
await this.model.save({ adapterOptions: { overwrite } });
|
||||||
|
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Job template saved',
|
title: 'Job template saved',
|
||||||
message: `${this.model.path} successfully editted`,
|
message: `${this.model.path} successfully editted`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.router.transitionTo('jobs.run.templates');
|
this.router.transitionTo('jobs.run.templates');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Job template cannot be editted.',
|
title: 'Job template cannot be editted.',
|
||||||
message: e,
|
message: e,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,20 +54,17 @@ export default class JobsRunTemplatesController extends Controller {
|
||||||
try {
|
try {
|
||||||
yield this.model.destroyRecord();
|
yield this.model.destroyRecord();
|
||||||
|
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Job template deleted',
|
title: 'Job template deleted',
|
||||||
message: `${this.model.path} successfully deleted`,
|
message: `${this.model.path} successfully deleted`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
this.router.transitionTo('jobs.run.templates.manage');
|
this.router.transitionTo('jobs.run.templates.manage');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Job template could not be deleted.`,
|
title: `Job template could not be deleted.`,
|
||||||
message: err,
|
message: err,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { alias } from '@ember/object/computed';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
|
|
||||||
export default class PoliciesPolicyController extends Controller {
|
export default class PoliciesPolicyController extends Controller {
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
@service router;
|
@service router;
|
||||||
@service store;
|
@service store;
|
||||||
|
|
||||||
|
@ -37,19 +37,18 @@ export default class PoliciesPolicyController extends Controller {
|
||||||
try {
|
try {
|
||||||
yield this.policy.deleteRecord();
|
yield this.policy.deleteRecord();
|
||||||
yield this.policy.save();
|
yield this.policy.save();
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Policy Deleted',
|
title: 'Policy Deleted',
|
||||||
type: 'success',
|
color: 'success',
|
||||||
|
type: `success`,
|
||||||
destroyOnClick: false,
|
destroyOnClick: false,
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
this.router.transitionTo('policies');
|
this.router.transitionTo('policies');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Error deleting Policy ${this.policy.name}`,
|
title: `Error deleting Policy ${this.policy.name}`,
|
||||||
message: err,
|
message: err,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -75,11 +74,10 @@ export default class PoliciesPolicyController extends Controller {
|
||||||
});
|
});
|
||||||
yield newToken.save();
|
yield newToken.save();
|
||||||
yield this.refreshTokens();
|
yield this.refreshTokens();
|
||||||
this.flashMessages.add({
|
const thing = this.notifications.add({
|
||||||
title: 'Example Token Created',
|
title: 'Example Token Created',
|
||||||
message: `${newToken.secret}`,
|
message: `${newToken.secret}`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
customAction: {
|
customAction: {
|
||||||
label: 'Copy to Clipboard',
|
label: 'Copy to Clipboard',
|
||||||
|
@ -88,6 +86,7 @@ export default class PoliciesPolicyController extends Controller {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log('thing', thing);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.error = {
|
this.error = {
|
||||||
title: 'Error creating new token',
|
title: 'Error creating new token',
|
||||||
|
@ -104,11 +103,9 @@ export default class PoliciesPolicyController extends Controller {
|
||||||
yield token.deleteRecord();
|
yield token.deleteRecord();
|
||||||
yield token.save();
|
yield token.save();
|
||||||
yield this.refreshTokens();
|
yield this.refreshTokens();
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Token successfully deleted',
|
title: 'Token successfully deleted',
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.error = {
|
this.error = {
|
||||||
|
|
|
@ -13,6 +13,8 @@ export default class VariablesVariableIndexController extends Controller {
|
||||||
@tracked sortProperty = 'key';
|
@tracked sortProperty = 'key';
|
||||||
@tracked sortDescending = true;
|
@tracked sortDescending = true;
|
||||||
|
|
||||||
|
@service notifications;
|
||||||
|
|
||||||
get sortedKeyValues() {
|
get sortedKeyValues() {
|
||||||
const sorted = this.model.keyValues.sortBy(this.sortProperty);
|
const sorted = this.model.keyValues.sortBy(this.sortProperty);
|
||||||
return this.sortDescending ? sorted : sorted.reverse();
|
return this.sortDescending ? sorted : sorted.reverse();
|
||||||
|
@ -39,19 +41,16 @@ export default class VariablesVariableIndexController extends Controller {
|
||||||
} else {
|
} else {
|
||||||
this.router.transitionTo('variables');
|
this.router.transitionTo('variables');
|
||||||
}
|
}
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: 'Variable deleted',
|
title: 'Variable deleted',
|
||||||
message: `${this.model.path} successfully deleted`,
|
message: `${this.model.path} successfully deleted`,
|
||||||
type: 'success',
|
color: 'success',
|
||||||
destroyOnClick: false,
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Error deleting ${this.model.path}`,
|
title: `Error deleting ${this.model.path}`,
|
||||||
message: err,
|
message: err,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
import WithWatchers from 'nomad-ui/mixins/with-watchers';
|
import WithWatchers from 'nomad-ui/mixins/with-watchers';
|
||||||
import notifyError from 'nomad-ui/utils/notify-error';
|
import notifyError from 'nomad-ui/utils/notify-error';
|
||||||
export default class AllocationRoute extends Route.extend(WithWatchers) {
|
export default class AllocationRoute extends Route.extend(WithWatchers) {
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
@service router;
|
@service router;
|
||||||
@service store;
|
@service store;
|
||||||
|
|
||||||
|
@ -51,11 +51,10 @@ export default class AllocationRoute extends Route.extend(WithWatchers) {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const [allocId, transition] = arguments;
|
const [allocId, transition] = arguments;
|
||||||
if (e?.errors[0]?.detail === 'alloc not found' && !!transition.from) {
|
if (e?.errors[0]?.detail === 'alloc not found' && !!transition.from) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Error: Not Found`,
|
title: `Error: Not Found`,
|
||||||
message: `Allocation of id: ${allocId} was not found.`,
|
message: `Allocation of id: ${allocId} was not found.`,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
this.goBackToReferrer(transition.from.name);
|
this.goBackToReferrer(transition.from.name);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import notifyForbidden from 'nomad-ui/utils/notify-forbidden';
|
||||||
@classic
|
@classic
|
||||||
export default class JobsRunIndexRoute extends Route {
|
export default class JobsRunIndexRoute extends Route {
|
||||||
@service can;
|
@service can;
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
@service router;
|
@service router;
|
||||||
@service store;
|
@service store;
|
||||||
@service system;
|
@service system;
|
||||||
|
@ -51,11 +51,10 @@ export default class JobsRunIndexRoute extends Route {
|
||||||
handle404(e) {
|
handle404(e) {
|
||||||
const error404 = e.errors?.find((err) => err.status === 404);
|
const error404 = e.errors?.find((err) => err.status === 404);
|
||||||
if (error404) {
|
if (error404) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: `Error loading job template`,
|
title: `Error loading job template`,
|
||||||
message: error404.detail,
|
message: error404.detail,
|
||||||
type: 'error',
|
color: 'critical',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
// @ts-check
|
||||||
|
import { default as FlashService } from 'ember-cli-flash/services/flash-messages';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} NotificationObject
|
||||||
|
* @property {string} title
|
||||||
|
* @property {string} [message]
|
||||||
|
* @property {string} [type]
|
||||||
|
* @property {string} [color = 'neutral']
|
||||||
|
* @property {boolean} [sticky = true]
|
||||||
|
* @property {boolean} [destroyOnClick = false]
|
||||||
|
* @property {number} [timeout = 5000]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class NotificationsService
|
||||||
|
* @extends FlashService
|
||||||
|
* A wrapper service around Ember Flash Messages, for adding notifications to the UI
|
||||||
|
*/
|
||||||
|
export default class NotificationsService extends FlashService {
|
||||||
|
/**
|
||||||
|
* @param {NotificationObject} notificationObject
|
||||||
|
* @returns {FlashService}
|
||||||
|
*/
|
||||||
|
add(notificationObject) {
|
||||||
|
// Set some defaults
|
||||||
|
if (!('type' in notificationObject)) {
|
||||||
|
notificationObject.type = notificationObject.color || 'neutral';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('sticky' in notificationObject)) {
|
||||||
|
notificationObject.sticky = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('destroyOnClick' in notificationObject)) {
|
||||||
|
notificationObject.destroyOnClick = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('timeout' in notificationObject)) {
|
||||||
|
notificationObject.timeout = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.add(notificationObject);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ export default class TokenService extends Service {
|
||||||
@service store;
|
@service store;
|
||||||
@service system;
|
@service system;
|
||||||
@service router;
|
@service router;
|
||||||
@service flashMessages;
|
@service notifications;
|
||||||
|
|
||||||
aclEnabled = true;
|
aclEnabled = true;
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ export default class TokenService extends Service {
|
||||||
// Let the user know at the 10 minute mark,
|
// Let the user know at the 10 minute mark,
|
||||||
// or any time they refresh with under 10 minutes left
|
// or any time they refresh with under 10 minutes left
|
||||||
if (diff < 1000 * 60 * MINUTES_LEFT_AT_WARNING) {
|
if (diff < 1000 * 60 * MINUTES_LEFT_AT_WARNING) {
|
||||||
const existingNotification = this.flashMessages.queue?.find(
|
const existingNotification = this.notifications.queue?.find(
|
||||||
(m) => m.title === EXPIRY_NOTIFICATION_TITLE
|
(m) => m.title === EXPIRY_NOTIFICATION_TITLE
|
||||||
);
|
);
|
||||||
// For the sake of updating the "time left" message, we keep running the task down to the moment of expiration
|
// For the sake of updating the "time left" message, we keep running the task down to the moment of expiration
|
||||||
|
@ -149,13 +149,12 @@ export default class TokenService extends Service {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (!this.expirationNotificationDismissed) {
|
if (!this.expirationNotificationDismissed) {
|
||||||
this.flashMessages.add({
|
this.notifications.add({
|
||||||
title: EXPIRY_NOTIFICATION_TITLE,
|
title: EXPIRY_NOTIFICATION_TITLE,
|
||||||
message: `Your token access expires ${moment(
|
message: `Your token access expires ${moment(
|
||||||
this.selfToken.expirationTime
|
this.selfToken.expirationTime
|
||||||
).fromNow()}`,
|
).fromNow()}`,
|
||||||
type: 'error',
|
color: 'warning',
|
||||||
destroyOnClick: false,
|
|
||||||
sticky: true,
|
sticky: true,
|
||||||
customCloseAction: () => {
|
customCloseAction: () => {
|
||||||
this.set('expirationNotificationDismissed', true);
|
this.set('expirationNotificationDismissed', true);
|
||||||
|
|
|
@ -1,66 +1,14 @@
|
||||||
$bonusRightPadding: 20px;
|
|
||||||
|
|
||||||
section.notifications {
|
section.notifications {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 10px;
|
bottom: 20px;
|
||||||
right: 10px;
|
right: 20px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
justify-items: right;
|
||||||
|
display: grid;
|
||||||
|
|
||||||
.flash-message {
|
.flash-message {
|
||||||
width: 300px;
|
&:not(:last-child) {
|
||||||
transition: all 700ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
margin-bottom: 1rem;
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
box-shadow: 1px 1px 4px 0px rgb(0, 0, 0, 0.1);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
padding-right: $bonusRightPadding;
|
|
||||||
|
|
||||||
&.alert-success {
|
|
||||||
background-color: lighten($nomad-green, 50%);
|
|
||||||
}
|
|
||||||
&.alert-error {
|
|
||||||
background-color: lighten($danger, 45%);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.close-button {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 10px;
|
|
||||||
line-height: 100%;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-progress {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
.alert-progressBar {
|
|
||||||
background-color: $nomad-green;
|
|
||||||
height: 2px;
|
|
||||||
width: 0%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
.alert-progress {
|
|
||||||
.alert-progressBar {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-action-button {
|
|
||||||
width: calc(100% + $bonusRightPadding - 1rem);
|
|
||||||
margin: 1.5rem 0 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,28 +7,27 @@
|
||||||
<SvgPatterns />
|
<SvgPatterns />
|
||||||
|
|
||||||
<section class="notifications">
|
<section class="notifications">
|
||||||
{{#each this.flashMessages.queue as |flash|}}
|
{{#each this.notifications.queue as |flash|}}
|
||||||
<FlashMessage @flash={{flash}} as |component flash close|>
|
<FlashMessage @flash={{flash}} as |component flash close|>
|
||||||
<span class="close-button" role="button" {{on "click"
|
<Hds::Toast
|
||||||
(queue
|
@color={{or flash.color "neutral"}}
|
||||||
(action close)
|
@onDismiss={{
|
||||||
(action (optional flash.customCloseAction))
|
queue
|
||||||
)
|
(action close)
|
||||||
}}>×</span>
|
(action (optional flash.customCloseAction))
|
||||||
{{#if flash.title}}
|
|
||||||
<h3>{{flash.title}}</h3>
|
}}
|
||||||
{{/if}}
|
as |T|>
|
||||||
{{#if flash.message}}
|
{{#if flash.title}}
|
||||||
<p>{{flash.message}}</p>
|
<T.Title>{{flash.title}}</T.Title>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if flash.customAction}}
|
{{#if flash.message}}
|
||||||
<button type="button" class="button custom-action-button" {{on "click" (action flash.customAction.action)}}>{{flash.customAction.label}}</button>
|
<T.Description>{{flash.message}}</T.Description>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if component.showProgressBar}}
|
{{#if flash.customAction}}
|
||||||
<div class="alert-progress">
|
<T.Button @text={{flash.customAction.label}} @color="secondary" {{on "click" (action flash.customAction.action)}} />
|
||||||
<div class="alert-progressBar" style={{component.progressDuration}}></div>
|
{{/if}}
|
||||||
</div>
|
</Hds::Toast>
|
||||||
{{/if}}
|
|
||||||
</FlashMessage>
|
</FlashMessage>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -464,11 +464,11 @@ module('Acceptance | allocation detail', function (hooks) {
|
||||||
|
|
||||||
component.onClick();
|
component.onClick();
|
||||||
|
|
||||||
await waitFor('.flash-message.alert-error');
|
await waitFor('.flash-message.alert-critical');
|
||||||
|
|
||||||
assert.verifySteps(['Transition dispatched.']);
|
assert.verifySteps(['Transition dispatched.']);
|
||||||
assert
|
assert
|
||||||
.dom('.flash-message.alert-error')
|
.dom('.flash-message.alert-critical')
|
||||||
.exists('A toast error message pops up.');
|
.exists('A toast error message pops up.');
|
||||||
|
|
||||||
// Clean-up
|
// Clean-up
|
||||||
|
|
|
@ -352,7 +352,7 @@ module('Acceptance | job run', function (hooks) {
|
||||||
'We do not navigate away from the page if an error is returned by the API.'
|
'We do not navigate away from the page if an error is returned by the API.'
|
||||||
);
|
);
|
||||||
assert
|
assert
|
||||||
.dom('.flash-message.alert-error')
|
.dom('.flash-message.alert-critical')
|
||||||
.exists('A toast error message pops up.');
|
.exists('A toast error message pops up.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ module('Acceptance | policies', function (hooks) {
|
||||||
await typeIn('[data-test-policy-name-input]', 'My Fun Policy');
|
await typeIn('[data-test-policy-name-input]', 'My Fun Policy');
|
||||||
await click('button[type="submit"]');
|
await click('button[type="submit"]');
|
||||||
assert
|
assert
|
||||||
.dom('.flash-message.alert-error')
|
.dom('.flash-message.alert-critical')
|
||||||
.exists('Doesnt let you save a bad name');
|
.exists('Doesnt let you save a bad name');
|
||||||
assert.equal(currentURL(), '/policies/new');
|
assert.equal(currentURL(), '/policies/new');
|
||||||
document.querySelector('[data-test-policy-name-input]').value = ''; // clear
|
document.querySelector('[data-test-policy-name-input]').value = ''; // clear
|
||||||
|
|
|
@ -213,10 +213,10 @@ module('Acceptance | tokens', function (hooks) {
|
||||||
// TTL Action
|
// TTL Action
|
||||||
await Jobs.visit();
|
await Jobs.visit();
|
||||||
assert
|
assert
|
||||||
.dom('.flash-message.alert-error button')
|
.dom('.flash-message.alert-warning button')
|
||||||
.exists('A global alert exists and has a clickable button');
|
.exists('A global alert exists and has a clickable button');
|
||||||
|
|
||||||
await click('.flash-message.alert-error button');
|
await click('.flash-message.alert-warning button');
|
||||||
assert.equal(
|
assert.equal(
|
||||||
currentURL(),
|
currentURL(),
|
||||||
'/settings/tokens',
|
'/settings/tokens',
|
||||||
|
@ -313,7 +313,7 @@ module('Acceptance | tokens', function (hooks) {
|
||||||
// short-circuiting our Ember Concurrency loop.
|
// short-circuiting our Ember Concurrency loop.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
assert
|
assert
|
||||||
.dom('.flash-message.alert-error')
|
.dom('.flash-message.alert-warning')
|
||||||
.doesNotExist('No notification yet for a token with 10m5s left');
|
.doesNotExist('No notification yet for a token with 10m5s left');
|
||||||
notificationNotRendered();
|
notificationNotRendered();
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
|
@ -322,7 +322,7 @@ module('Acceptance | tokens', function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
assert
|
assert
|
||||||
.dom('.flash-message.alert-error')
|
.dom('.flash-message.alert-warning')
|
||||||
.exists('Notification is rendered at the 10m mark');
|
.exists('Notification is rendered at the 10m mark');
|
||||||
notificationRendered();
|
notificationRendered();
|
||||||
run.cancelTimers();
|
run.cancelTimers();
|
||||||
|
|
|
@ -362,9 +362,9 @@ module('Acceptance | variables', function (hooks) {
|
||||||
assert.equal(currentURL(), '/variables/new');
|
assert.equal(currentURL(), '/variables/new');
|
||||||
await typeIn('.path-input', 'foo/bar');
|
await typeIn('.path-input', 'foo/bar');
|
||||||
await click('button[type="submit"]');
|
await click('button[type="submit"]');
|
||||||
assert.dom('.flash-message.alert-error').exists();
|
assert.dom('.flash-message.alert-critical').exists();
|
||||||
await click('.flash-message.alert-error .close-button');
|
await click('.flash-message.alert-critical .hds-dismiss-button');
|
||||||
assert.dom('.flash-message.alert-error').doesNotExist();
|
assert.dom('.flash-message.alert-critical').doesNotExist();
|
||||||
|
|
||||||
await typeIn('.key-value label:nth-child(1) input', 'myKey');
|
await typeIn('.key-value label:nth-child(1) input', 'myKey');
|
||||||
await typeIn('.key-value label:nth-child(2) input', 'superSecret');
|
await typeIn('.key-value label:nth-child(2) input', 'superSecret');
|
||||||
|
|
Loading…
Reference in New Issue