@fleetbase/ember-core 0.2.22 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@ import getUserOptions from '../utils/get-user-options';
13
13
  import config from 'ember-get-config';
14
14
 
15
15
  if (isBlank(config.API.host)) {
16
- config.API.host = `${window.location.protocol}//${window.location.hostname}:8000`;
16
+ config.API.host = `${window.location.protocol}//${window.location.hostname}`;
17
17
  }
18
18
  const DEFAULT_ERROR_MESSAGE = 'Oops! Something went wrong. Please try again or contact support if the issue persists.';
19
19
  export default class ApplicationAdapter extends RESTAdapter {
@@ -54,38 +54,38 @@ export default class CrudService extends Service {
54
54
  */
55
55
  @action delete(model, options = {}) {
56
56
  const modelName = getModelName(model, get(options, 'modelName'), { humanize: true, capitalizeWords: true });
57
+ const successNotification = options.successNotification || `${model.name ? modelName + " '" + model.name + "'" : "'" + modelName + "'"} has been deleted.`;
57
58
 
58
59
  this.modalsManager.confirm({
59
60
  title: `Are you sure to delete this ${modelName}?`,
60
61
  args: ['model'],
61
62
  model,
62
- confirm: (modal) => {
63
- if (typeof options.onConfirm === 'function') {
64
- options.onConfirm(model);
63
+ confirm: async (modal) => {
64
+ if (typeof options.onTrigger === 'function') {
65
+ options.onTrigger(model);
65
66
  }
66
67
 
67
68
  modal.startLoading();
68
69
 
69
- return model
70
- .destroyRecord()
71
- .then((model) => {
72
- this.notifications.success(options.successNotification || `${model.name ? modelName + " '" + model.name + "'" : "'" + modelName + "'"} has been deleted.`);
73
- if (typeof options.onSuccess === 'function') {
74
- options.onSuccess(model);
75
- }
76
- })
77
- .catch((error) => {
78
- this.notifications.serverError(error);
70
+ try {
71
+ const response = await model.destroyRecord();
72
+ this.notifications.success(successNotification);
73
+ if (typeof options.onSuccess === 'function') {
74
+ options.onSuccess(model);
75
+ }
79
76
 
80
- if (typeof options.onError === 'function') {
81
- options.onError(error, model);
82
- }
83
- })
84
- .finally(() => {
85
- if (typeof options.callback === 'function') {
86
- options.callback(model);
87
- }
88
- });
77
+ return response;
78
+ } catch (error) {
79
+ this.notifications.serverError(error);
80
+
81
+ if (typeof options.onError === 'function') {
82
+ options.onError(error, model);
83
+ }
84
+ } finally {
85
+ if (typeof options.callback === 'function') {
86
+ options.callback(model);
87
+ }
88
+ }
89
89
  },
90
90
  ...options,
91
91
  });
@@ -135,10 +135,21 @@ export default class CrudService extends Service {
135
135
  const modelName = getModelName(firstModel, get(options, 'modelName'), { humanize: true, capitalizeWords: true });
136
136
  const count = selected.length;
137
137
  const actionMethod = (typeof options.actionMethod === 'string' ? options.actionMethod : `POST`).toLowerCase();
138
- const fetchParams = getWithDefault(options, 'fetchParams', {});
139
- const fetchOptions = getWithDefault(options, 'fetchOptions', {});
138
+ const modalTemplate = getWithDefault(options, 'template', 'modals/bulk-action-model');
139
+ const successMessage = options.successNotification ?? `${count} ${pluralize(count, modelName)} were updated successfully.`;
140
+
141
+ if (typeof options.resolveModelName === 'function') {
142
+ selected = selected.map((model) => {
143
+ const resolvedModelName = options.resolveModelName(model);
144
+ if (typeof resolvedModelName === 'string') {
145
+ model.set('list_resolved_name', resolvedModelName);
146
+ }
147
+
148
+ return model;
149
+ });
150
+ }
140
151
 
141
- this.modalsManager.show('modals/bulk-action-model', {
152
+ this.modalsManager.show(modalTemplate, {
142
153
  title: `Bulk ${verb} ${pluralize(modelName)}`,
143
154
  acceptButtonText: humanize(verb),
144
155
  args: ['selected'],
@@ -151,42 +162,49 @@ export default class CrudService extends Service {
151
162
  selected.removeObject(model);
152
163
  this.modalsManager.setOption('selected', selected);
153
164
  },
154
- confirm: (modal) => {
165
+ confirm: async (modal) => {
155
166
  const selected = modal.getOption('selected');
167
+ const fetchParams = modal.getOption('fetchParams', {});
168
+ const fetchOptions = modal.getOption('fetchOptions', {});
169
+ const callback = modal.getOption('callback');
156
170
 
157
- if (typeof options.onConfirm === 'function') {
158
- options.onConfirm(selected);
171
+ if (typeof options.withSelected === 'function') {
172
+ options.withSelected(selected);
159
173
  }
160
174
 
161
175
  modal.startLoading();
162
176
 
163
- return this.fetch[actionMethod](
164
- options.actionPath,
165
- {
166
- ids: selected.map((model) => model.id),
167
- ...fetchParams,
168
- },
169
- fetchOptions
170
- )
171
- .then((response) => {
172
- this.notifications.success(response.message ?? options.successNotification ?? `${count} ${pluralize(modelName, count)} were updated successfully.`);
173
-
174
- if (typeof options.onSuccess === 'function') {
175
- options.onSuccess(selected);
176
- }
177
- })
178
- .catch((error) => {
179
- this.notifications.serverError(error);
177
+ try {
178
+ const response = await this.fetch.request(
179
+ options.actionPath,
180
+ actionMethod,
181
+ {
182
+ body: JSON.stringify({
183
+ ids: selected.map((model) => model.id),
184
+ ...fetchParams,
185
+ }),
186
+ },
187
+ fetchOptions
188
+ );
180
189
 
181
- if (typeof options.onError === 'function') {
182
- options.onError(error, selected);
183
- }
184
- })
185
- .finally(() => {
186
- if (typeof options.callback === 'function') {
187
- options.callback(selected);
188
- }
189
- });
190
+ this.notifications.success(response.message ?? successMessage);
191
+ if (typeof options.onSuccess === 'function') {
192
+ options.onSuccess(selected);
193
+ }
194
+
195
+ return response;
196
+ } catch (error) {
197
+ console.error(error.message, error);
198
+ this.notifications.serverError(error);
199
+
200
+ if (typeof options.onError === 'function') {
201
+ options.onError(error, selected);
202
+ }
203
+ } finally {
204
+ if (typeof callback === 'function') {
205
+ callback(selected);
206
+ }
207
+ }
190
208
  },
191
209
  ...options,
192
210
  });
@@ -7,6 +7,7 @@ import { computed, get } from '@ember/object';
7
7
  import { isBlank } from '@ember/utils';
8
8
  import { alias } from '@ember/object/computed';
9
9
  import { storageFor } from 'ember-local-storage';
10
+ import { debug } from '@ember/debug';
10
11
 
11
12
  export default class CurrentUserService extends Service.extend(Evented) {
12
13
  @service session;
@@ -17,6 +18,7 @@ export default class CurrentUserService extends Service.extend(Evented) {
17
18
  @service intl;
18
19
 
19
20
  @tracked user = { id: 'anon' };
21
+ @tracked userSnapshot = { id: 'anon' };
20
22
  @tracked company = {};
21
23
  @tracked permissions = [];
22
24
  @tracked organizations = [];
@@ -24,16 +26,16 @@ export default class CurrentUserService extends Service.extend(Evented) {
24
26
  @tracked locale = 'en-us';
25
27
 
26
28
  @storageFor('user-options') options;
27
- @alias('user.id') id;
28
- @alias('user.name') name;
29
- @alias('user.phone') phone;
30
- @alias('user.email') email;
31
- @alias('user.avatar_url') avatarUrl;
32
- @alias('user.is_admin') isAdmin;
33
- @alias('user.company_uuid') companyId;
34
- @alias('user.company_name') companyName;
35
- @alias('user.role_name') roleName;
36
- @alias('user.role') role;
29
+ @alias('userSnapshot.id') id;
30
+ @alias('userSnapshot.name') name;
31
+ @alias('userSnapshot.phone') phone;
32
+ @alias('userSnapshot.email') email;
33
+ @alias('userSnapshot.avatar_url') avatarUrl;
34
+ @alias('userSnapshot.is_admin') isAdmin;
35
+ @alias('userSnapshot.company_uuid') companyId;
36
+ @alias('userSnapshot.company_name') companyName;
37
+ @alias('userSnapshot.role_name') roleName;
38
+ @alias('userSnapshot.role') role;
37
39
 
38
40
  @computed('id') get optionsPrefix() {
39
41
  return `${this.id}:`;
@@ -62,7 +64,10 @@ export default class CurrentUserService extends Service.extend(Evented) {
62
64
  async load() {
63
65
  if (this.session.isAuthenticated) {
64
66
  const user = await this.store.findRecord('user', 'me');
67
+ const snapshot = await this.getUserSnapshot(user);
68
+
65
69
  this.set('user', user);
70
+ this.set('userSnapshot', snapshot);
66
71
  this.trigger('user.loaded', user);
67
72
 
68
73
  // Set permissions
@@ -85,9 +90,11 @@ export default class CurrentUserService extends Service.extend(Evented) {
85
90
 
86
91
  try {
87
92
  const user = await this.store.queryRecord('user', { me: true });
93
+ const snapshot = await this.getUserSnapshot(user);
88
94
 
89
95
  // Set current user
90
96
  this.set('user', user);
97
+ this.set('userSnapshot', snapshot);
91
98
  this.trigger('user.loaded', user);
92
99
 
93
100
  // Set permissions
@@ -116,7 +123,7 @@ export default class CurrentUserService extends Service.extend(Evented) {
116
123
 
117
124
  return user;
118
125
  } catch (error) {
119
- console.log(error.message);
126
+ debug(`Error loading current user : ${error.message}`);
120
127
  throw error;
121
128
  }
122
129
  }
@@ -257,4 +264,20 @@ export default class CurrentUserService extends Service.extend(Evented) {
257
264
  filledOption(key) {
258
265
  return !isBlank(this.getOption(key));
259
266
  }
267
+
268
+ async getUserSnapshot(user) {
269
+ const role = await user.get('role');
270
+ const snapshot = user.serialize({ includeId: true });
271
+
272
+ return {
273
+ ...snapshot,
274
+ id: snapshot.uuid,
275
+ company_name: user.get('company_name'),
276
+ role_name: user.get('role_name'),
277
+ role: {
278
+ ...role.serialize({ includeId: true }),
279
+ id: role.get('id'),
280
+ },
281
+ };
282
+ }
260
283
  }
@@ -19,7 +19,7 @@ import isEmptyObject from '../utils/is-empty-object';
19
19
  import fetch from 'fetch';
20
20
 
21
21
  if (isBlank(config.API.host)) {
22
- config.API.host = `${window.location.protocol}//${window.location.hostname}:8000`;
22
+ config.API.host = `${window.location.protocol}//${window.location.hostname}`;
23
23
  }
24
24
 
25
25
  export default class FetchService extends Service {
@@ -612,7 +612,7 @@ export default class FetchService extends Service {
612
612
  const headers = Object.assign(this.getHeaders(), options.headers ?? {});
613
613
  const method = options.method ?? 'GET';
614
614
  const credentials = options.credentials ?? this.credentials;
615
- const baseUrl = `${options.host || this.host}/${options.namespace || this.namespace}`;
615
+ const baseUrl = options.externalRequest === true ? '' : `${options.host ?? this.host}/${options.namespace || this.namespace}`;
616
616
  const isReadOnlyRequest = ['GET', 'HEAD'].includes(method.toUpperCase());
617
617
  const params = isReadOnlyRequest && !isEmptyObject(query) ? `?${new URLSearchParams(query).toString()}` : '';
618
618
  const body = !isReadOnlyRequest ? JSON.stringify(query) : {};
@@ -628,7 +628,7 @@ export default class FetchService extends Service {
628
628
  }
629
629
 
630
630
  return new Promise((resolve, reject) => {
631
- return fetch(`${baseUrl}/${path}${params}`, fetchOptions)
631
+ return fetch(`${baseUrl ? baseUrl + '/' : ''}${path}${params}`, fetchOptions)
632
632
  .then((response) => {
633
633
  options.fileName = this.getFilenameFromResponse(response, options.fileName);
634
634
  options.mimeType = this.getMimeTypeFromResponse(response, options.mimeType);
@@ -170,7 +170,6 @@ export default class FiltersService extends Service {
170
170
 
171
171
  queryParams[qp] = get(controller, qp);
172
172
  }
173
-
174
173
  return queryParams;
175
174
  }
176
175
  }
@@ -0,0 +1,8 @@
1
+ import { camelize } from '@ember/string';
2
+
3
+ export default function createNotificationKey(definition, name) {
4
+ const withoutSlashes = definition.replace(/[\W_]+/g, '');
5
+ const key = `${camelize(withoutSlashes)}__${camelize(name)}`;
6
+
7
+ return key;
8
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Check whether the current browser session is authenticated.
3
+ *
4
+ * Reads the session payload Ember Simple Auth stores in
5
+ * localStorage (`ember_simple_auth-session`) and returns `true`
6
+ * only if it finds a bearer token.
7
+ *
8
+ * @return {boolean} `true` when a user appears to be logged in, otherwise `false`.
9
+ */
10
+ export default function isAuthenticated() {
11
+ try {
12
+ const rawSession = window.localStorage.getItem('ember_simple_auth-session');
13
+ if (!rawSession) {
14
+ return false; // nothing stored
15
+ }
16
+
17
+ const { authenticated } = JSON.parse(rawSession);
18
+
19
+ // Basic check: token must be a non-empty string
20
+ return typeof authenticated?.token === 'string' && authenticated.token.trim().length > 0;
21
+ } catch {
22
+ // Malformed JSON or restricted access to localStorage
23
+ return false;
24
+ }
25
+ }
@@ -1,5 +1,6 @@
1
- import loadExtensions from '../utils/load-extensions';
2
- import fleetbaseApiFetch from '../utils/fleetbase-api-fetch';
1
+ import loadExtensions from './load-extensions';
2
+ import fleetbaseApiFetch from './fleetbase-api-fetch';
3
+ import isAuthenticated from './is-authenticated';
3
4
 
4
5
  export default async function loadInstalledExtensions(additionalCoreEngines = []) {
5
6
  const CORE_ENGINES = [
@@ -11,7 +12,12 @@ export default async function loadInstalledExtensions(additionalCoreEngines = []
11
12
  ...additionalCoreEngines,
12
13
  ];
13
14
  const INDEXED_ENGINES = await loadExtensions();
14
- const INSTALLED_ENGINES = await fleetbaseApiFetch('get', 'engines', {}, { namespace: '~registry/v1', fallbackResponse: [] });
15
+ // const INSTALLED_ENGINES = await fleetbaseApiFetch('get', 'engines', {}, { namespace: '~registry/v1', fallbackResponse: [] });
16
+
17
+ let INSTALLED_ENGINES = [];
18
+ if (isAuthenticated()) {
19
+ INSTALLED_ENGINES = await fleetbaseApiFetch('GET', 'engines', {}, { namespace: '~registry/v1', fallbackResponse: [] });
20
+ }
15
21
 
16
22
  const isInstalledEngine = (engineName) => {
17
23
  return CORE_ENGINES.includes(engineName) || INSTALLED_ENGINES.find((pkg) => pkg.name === engineName);
@@ -0,0 +1,15 @@
1
+ import { later } from '@ember/runloop';
2
+
3
+ export default function timeout(ms = 300, options = {}) {
4
+ const response = options.response ? options.response : true;
5
+
6
+ return new Promise((resolve) => {
7
+ later(
8
+ this,
9
+ () => {
10
+ resolve(response);
11
+ },
12
+ ms
13
+ );
14
+ });
15
+ }
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/utils/create-notification-key';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/utils/is-authenticated';
@@ -0,0 +1 @@
1
+ export { default } from '@fleetbase/ember-core/utils/timeout';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/ember-core",
3
- "version": "0.2.22",
3
+ "version": "0.3.0",
4
4
  "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
5
5
  "keywords": [
6
6
  "fleetbase-core",