@things-factory/auth-ui 8.0.0-beta.9 → 8.0.2

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.
Files changed (100) hide show
  1. package/client/auth-style-sign.ts +194 -0
  2. package/client/bootstrap.ts +51 -0
  3. package/client/components/abstract-auth-page.ts +301 -0
  4. package/client/components/abstract-password-reset.ts +168 -0
  5. package/client/components/abstract-sign.ts +127 -0
  6. package/client/components/change-password.ts +153 -0
  7. package/client/components/contact-us.ts +113 -0
  8. package/client/components/create-domain-popup.ts +141 -0
  9. package/client/components/create-role.ts +123 -0
  10. package/client/components/create-user.ts +95 -0
  11. package/client/components/credential-manager.ts +64 -0
  12. package/client/components/delete-user-popup.ts +117 -0
  13. package/client/components/domain-switch.ts +127 -0
  14. package/client/components/invite-customer.ts +104 -0
  15. package/client/components/invite-user.ts +96 -0
  16. package/client/components/my-login-history.ts +101 -0
  17. package/client/components/ownership-transfer-popup.ts +110 -0
  18. package/client/components/partner-info-card.ts +89 -0
  19. package/client/components/partner-role-editor.ts +153 -0
  20. package/client/components/profile-component.ts +332 -0
  21. package/client/components/role-edit-form.ts +92 -0
  22. package/client/components/role-privilege-editor.ts +267 -0
  23. package/client/components/role-selector.ts +102 -0
  24. package/client/components/user-role-editor.ts +499 -0
  25. package/client/constants/application.ts +9 -0
  26. package/client/constants/index.ts +1 -0
  27. package/client/entries/auth/activate.ts +272 -0
  28. package/client/entries/auth/checkin.ts +190 -0
  29. package/client/entries/auth/forgot-password.ts +103 -0
  30. package/client/entries/auth/reset-password.ts +22 -0
  31. package/client/entries/auth/result.ts +193 -0
  32. package/client/entries/auth/signin.ts +18 -0
  33. package/client/entries/auth/signup.ts +109 -0
  34. package/client/entries/auth/unlock-user.ts +22 -0
  35. package/client/entries/oauth2/oauth2-decision-error-page.ts +50 -0
  36. package/client/entries/oauth2/oauth2-decision-page.ts +196 -0
  37. package/client/entries/public/home.ts +246 -0
  38. package/client/index.ts +124 -0
  39. package/client/pages/app-binding/app-binding.ts +423 -0
  40. package/client/pages/app-binding/app-bindings.ts +171 -0
  41. package/client/pages/appliance/appliance.ts +452 -0
  42. package/client/pages/appliance/home.ts +177 -0
  43. package/client/pages/appliance/register.ts +183 -0
  44. package/client/pages/application/application.ts +428 -0
  45. package/client/pages/application/applications.ts +182 -0
  46. package/client/pages/application/register.ts +211 -0
  47. package/client/pages/attribute/attribute-set-item-list.ts +237 -0
  48. package/client/pages/attribute/attribute-set-management.ts +282 -0
  49. package/client/pages/auth-provider/auth-provider-management.ts +381 -0
  50. package/client/pages/domain/domain-management.ts +410 -0
  51. package/client/pages/partner/partner-management.ts +112 -0
  52. package/client/pages/profile.ts +32 -0
  53. package/client/pages/role/role-management.ts +134 -0
  54. package/client/pages/user/user-management.ts +224 -0
  55. package/client/route.ts +67 -0
  56. package/client/themes/auth-theme.css +65 -0
  57. package/client/utils/password-rule.ts +37 -0
  58. package/dist-client/components/abstract-auth-page.js +10 -10
  59. package/dist-client/components/abstract-auth-page.js.map +1 -1
  60. package/dist-client/components/abstract-password-reset.d.ts +2 -1
  61. package/dist-client/components/abstract-password-reset.js +14 -7
  62. package/dist-client/components/abstract-password-reset.js.map +1 -1
  63. package/dist-client/components/abstract-sign.js +11 -12
  64. package/dist-client/components/abstract-sign.js.map +1 -1
  65. package/dist-client/components/contact-us.d.ts +1 -1
  66. package/dist-client/components/contact-us.js +7 -10
  67. package/dist-client/components/contact-us.js.map +1 -1
  68. package/dist-client/components/create-user.js +5 -28
  69. package/dist-client/components/create-user.js.map +1 -1
  70. package/dist-client/components/invite-user.js +11 -19
  71. package/dist-client/components/invite-user.js.map +1 -1
  72. package/dist-client/components/ownership-transfer-popup.js +3 -3
  73. package/dist-client/components/ownership-transfer-popup.js.map +1 -1
  74. package/dist-client/components/profile-component.d.ts +1 -5
  75. package/dist-client/components/profile-component.js +4 -64
  76. package/dist-client/components/profile-component.js.map +1 -1
  77. package/dist-client/components/role-privilege-editor.js +1 -2
  78. package/dist-client/components/role-privilege-editor.js.map +1 -1
  79. package/dist-client/components/user-role-editor.js +18 -18
  80. package/dist-client/components/user-role-editor.js.map +1 -1
  81. package/dist-client/entries/auth/checkin.js +1 -1
  82. package/dist-client/entries/auth/checkin.js.map +1 -1
  83. package/dist-client/entries/auth/forgot-password.js +2 -11
  84. package/dist-client/entries/auth/forgot-password.js.map +1 -1
  85. package/dist-client/entries/auth/signup.js +7 -13
  86. package/dist-client/entries/auth/signup.js.map +1 -1
  87. package/dist-client/index.js +1 -1
  88. package/dist-client/index.js.map +1 -1
  89. package/dist-client/pages/user/user-management.d.ts +1 -5
  90. package/dist-client/pages/user/user-management.js +7 -6
  91. package/dist-client/pages/user/user-management.js.map +1 -1
  92. package/dist-client/tsconfig.tsbuildinfo +1 -1
  93. package/dist-server/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +12 -12
  95. package/server/index.ts +0 -0
  96. package/translations/en.json +2 -6
  97. package/translations/ja.json +2 -6
  98. package/translations/ko.json +2 -6
  99. package/translations/ms.json +2 -6
  100. package/translations/zh.json +2 -6
@@ -0,0 +1,410 @@
1
+ import '@material/web/icon/icon.js'
2
+
3
+ import '@operato/data-grist/ox-grist.js'
4
+ import '@operato/data-grist/ox-filters-form.js'
5
+ import '@operato/context/ox-context-page-toolbar.js'
6
+ import '@operato/attribute/ox-attribute-view.js'
7
+ import '../../components/create-domain-popup'
8
+
9
+ import gql from 'graphql-tag'
10
+ import { css, html } from 'lit'
11
+ import { customElement, property, query } from 'lit/decorators.js'
12
+ import moment from '@operato/moment-timezone-es'
13
+ import { connect } from 'pwa-helpers/connect-mixin'
14
+
15
+ import { DataGrist } from '@operato/data-grist/ox-grist.js'
16
+ import { client } from '@operato/graphql'
17
+ import { i18next, localize } from '@operato/i18n'
18
+ import { openPopup } from '@operato/layout'
19
+ import { PageView, store } from '@operato/shell'
20
+ import { CommonHeaderStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
21
+ import { isMobileDevice } from '@operato/utils'
22
+ import { p13n } from '@operato/p13n'
23
+ import { FetchOption } from '@operato/data-grist'
24
+
25
+ function getSystemTimeZone(): string {
26
+ try {
27
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
28
+ if (!timeZone) {
29
+ throw new Error('Unable to resolve timeZone')
30
+ }
31
+ return timeZone
32
+ } catch (e) {
33
+ console.warn('Failed to get system timeZone, falling back to UTC.', e)
34
+ return 'UTC'
35
+ }
36
+ }
37
+
38
+ const DEFAULT_TZ = getSystemTimeZone()
39
+ const TIMEZONE_OPTIONS = ['', DEFAULT_TZ, ...moment.tz.names().filter(tz => tz !== DEFAULT_TZ)]
40
+
41
+ @customElement('domain-management')
42
+ export class DomainManagement extends connect(store)(p13n(localize(i18next)(PageView))) {
43
+ static styles = [
44
+ CommonGristStyles,
45
+ CommonHeaderStyles,
46
+ ScrollbarStyles,
47
+ css`
48
+ :host {
49
+ display: flex;
50
+ flex-direction: column;
51
+
52
+ overflow: hidden;
53
+ }
54
+
55
+ ox-grist {
56
+ overflow-y: auto;
57
+ flex: 1;
58
+ }
59
+ `
60
+ ]
61
+
62
+ @property({ type: Boolean }) active: boolean = false
63
+ @property({ type: Object }) config: any
64
+ @property({ type: String }) mode: 'CARD' | 'LIST' | 'GRID' = isMobileDevice() ? 'CARD' : 'GRID'
65
+
66
+ @query('ox-grist') private grist!: DataGrist
67
+
68
+ get context() {
69
+ return {
70
+ title: i18next.t('text.domain management'),
71
+ search: {
72
+ handler: (search: string) => {
73
+ this.grist.searchText = search
74
+ },
75
+ value: this.grist?.searchText || ''
76
+ },
77
+ filter: {
78
+ handler: () => {
79
+ this.grist.toggleHeadroom()
80
+ }
81
+ },
82
+ actions: [
83
+ {
84
+ title: i18next.t('button.add'),
85
+ action: this.onCreateDomain.bind(this),
86
+ icon: 'add'
87
+ },
88
+ {
89
+ title: i18next.t('button.save'),
90
+ action: this.onUpdateDomains.bind(this),
91
+ icon: 'save'
92
+ }
93
+ ],
94
+ toolbar: false
95
+ }
96
+ }
97
+
98
+ render() {
99
+ return html`
100
+ <ox-grist
101
+ .mode=${this.mode}
102
+ .config=${this.config}
103
+ .fetchHandler=${this.fetchHandler.bind(this)}
104
+ .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}
105
+ >
106
+ <div slot="headroom" class="header">
107
+ <ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
108
+ </div>
109
+
110
+ <ox-grist-personalizer slot="setting"></ox-grist-personalizer>
111
+ </ox-grist>
112
+ `
113
+ }
114
+
115
+ async pageInitialized() {
116
+ this.config = {
117
+ rows: {
118
+ appendable: false
119
+ },
120
+ columns: [
121
+ { type: 'gutter', gutterName: 'sequence' },
122
+ {
123
+ type: 'gutter',
124
+ gutterName: 'button',
125
+ icon: 'assignment',
126
+ title: i18next.t('title.open attributes view'),
127
+ handlers: {
128
+ click: async (columns, data, column, record, rowIndex) => {
129
+ const attributeSet = await this.getAttributeSetForDomain()
130
+
131
+ openPopup(
132
+ html`
133
+ <ox-attribute-view
134
+ .attributeSet=${attributeSet}
135
+ .value=${record.attributes}
136
+ style="background-color: white;"
137
+ ></ox-attribute-view>
138
+ `,
139
+ {
140
+ backdrop: true,
141
+ size: 'large',
142
+ title: record.name + ' ' + i18next.t('title.attributes')
143
+ }
144
+ )
145
+ }
146
+ }
147
+ },
148
+ {
149
+ type: 'gutter',
150
+ gutterName: 'button',
151
+ icon: 'domain',
152
+ title: i18next.t('title.move to this domain'),
153
+ handlers: {
154
+ click: (_columns, _data, _column, record, _rowIndex) => {
155
+ if (record.id) {
156
+ const subdomain = record.subdomain
157
+ location.pathname = `/domain/${subdomain}/users`
158
+ }
159
+ }
160
+ }
161
+ },
162
+ {
163
+ type: 'string',
164
+ name: 'name',
165
+ header: i18next.t('field.name'),
166
+ record: {
167
+ editable: false
168
+ },
169
+ filter: 'search',
170
+ sortable: true,
171
+ width: 200
172
+ },
173
+ {
174
+ type: 'string',
175
+ name: 'description',
176
+ header: i18next.t('field.description'),
177
+ record: {
178
+ editable: true
179
+ },
180
+ filter: 'search',
181
+ width: 200
182
+ },
183
+ {
184
+ type: 'string',
185
+ name: 'subdomain',
186
+ header: i18next.t('field.subdomain'),
187
+ record: {
188
+ editable: false
189
+ },
190
+ filter: 'search',
191
+ width: 200
192
+ },
193
+ {
194
+ type: 'resource-object',
195
+ name: 'parent',
196
+ header: i18next.t('field.parent-domain'),
197
+ record: {
198
+ editable: true,
199
+ options: {
200
+ title: i18next.t('text.select domain'),
201
+ queryName: 'domains',
202
+ nameField: 'name',
203
+ descriptionField: 'description',
204
+ select: [
205
+ { name: 'id', hidden: true },
206
+ { name: 'name', header: i18next.t('field.name'), filter: 'search' },
207
+ { name: 'description', header: i18next.t('field.description'), filter: 'search' }
208
+ ],
209
+ list: { fields: ['name', 'description'] }
210
+ }
211
+ },
212
+ width: 140
213
+ },
214
+ {
215
+ type: 'resource-object',
216
+ name: 'ownerUser',
217
+ header: i18next.t('field.owner-user'),
218
+ record: {
219
+ editable: true,
220
+ options: {
221
+ title: i18next.t('title.lookup user'),
222
+ queryName: 'users',
223
+ nameField: 'name',
224
+ descriptionField: 'email',
225
+ select: [
226
+ { name: 'id', hidden: true },
227
+ { name: 'name', header: i18next.t('field.name'), filter: 'search' },
228
+ { name: 'email', header: i18next.t('field.email'), filter: 'search' }
229
+ ],
230
+ filters: [{ name: 'userType', operator: 'eq', value: 'user' }],
231
+ list: { fields: ['name', 'email'] }
232
+ }
233
+ },
234
+ width: 140
235
+ },
236
+ {
237
+ type: 'select',
238
+ name: 'extType',
239
+ header: i18next.t('field.type'),
240
+ record: {
241
+ editable: true,
242
+ options: []
243
+ },
244
+ width: 70
245
+ },
246
+ {
247
+ type: 'select',
248
+ name: 'timezone',
249
+ header: i18next.t('field.timezone'),
250
+ record: {
251
+ editable: true,
252
+ options: TIMEZONE_OPTIONS
253
+ },
254
+ width: 120
255
+ },
256
+ {
257
+ type: 'attributes',
258
+ name: 'attributes',
259
+ header: i18next.t('field.attributes'),
260
+ record: {
261
+ editable: true,
262
+ options: async () => {
263
+ return {
264
+ objectified: true,
265
+ attributeSet: await this.getAttributeSetForDomain()
266
+ }
267
+ }
268
+ },
269
+ width: 120
270
+ },
271
+ {
272
+ type: 'datetime',
273
+ name: 'updatedAt',
274
+ header: i18next.t('field.updated_at'),
275
+ width: 180
276
+ }
277
+ ]
278
+ }
279
+ }
280
+
281
+ async pageUpdated(changes, lifecycle) {
282
+ if (this.active) {
283
+ await this.updateComplete
284
+
285
+ this.grist.fetch()
286
+ }
287
+ }
288
+
289
+ async fetchHandler({ page, limit, sortings = [], filters = [] }: FetchOption) {
290
+ const response = await client.query({
291
+ query: gql`
292
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
293
+ domains(filters: $filters, pagination: $pagination, sortings: $sortings) {
294
+ items {
295
+ id
296
+ name
297
+ description
298
+ subdomain
299
+ parent {
300
+ id
301
+ name
302
+ }
303
+ ownerUser {
304
+ id
305
+ name
306
+ email
307
+ }
308
+ extType
309
+ timezone
310
+ attributes
311
+ updatedAt
312
+ }
313
+ total
314
+ }
315
+ }
316
+ `,
317
+ variables: {
318
+ filters,
319
+ pagination: { page, limit },
320
+ sortings
321
+ }
322
+ })
323
+
324
+ return {
325
+ total: response.data.domains.total || 0,
326
+ records: response.data.domains.items || []
327
+ }
328
+ }
329
+
330
+ async onUpdateDomains() {
331
+ let patches = this.grist.dirtyRecords
332
+
333
+ if (patches && patches.length) {
334
+ patches = patches.map(domain => {
335
+ let patchField = domain.id ? { id: domain.id } : {}
336
+ const dirtyFields = domain.__dirtyfields__
337
+ for (let key in dirtyFields) {
338
+ if (key == 'ownerUser') {
339
+ const ownerUser = dirtyFields[key].after
340
+ patchField['owner'] = ownerUser.id || ''
341
+ } else {
342
+ patchField[key] = dirtyFields[key].after
343
+ }
344
+ }
345
+
346
+ return {
347
+ ...patchField
348
+ }
349
+ })
350
+
351
+ const response = await client.mutate({
352
+ mutation: gql`
353
+ mutation updateDomains($patches: [DomainPatch!]!) {
354
+ updateDomains(patches: $patches)
355
+ }
356
+ `,
357
+ variables: { patches }
358
+ })
359
+
360
+ if (!response.errors) {
361
+ this.grist.fetch()
362
+ return this.showToast(i18next.t('text.updated_successfully'))
363
+ }
364
+ }
365
+ }
366
+
367
+ async onCreateDomain() {
368
+ openPopup(
369
+ html`
370
+ <create-domain-popup
371
+ @fetch-data=${async () => {
372
+ this.grist.fetch()
373
+ }}
374
+ ></create-domain-popup>
375
+ `,
376
+ {
377
+ size: 'small',
378
+ title: `${i18next.t('title.create_domain')}`
379
+ }
380
+ )
381
+ }
382
+
383
+ async getAttributeSetForDomain() {
384
+ const response = await client.query({
385
+ query: gql`
386
+ query {
387
+ attributeSet: attributeSetByEntity(entity: "Domain") {
388
+ entity
389
+ description
390
+ items {
391
+ name
392
+ description
393
+ tag
394
+ type
395
+ active
396
+ hidden
397
+ options
398
+ }
399
+ }
400
+ }
401
+ `
402
+ })
403
+
404
+ return response.data?.attributeSet || {}
405
+ }
406
+
407
+ showToast(message) {
408
+ document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))
409
+ }
410
+ }
@@ -0,0 +1,112 @@
1
+ import '@things-factory/component-ui'
2
+ import '../../components/partner-info-card'
3
+ import '../../components/partner-role-editor'
4
+ import '../../components/invite-customer'
5
+
6
+ import gql from 'graphql-tag'
7
+ import { css, html } from 'lit'
8
+ import { customElement, property } from 'lit/decorators.js'
9
+ import { connect } from 'pwa-helpers/connect-mixin.js'
10
+
11
+ import { client, gqlContext } from '@operato/graphql'
12
+ import { i18next } from '@operato/i18n'
13
+ import { PageView, store } from '@operato/shell'
14
+
15
+ @customElement('partner-management')
16
+ class PartnerManagement extends connect(store)(PageView) {
17
+ static styles = [
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ background-color: var(--md-sys-color-background);
23
+ padding: var(--spacing-large);
24
+
25
+ overflow: auto;
26
+ }
27
+ h2 {
28
+ margin: var(--title-margin);
29
+ font: var(--title-font);
30
+ color: var(--title-text-color);
31
+ }
32
+ .role-editor {
33
+ flex: 1;
34
+ }
35
+ `
36
+ ]
37
+
38
+ @property({ type: Array }) customers: any[] = []
39
+ @property({ type: Array }) vendors: any[] = []
40
+
41
+ get context() {
42
+ return {
43
+ title: `${i18next.t('title.partners')}`,
44
+ help: 'operato-hub/partners'
45
+ }
46
+ }
47
+
48
+ render() {
49
+ const data = {
50
+ [i18next.t('label.customers')]: this.customers || [],
51
+ [i18next.t('label.vendors')]: this.vendors || []
52
+ }
53
+
54
+ return html`
55
+ <h2>partners</h2>
56
+
57
+ <quick-find-list
58
+ .data="${data}"
59
+ .contentRenderer="${(item, currentTabKey) => html`
60
+ ${currentTabKey === i18next.t('label.customers')
61
+ ? html`
62
+ <partner-role-editor
63
+ class="role-editor"
64
+ .customer="${item}"
65
+ @contractTerminated="${this.refreshPartners.bind(this)}"
66
+ ></partner-role-editor>
67
+ `
68
+ : html`<partner-info-card .partner="${item}"></partner-info-card>`}
69
+ `}"
70
+ >
71
+ </quick-find-list>
72
+
73
+ <invite-customer
74
+ .customers=${this.customers || []}
75
+ @invitationCompleted="${this.refreshPartners.bind(this)}"
76
+ ></invite-customer>
77
+ `
78
+ }
79
+
80
+ pageUpdated() {
81
+ if (this.active) {
82
+ this.refreshPartners()
83
+ } else {
84
+ /* this page is deactivated */
85
+ }
86
+ }
87
+
88
+ async refreshPartners() {
89
+ const response = await client.query({
90
+ query: gql`
91
+ query {
92
+ customers {
93
+ id
94
+ name
95
+ description
96
+ }
97
+ vendors {
98
+ id
99
+ name
100
+ description
101
+ }
102
+ }
103
+ `,
104
+ context: gqlContext()
105
+ })
106
+
107
+ if (!response.errors?.length) {
108
+ this.customers = response.data.customers
109
+ this.vendors = response.data.vendors
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,32 @@
1
+ import '../components/profile-component'
2
+
3
+ import { css, html } from 'lit'
4
+ import { customElement } from 'lit/decorators.js'
5
+
6
+ import { i18next, localize } from '@operato/i18n'
7
+ import { PageView } from '@operato/shell'
8
+ import { ScrollbarStyles } from '@operato/styles'
9
+
10
+ @customElement('auth-profile')
11
+ export class AuthProfile extends localize(i18next)(PageView) {
12
+ static styles = [
13
+ ScrollbarStyles,
14
+ css`
15
+ :host {
16
+ display: block;
17
+ background-color: var(--md-sys-color-background);
18
+ overflow-y: auto;
19
+ }
20
+ `
21
+ ]
22
+
23
+ get context() {
24
+ return {
25
+ title: i18next.t('text.auth profile')
26
+ }
27
+ }
28
+
29
+ render() {
30
+ return html` <profile-component></profile-component> `
31
+ }
32
+ }
@@ -0,0 +1,134 @@
1
+ import '@things-factory/component-ui'
2
+ import '../../components/create-role'
3
+ import '../../components/role-edit-form'
4
+ import '../../components/role-privilege-editor'
5
+
6
+ import gql from 'graphql-tag'
7
+ import { css, html } from 'lit'
8
+ import { customElement, property, query } from 'lit/decorators.js'
9
+ import { connect } from 'pwa-helpers/connect-mixin.js'
10
+
11
+ import { client, gqlContext } from '@operato/graphql'
12
+ import { i18next } from '@operato/i18n'
13
+ import { InheritedValueType, PageView, store } from '@operato/shell'
14
+
15
+ @customElement('role-management')
16
+ class RoleManagement extends connect(store)(PageView) {
17
+ static styles = [
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+ background-color: var(--md-sys-color-background);
23
+ padding: var(--spacing-large);
24
+ overflow: auto;
25
+ }
26
+
27
+ [subtitle] {
28
+ padding: var(--subtitle-padding);
29
+ font: var(--subtitle-font);
30
+ color: var(--subtitle-text-color);
31
+
32
+ text-transform: capitalize;
33
+ }
34
+
35
+ input {
36
+ display: block;
37
+ }
38
+
39
+ #roles-privileges {
40
+ flex: 1;
41
+ display: flex;
42
+ margin-top: var(--spacing-medium);
43
+ max-width: var(--content-container-max-width);
44
+ flex-direction: row;
45
+ overflow: auto;
46
+ border-top: var(--border-dim-color);
47
+ }
48
+ `
49
+ ]
50
+
51
+ @property({ type: String }) title: string = ''
52
+ @property({ type: Object }) role: any
53
+ @property({ type: Array }) roles: any[] = []
54
+ @property({ type: Object }) updateRoleObj: any
55
+
56
+ get context() {
57
+ return {
58
+ title: i18next.t('text.role_management'),
59
+ help: 'auth/roles'
60
+ }
61
+ }
62
+
63
+ render() {
64
+ const data = {
65
+ roles: this.roles || []
66
+ }
67
+
68
+ return html`
69
+ <create-role
70
+ @fetch-roles=${async () => {
71
+ this.roles = await this.fetchRoles()
72
+ }}
73
+ ></create-role>
74
+
75
+ <div subtitle>${i18next.t('text.registered roles')}</div>
76
+ <div id="roles-privileges">
77
+ <quick-find-list
78
+ .data="${data}"
79
+ .openHeaderRenderer=${role => html`
80
+ <role-edit-form .role="${role}" @roleChanged="${e => (this.updateRoleObj = e.detail)}"></role-edit-form>
81
+ `}
82
+ .contentRenderer="${(role, _currentTabKey) => html`
83
+ <role-privilege-editor
84
+ .updateRoleObj=${this.updateRoleObj || {}}
85
+ .role=${role}
86
+ @fetch-roles=${async () => {
87
+ this.roles = await this.fetchRoles()
88
+ await this.updateComplete
89
+ this.quickFindList.openByKey(role.id)
90
+ }}
91
+ ></role-privilege-editor>
92
+ `}"
93
+ ></quick-find-list>
94
+ </div>
95
+ `
96
+ }
97
+
98
+ @query('quick-find-list') quickFindList!: HTMLElement & { openByKey: (roleId: string) => void }
99
+
100
+ async pageUpdated(changes, lifecycle, before) {
101
+ if (this.active) {
102
+ this.roles = await this.fetchRoles()
103
+ this.role = this.roles && this.roles[0]
104
+ }
105
+ }
106
+
107
+ async fetchRoles() {
108
+ const response = await client.query({
109
+ query: gql`
110
+ query roles($sortings: [Sorting!], $inherited: InheritedValueType) {
111
+ roles(sortings: $sortings, inherited: $inherited) {
112
+ items {
113
+ id
114
+ name
115
+ description
116
+ privileges {
117
+ id
118
+ name
119
+ }
120
+ }
121
+ total
122
+ }
123
+ }
124
+ `,
125
+ variables: {
126
+ sortings: [{ name: 'name' }],
127
+ inherited: InheritedValueType.Include
128
+ },
129
+ context: gqlContext()
130
+ })
131
+
132
+ return response.data.roles.items
133
+ }
134
+ }