@things-factory/auth-ui 6.1.185 → 6.1.186

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 (29) hide show
  1. package/client/components/abstract-auth-page.ts +10 -0
  2. package/client/index.ts +11 -0
  3. package/client/pages/attribute/attribute-set-management.ts +1 -1
  4. package/client/pages/auth-provider/auth-provider-management.ts +355 -0
  5. package/client/pages/auth-provider/auth-provider.ts +394 -0
  6. package/client/route.ts +8 -0
  7. package/dist-client/components/abstract-auth-page.js +8 -0
  8. package/dist-client/components/abstract-auth-page.js.map +1 -1
  9. package/dist-client/index.js +10 -0
  10. package/dist-client/index.js.map +1 -1
  11. package/dist-client/pages/attribute/attribute-set-management.d.ts +0 -1
  12. package/dist-client/pages/attribute/attribute-set-management.js +1 -1
  13. package/dist-client/pages/attribute/attribute-set-management.js.map +1 -1
  14. package/dist-client/pages/auth-provider/auth-provider-management.d.ts +55 -0
  15. package/dist-client/pages/auth-provider/auth-provider-management.js +349 -0
  16. package/dist-client/pages/auth-provider/auth-provider-management.js.map +1 -0
  17. package/dist-client/pages/auth-provider/auth-provider.d.ts +1 -0
  18. package/dist-client/pages/auth-provider/auth-provider.js +381 -0
  19. package/dist-client/pages/auth-provider/auth-provider.js.map +1 -0
  20. package/dist-client/route.js +6 -0
  21. package/dist-client/route.js.map +1 -1
  22. package/dist-client/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +5 -5
  24. package/things-factory.config.js +3 -1
  25. package/translations/en.json +4 -0
  26. package/translations/ja.json +4 -0
  27. package/translations/ko.json +4 -0
  28. package/translations/ms.json +4 -0
  29. package/translations/zh.json +4 -0
@@ -193,6 +193,8 @@ export abstract class AbstractAuthPage extends localize(i18next)(LitElement) {
193
193
  }
194
194
 
195
195
  get links() {
196
+ const ssoLinks = this.data?.ssoLinks || []
197
+
196
198
  return html`
197
199
  <a class="link" href="/auth/signup">
198
200
  <mwc-button icon="add_task"><ox-i18n msgid="field.sign up"></ox-i18n></mwc-button>
@@ -200,6 +202,14 @@ export abstract class AbstractAuthPage extends localize(i18next)(LitElement) {
200
202
  <a class="link" href="/auth/forgot-password">
201
203
  <mwc-button icon="lock_open"><ox-i18n msgid="field.forgot-password"></ox-i18n></mwc-button>
202
204
  </a>
205
+
206
+ ${ssoLinks.map(
207
+ sso => html`
208
+ <a class="link" href=${sso.link}>
209
+ <mwc-button icon="badge">${i18next.t('label.signin with', { title: sso.title })}</mwc-button>
210
+ </a>
211
+ `
212
+ )}
203
213
  `
204
214
  }
205
215
 
package/client/index.ts CHANGED
@@ -56,6 +56,17 @@ export async function setAuthManagementMenus(credential) {
56
56
  }
57
57
  }
58
58
  })
59
+
60
+ store.dispatch({
61
+ type: ADD_MORENDA,
62
+ morenda: {
63
+ icon: html` <mwc-icon>badge</mwc-icon> `,
64
+ name: html` <ox-i18n msgid="text.auth-provider management"></ox-i18n> `,
65
+ action: () => {
66
+ navigate('auth-providers')
67
+ }
68
+ }
69
+ })
59
70
  }
60
71
 
61
72
  if (await hasPrivilege({ superUserGranted: true })) {
@@ -52,7 +52,7 @@ export class AttributeSetManagementPage extends connect(store)(localize(i18next)
52
52
  this.grist.toggleHeadroom()
53
53
  }
54
54
  },
55
- help: 'dataset/attribute',
55
+ // help: 'system/attribute',
56
56
  actions: [
57
57
  {
58
58
  title: i18next.t('button.save'),
@@ -0,0 +1,355 @@
1
+ import '@operato/data-grist'
2
+ import './auth-provider.js'
3
+
4
+ import gql from 'graphql-tag'
5
+ import { css, html } from 'lit'
6
+ import { customElement, property, query, state } from 'lit/decorators.js'
7
+ import { connect } from 'pwa-helpers/connect-mixin'
8
+
9
+ import { getEditor, getRenderer, ColumnConfig, DataGrist, FetchOption, SortersControl } from '@operato/data-grist'
10
+ import { client } from '@operato/graphql'
11
+ import { i18next, localize } from '@operato/i18n'
12
+ import { notify, openPopup } from '@operato/layout'
13
+ import { PageView, store } from '@operato/shell'
14
+ import { CommonButtonStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
15
+ import { isMobileDevice, sleep } from '@operato/utils'
16
+ import { OxPopup } from '@operato/popup'
17
+
18
+ @customElement('auth-provider-management')
19
+ export class AuthProviderManagementPage extends connect(store)(localize(i18next)(PageView)) {
20
+ static styles = [
21
+ ScrollbarStyles,
22
+ CommonGristStyles,
23
+ css`
24
+ :host {
25
+ display: flex;
26
+
27
+ width: 100%;
28
+
29
+ --grid-record-emphasized-background-color: red;
30
+ --grid-record-emphasized-color: yellow;
31
+ }
32
+ `
33
+ ]
34
+
35
+ @state() gristConfig: any
36
+ @state() mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
37
+ @state() authProviderTypes: { type: string; description?: string; help?: string; parameterSpec?: any[] }[] = []
38
+
39
+ @query('ox-grist') private grist!: DataGrist
40
+ @query('#sorter-control') private sortersControl!: OxPopup
41
+
42
+ get context() {
43
+ return {
44
+ title: i18next.t('title.auth-provider'),
45
+ search: {
46
+ handler: (search: string) => {
47
+ this.grist.searchText = search
48
+ },
49
+ value: this.grist?.searchText || ''
50
+ },
51
+ filter: {
52
+ handler: () => {
53
+ this.grist.toggleHeadroom()
54
+ }
55
+ },
56
+ // help: 'auth/auth-provider',
57
+ actions: [
58
+ {
59
+ title: i18next.t('button.save'),
60
+ action: this.updateAuthProvider.bind(this),
61
+ ...CommonButtonStyles.save
62
+ },
63
+ {
64
+ title: i18next.t('button.delete'),
65
+ action: this.deleteAuthProvider.bind(this),
66
+ ...CommonButtonStyles.delete
67
+ }
68
+ ]
69
+ }
70
+ }
71
+
72
+ render() {
73
+ const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
74
+
75
+ return html`
76
+ <ox-grist
77
+ .mode=${mode}
78
+ .config=${this.gristConfig}
79
+ .fetchHandler=${this.fetchHandler.bind(this)}
80
+ ?url-params-sensitive=${this.active}
81
+ >
82
+ <div slot="headroom">
83
+ <div id="filters">
84
+ <ox-filters-form autofocus without-search></ox-filters-form>
85
+ </div>
86
+
87
+ <div id="sorters">
88
+ Sort
89
+ <mwc-icon
90
+ @click=${e => {
91
+ const target = e.currentTarget
92
+ this.sortersControl.open({
93
+ right: 0,
94
+ top: target.offsetTop + target.offsetHeight
95
+ })
96
+ }}
97
+ >expand_more</mwc-icon
98
+ >
99
+ <ox-popup id="sorter-control">
100
+ <ox-sorters-control> </ox-sorters-control>
101
+ </ox-popup>
102
+ </div>
103
+ </div>
104
+ </ox-grist>
105
+ `
106
+ }
107
+
108
+ async pageInitialized(lifecycle) {
109
+ this.gristConfig = {
110
+ list: {
111
+ fields: ['entity', 'description']
112
+ },
113
+ columns: [
114
+ { type: 'gutter', gutterName: 'sequence' },
115
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
116
+ {
117
+ type: 'gutter',
118
+ gutterName: 'button',
119
+ title: i18next.t('title.auth-provider'),
120
+ icon: 'sync',
121
+ handlers: {
122
+ click: (columns, data, column, record, rowIndex) => {
123
+ if (!record.id) return
124
+ this.synchronizeUsers(record.id)
125
+ }
126
+ }
127
+ },
128
+ {
129
+ type: 'select',
130
+ name: 'type',
131
+ header: i18next.t('field.type'),
132
+ record: {
133
+ editable: true,
134
+ options: () => ['', ...this.authProviderTypes.map(({ type }) => type)]
135
+ },
136
+ filter: 'search',
137
+ sortable: true,
138
+ width: 150
139
+ },
140
+ {
141
+ type: 'boolean',
142
+ name: 'active',
143
+ header: i18next.t('field.active'),
144
+ record: {
145
+ editable: true
146
+ },
147
+ sortable: true,
148
+ width: 150
149
+ },
150
+ {
151
+ type: 'string',
152
+ name: 'tenantId',
153
+ header: i18next.t('label.tenant-id'),
154
+ record: {
155
+ editable: true
156
+ },
157
+ width: 200
158
+ },
159
+ {
160
+ type: 'string',
161
+ name: 'clientId',
162
+ header: i18next.t('label.client-id'),
163
+ record: {
164
+ editable: true
165
+ },
166
+ width: 200
167
+ },
168
+ {
169
+ type: 'string',
170
+ name: 'clientSecret',
171
+ header: i18next.t('label.client-secret'),
172
+ record: {
173
+ editable: true
174
+ },
175
+ width: 200
176
+ },
177
+ {
178
+ type: 'parameters',
179
+ name: 'params',
180
+ header: i18next.t('field.params'),
181
+ record: {
182
+ editable: true,
183
+ options: async (value, column, record, row, field) => {
184
+ const {
185
+ description,
186
+ help,
187
+ parameterSpec: spec
188
+ } = record.type ? this.authProviderTypes[record.type] : ({} as any)
189
+ const context = this.grist
190
+
191
+ return { description, help, spec, context }
192
+ },
193
+ renderer: 'json5'
194
+ },
195
+ width: 200
196
+ },
197
+ {
198
+ type: 'datetime',
199
+ name: 'updatedAt',
200
+ header: i18next.t('field.updated_at'),
201
+ record: {
202
+ editable: false
203
+ },
204
+ sortable: true,
205
+ width: 180
206
+ }
207
+ ],
208
+ rows: {
209
+ selectable: {
210
+ multiple: true
211
+ }
212
+ },
213
+ sorters: [
214
+ {
215
+ name: 'type'
216
+ }
217
+ ]
218
+ }
219
+ }
220
+
221
+ async fetchHandler({ page, limit, sortings = [], filters = [] }: FetchOption) {
222
+ const response = await client.query({
223
+ query: gql`
224
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
225
+ authProviderTypes {
226
+ items {
227
+ type
228
+ description
229
+ help
230
+ parameterSpec {
231
+ type
232
+ label
233
+ name
234
+ placeholder
235
+ property
236
+ }
237
+ }
238
+ total
239
+ }
240
+
241
+ responses: authProviders(filters: $filters, pagination: $pagination, sortings: $sortings) {
242
+ items {
243
+ id
244
+ type
245
+ active
246
+ tenantId
247
+ clientId
248
+ clientSecret
249
+ params
250
+ updatedAt
251
+ }
252
+ total
253
+ }
254
+ }
255
+ `,
256
+ variables: {
257
+ filters,
258
+ pagination: { page, limit },
259
+ sortings
260
+ }
261
+ })
262
+
263
+ this.authProviderTypes = response.data.authProviderTypes.items
264
+
265
+ return {
266
+ total: response.data.responses.total || 0,
267
+ records: response.data.responses.items || []
268
+ }
269
+ }
270
+
271
+ private async deleteAuthProvider() {
272
+ if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
273
+ const ids = this.grist.selected.map(record => record.id)
274
+ if (ids && ids.length > 0) {
275
+ const response = await client.mutate({
276
+ mutation: gql`
277
+ mutation ($ids: [String!]!) {
278
+ deleteAuthProviders(ids: $ids)
279
+ }
280
+ `,
281
+ variables: {
282
+ ids
283
+ }
284
+ })
285
+
286
+ if (!response.errors) {
287
+ this.grist.fetch()
288
+ notify({
289
+ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
290
+ })
291
+ }
292
+ }
293
+ }
294
+ }
295
+
296
+ private async updateAuthProvider() {
297
+ let patches = this.grist.dirtyRecords
298
+ if (patches && patches.length) {
299
+ patches = patches.map(patch => {
300
+ let patchField: any = patch.id ? { id: patch.id } : {}
301
+ const dirtyFields = patch.__dirtyfields__
302
+ for (let key in dirtyFields) {
303
+ patchField[key] = dirtyFields[key].after
304
+ }
305
+ if (patchField['reportTemplate'] instanceof FileList) {
306
+ patchField['reportTemplate'] = patchField['reportTemplate'][0]
307
+ }
308
+ patchField.cuFlag = patch.__dirty__
309
+
310
+ return patchField
311
+ })
312
+
313
+ const response = await client.mutate({
314
+ mutation: gql`
315
+ mutation ($patches: [AuthProviderPatch!]!) {
316
+ updateMultipleAuthProvider(patches: $patches) {
317
+ id
318
+ }
319
+ }
320
+ `,
321
+ variables: {
322
+ patches
323
+ }
324
+ })
325
+
326
+ if (!response.errors) {
327
+ this.grist.fetch()
328
+ }
329
+ }
330
+ }
331
+
332
+ private async synchronizeUsers(id: string) {
333
+ if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.synchronize') }))) {
334
+ if (id && id.length > 0) {
335
+ const response = await client.mutate({
336
+ mutation: gql`
337
+ mutation ($id: String!) {
338
+ synchronizeAuthProviderUsers(id: $id)
339
+ }
340
+ `,
341
+ variables: {
342
+ id
343
+ }
344
+ })
345
+
346
+ if (!response.errors) {
347
+ this.grist.fetch()
348
+ notify({
349
+ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.synchronize') })
350
+ })
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }