@humandialog/forms.svelte 0.6.2 → 1.1.1

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.
@@ -2,7 +2,8 @@
2
2
  import { FaUserPlus,
3
3
  FaUserMinus,
4
4
  FaPen,
5
- FaInfoCircle} from 'svelte-icons/fa'
5
+ FaInfoCircle,
6
+ FaChevronDown} from 'svelte-icons/fa'
6
7
 
7
8
  import Page from './page.svelte'
8
9
  import List from './components/list/list.svelte'
@@ -13,10 +14,14 @@
13
14
  import ListStaticProperty from './components/list/list.static.svelte'
14
15
  import Input from './components/inputbox.ltop.svelte';
15
16
  import Icon from "./components/icon.svelte";
17
+ import Combo from './components/combo/combo.svelte'
16
18
  import Modal from './modal.svelte'
17
19
  import Checkbox from './components/checkbox.svelte';
18
- import {Popover} from 'flowbite-svelte'
19
- import { reef } from '@humandialog/auth.svelte';
20
+ import {Popover, Alert} from 'flowbite-svelte'
21
+ import { reef, session, signInHRef } from '@humandialog/auth.svelte';
22
+ import { ComboSource } from './';
23
+ import {removeAt} from './utils'
24
+ import {showMenu} from './components/menu'
20
25
 
21
26
 
22
27
  // ==============================================================================
@@ -27,7 +32,7 @@
27
32
  export let refAttrib = "$ref";
28
33
  export let showFiles = false;
29
34
  //export let show_admin = true;
30
- export let appGroups = undefined;
35
+ export let showAccessRoles = false;
31
36
 
32
37
  // ===============================================================================
33
38
 
@@ -41,11 +46,34 @@
41
46
 
42
47
  let reef_users = [];
43
48
  let new_reef_user_id = 1;
49
+ let access_roles = [];
44
50
 
45
51
  let fake_users;
46
52
 
53
+ const authAccessKinds = [
54
+ { name: 'No auth access', key: 0 },
55
+ { name: 'Read auth access', key: 1 },
56
+ { name: 'Can invite others', key: 3 },
57
+ { name: 'Full auth access', key: 7 },
58
+ ]
59
+
60
+ const filesAccessKinds = [
61
+ { name: 'Can read files', key: 0 },
62
+ { name: 'Can write files', key: 1 },
63
+ { name: 'Full files access', key: 3 },
64
+ ]
65
+
47
66
  async function init()
48
67
  {
68
+ if(showAccessRoles)
69
+ {
70
+ let roles = await reef.get('/sys/list_access_roles')
71
+ access_roles = [];
72
+ if(roles)
73
+ roles.forEach( gname => access_roles.push({name: gname}));
74
+ }
75
+
76
+
49
77
  reef_users = [];
50
78
  if(users && Array.isArray(users) && users.length > 0)
51
79
  {
@@ -59,8 +87,8 @@
59
87
  [refAttrib]: u[refAttrib],
60
88
  auth_group: 0,
61
89
  files_group :0,
62
- app_group: 0,
63
- other: "",
90
+ acc_role: "",
91
+ //other: "",
64
92
  avatar_url : "",
65
93
  invitation_not_accepted: false,
66
94
  removed: false,
@@ -91,10 +119,12 @@
91
119
  if(handled_no == reef_users.length)
92
120
  {
93
121
  //console.log('reload', reef_users)
122
+ //reef_users = [...reef_users]
94
123
 
95
124
  list?.reload(reef_users);
96
125
  }
97
126
  } )
127
+
98
128
  }
99
129
 
100
130
  function set_user_info(user, info)
@@ -102,10 +132,11 @@
102
132
  user.auth_group = info.auth_group ?? 0;
103
133
  user.files_group = info.files_group ?? 0;
104
134
  user.avatar_url = info.avatar_url ?? "";
105
- user.other = info.access_details ?? "";
106
- user.app_group = info.reef_user_group_id ?? 0;
135
+ user.access_details = info.access_details
136
+ user.acc_role = info.access_details?.role ?? '';
107
137
  user.removed = info.removed ?? false;
108
138
  user.invitation_not_accepted = info.invitation_not_accepted ?? false;
139
+ //console.log(info)
109
140
 
110
141
  if(user.removed)
111
142
  user.membership_tag = "Removed";
@@ -128,25 +159,14 @@
128
159
  let new_user = {
129
160
  name: '',
130
161
  email: '',
131
- maintainer: false
162
+ auth_group: 0,
163
+ files_group: 0,
164
+ acc_role: ''
132
165
  }
133
166
 
134
167
  let name_input;
135
168
  let email_input;
136
169
 
137
- async function delete_user(user)
138
- {
139
- let email = user[emailAttrib];
140
-
141
- let removed_user_details = await reef.get('/sys/kick_out_user?email=' + email);
142
- if(removed_user_details)
143
- {
144
- //let removed_user = reef_users.find( u => u[emailAttrib] == user[emailAttrib])
145
- set_user_info(user, removed_user_details);
146
- list?.refresh();
147
- }
148
- }
149
-
150
170
  async function on_name_changed(user, name, property)
151
171
  {
152
172
  user[property] = name;
@@ -166,36 +186,92 @@
166
186
  if(user.auth_group != flags)
167
187
  {
168
188
  let email = user[emailAttrib];
169
- let info = await reef.get(`sys/set_user_details?email=${email}&auth_group=${flags}`)
170
- if(info)
189
+ const res = await reef.fetch(`/json/anyv/sys/set_user_details?email=${email}&auth_group=${flags}`)
190
+ if(res.ok)
171
191
  {
192
+ const response_string = await res.text();
193
+ const info = JSON.parse(response_string);
194
+
172
195
  user.auth_group = flags;
173
196
 
174
197
  set_user_info(user, info)
175
- list?.refresh();
198
+ list?.rereder();
199
+ return true;
200
+ }
201
+ else
202
+ {
203
+ const err_msg = await res.text();
204
+ alerts.push(err_msg)
205
+ alerts = [...alerts];
206
+ return false;
176
207
  }
177
208
  }
209
+ return false;
178
210
  }
179
211
 
180
212
  async function on_change_files_access(user, flags, name)
181
213
  {
182
214
  if(user.files_group != flags)
183
215
  {
184
- user.files_group = flags;
185
-
186
216
  let email = user[emailAttrib];
187
- let info = await reef.get(`sys/set_user_details?email=${email}&files_group=${flags}`)
188
- if(info)
217
+ const res = await reef.fetch(`/json/anyv/sys/set_user_details?email=${email}&files_group=${flags}`)
218
+ if(res.ok)
219
+ {
220
+ const response_string = await res.text();
221
+ const info = JSON.parse(response_string);
222
+
223
+ user.files_group = flags;
224
+
225
+ set_user_info(user, info)
226
+ list?.rereder();
227
+ return true;
228
+ }
229
+ else
230
+ {
231
+ const err_msg = await res.text();
232
+ alerts.push(err_msg)
233
+ alerts = [...alerts];
234
+ return false;
235
+ }
236
+ }
237
+ return false;
238
+ }
239
+
240
+ async function on_change_access_role(user, role, name)
241
+ {
242
+ if(user.access_details && user.access_details.role != role)
243
+ {
244
+ const email = user[emailAttrib];
245
+ const res = await reef.fetch(`/json/anyv/sys/set_user_role?email=${email}&role=${role}`)
246
+ if(res.ok)
189
247
  {
248
+ const response_string = await res.text();
249
+ const info = JSON.parse(response_string);
250
+
251
+ user.access_details.role = role;
252
+ user.acc_role = role
253
+
190
254
  set_user_info(user, info)
191
- list?.refresh();
255
+ list?.rereder();
256
+ return true;
257
+ }
258
+ else
259
+ {
260
+ const err_msg = await res.text();
261
+ alerts = [err_msg, ...alerts];
262
+ return false;
192
263
  }
193
264
  }
265
+ return false;
194
266
  }
195
267
 
196
268
  function create_new_user()
197
269
  {
270
+ if(showAccessRoles)
271
+ new_user.acc_role = access_roles[0].name
272
+
198
273
  create_new_user_enabled = true;
274
+
199
275
  }
200
276
 
201
277
  let page_operations=[
@@ -214,14 +290,26 @@
214
290
  action: (focused) => { list.edit(user, nameAttrib) }
215
291
  },
216
292
  {
217
- caption: 'Privileges',
293
+ caption: 'Users management',
218
294
  action: (focused) => { list.edit(user, 'Privileges') }
219
295
  }];
220
296
 
297
+ if(showAccessRoles)
298
+ {
299
+ operations.push({
300
+ separator: true
301
+ });
302
+
303
+ operations.push({
304
+ caption: 'Access role',
305
+ action: (focused) => { list.edit(user, 'Access') }
306
+ });
307
+ }
308
+
221
309
  if(showFiles)
222
310
  {
223
311
  operations.push({
224
- caption: 'Files',
312
+ caption: 'External files',
225
313
  action: (focused) => { list.edit(user, 'Files') }
226
314
  });
227
315
  }
@@ -232,6 +320,9 @@
232
320
  let user_operations = (user) => {
233
321
 
234
322
  let operations = [];
323
+
324
+ if(user.removed)
325
+ return [];
235
326
 
236
327
  let edit_operations = get_edit_operations(user)
237
328
  if(edit_operations.length == 1)
@@ -254,13 +345,16 @@
254
345
  operations.push({
255
346
  caption: '',
256
347
  icon: FaUserMinus,
257
- action: (focused) => delete_user(user)
348
+ action: (focused) => askToRemove(user)
258
349
  });
259
350
 
260
351
  return operations;
261
352
  }
262
353
 
263
354
  let user_context_menu = (user) => {
355
+ if(user.removed)
356
+ return [];
357
+
264
358
  let edit_operations = get_edit_operations(user);
265
359
  return {
266
360
  grid: edit_operations
@@ -298,11 +392,7 @@
298
392
 
299
393
  }
300
394
 
301
- function is_valid_name(s)
302
- {
303
- return !!s;
304
- }
305
-
395
+
306
396
  function add_fake_users(reef_users)
307
397
  {
308
398
  const names = [
@@ -379,37 +469,69 @@
379
469
  if(!email_input?.validate())
380
470
  return;
381
471
 
382
- let result = await reef.post('/sys/invite_user',
383
- {
384
- email: new_user.email,
385
- admin: new_user.maintainer,
386
- set:
387
- {
388
- [nameAttrib]: new_user.name,
389
- [emailAttrib]: new_user.email
390
- }
472
+ try {
473
+ const res = await reef.fetch('/json/anyv/sys/invite_user', {
474
+ method: 'POST',
475
+ body: JSON.stringify({
476
+ email: new_user.email,
477
+ auth_group: new_user.auth_group,
478
+ files_group: new_user.files_group,
479
+ role: new_user.acc_role,
480
+ client_id: $session.configuration.client_id,
481
+ redirect_uri: `${window.location.origin}/#/auth/cb`,
482
+ state: `${window.location.origin}/#/auth/signin`,
483
+ set:
484
+ {
485
+ [nameAttrib]: new_user.name,
486
+ [emailAttrib]: new_user.email
487
+ }
488
+ })
391
489
  })
392
- if(result)
393
- {
394
- let created_user = result.User;
395
- let new_reef_user = {
396
- [nameAttrib]: created_user[nameAttrib],
397
- [emailAttrib]: created_user[emailAttrib],
398
- [refAttrib]: created_user[refAttrib]
399
- }
400
490
 
401
- let details = await reef.get(`/sys/user_details?email=${new_reef_user[emailAttrib]}`)
402
- set_user_info(new_reef_user, details);
491
+ if(res.ok)
492
+ {
493
+ const result = await res.json();
494
+ let created_user = result.User;
495
+ let new_reef_user = {
496
+ [nameAttrib]: created_user[nameAttrib],
497
+ [emailAttrib]: created_user[emailAttrib],
498
+ [refAttrib]: created_user[refAttrib]
499
+ }
403
500
 
404
- reef_users = [...reef_users, new_reef_user]
405
- list?.reload(reef_users);
406
- }
501
+ let details = await reef.get(`/sys/user_details?email=${new_reef_user[emailAttrib]}`)
502
+ set_user_info(new_reef_user, details);
407
503
 
504
+ let sameUserIdx = reef_users.findIndex(ru => ru[refAttrib] == new_reef_user[refAttrib])
505
+ if(sameUserIdx >= 0)
506
+ {
507
+ reef_users[sameUserIdx] = new_reef_user;
508
+ reef_users = [... reef_users];
509
+ }
510
+ else
511
+ {
512
+ reef_users = [...reef_users, new_reef_user]
513
+ }
408
514
 
515
+
516
+ list?.reload(reef_users);
517
+ }
518
+ else
519
+ {
520
+ const err_msg = await res.text();
521
+ alerts = [err_msg, ...alerts];
522
+ }
523
+ }
524
+ catch (err)
525
+ {
526
+ alerts = [err, ...alerts];
527
+ }
409
528
 
410
529
  new_user.name = '';
411
530
  new_user.email = '';
412
- new_user.maintainer = false;
531
+ new_user.auth_group = 0;
532
+ new_user.files_group = 0;
533
+ new_user.acc_role = ''
534
+
413
535
  create_new_user_enabled = false;
414
536
  }
415
537
 
@@ -417,10 +539,54 @@
417
539
  {
418
540
  new_user.name = '';
419
541
  new_user.email = '';
420
- new_user.maintainer = false;
542
+ new_user.auth_group = 0;
543
+ new_user.files_group = 0;
544
+ new_user.acc_role = ''
545
+
421
546
  create_new_user_enabled = false;
422
547
  }
423
548
 
549
+ let alerts = []
550
+
551
+ let removeModal;
552
+ let userToRemove;
553
+ function askToRemove(user)
554
+ {
555
+ userToRemove = user;
556
+ removeModal.show()
557
+ }
558
+
559
+ async function removeUser()
560
+ {
561
+ if(!userToRemove)
562
+ return;
563
+
564
+ let email = userToRemove[emailAttrib];
565
+ try{
566
+
567
+ const res = await reef.fetch('/json/anyv/sys/kick_out_user?email=' + email)
568
+ removeModal.hide();
569
+
570
+ if(res.ok)
571
+ {
572
+ const removed_user_details = await res.json();
573
+ //let removed_user = reef_users.find( u => u[emailAttrib] == user[emailAttrib])
574
+ set_user_info(userToRemove, removed_user_details);
575
+ list?.rereder();
576
+ }
577
+ else
578
+ {
579
+ const err = await res.text()
580
+ alerts = [err, ...alerts];
581
+ }
582
+ }
583
+ catch(err)
584
+ {
585
+ alerts = [err, ...alerts];
586
+ }
587
+ }
588
+
589
+
424
590
  </script>
425
591
 
426
592
  <Page self={data_item}
@@ -429,6 +595,23 @@
429
595
  clearsContext='props sel'>
430
596
  <!--a href="/" class="underline text-sm font-semibold ml-3"> &lt; Back to root</a-->
431
597
 
598
+ <!-- alerts -->
599
+ <section class="absolute left-2 sm:left-auto sm:right-2 bottom-2 flex flex-col gap-2">
600
+ {#each alerts as alert, idx}
601
+ <Alert class="bg-red-900/40 shadow-lg shadow-stone-400 dark:shadow-black flex flex-row">
602
+ <p>
603
+ {alert}
604
+ </p>
605
+ <button class="font-bold ml-auto"
606
+ on:click={() => {alerts = removeAt(alerts, idx)}}>
607
+ x
608
+ </button>
609
+ </Alert>
610
+ {/each}
611
+ </section>
612
+
613
+
614
+
432
615
  {#if reef_users && reef_users.length > 0}
433
616
  <List objects={reef_users}
434
617
  title='Members'
@@ -441,25 +624,29 @@
441
624
  <ListStaticProperty name="Membership" a="membership_tag"/>
442
625
 
443
626
  <ListComboProperty name='Privileges' a='auth_group' onSelect={on_change_privileges}>
444
- <ComboItem name='None' key={0} />
445
- <ComboItem name='Can see' key={1} />
446
- <ComboItem name='Can invite' key={3} />
447
- <ComboItem name='Can fully manage' key={7} />
627
+ {#each authAccessKinds as kind}
628
+ <ComboItem name={kind.name} key={kind.key} />
629
+ {/each}
448
630
  </ListComboProperty>
449
631
 
632
+ {#if showAccessRoles}
633
+ <ListComboProperty name='Access' a='acc_role' onSelect={on_change_access_role}>
634
+ <ComboSource objects={access_roles} name='name' key='name'/>
635
+ </ListComboProperty>
636
+ {/if}
637
+
450
638
  {#if showFiles}
451
639
  <ListComboProperty name='Files' a='files_group' onSelect={on_change_files_access}>
452
- <ComboItem name='None' key={0} />
453
- <ComboItem name='Read' key={1} />
454
- <ComboItem name='Write' key={2} />
455
- <ComboItem name='Read&Write' key={3} />
640
+ {#each filesAccessKinds as kind}
641
+ <ComboItem name={kind.name} key={kind.key} />
642
+ {/each}
456
643
  </ListComboProperty>
457
644
  {/if}
458
645
 
459
646
 
460
647
  </List>
461
648
  {/if}
462
-
649
+
463
650
  </Page>
464
651
 
465
652
  <Modal bind:open={create_new_user_enabled}
@@ -468,28 +655,154 @@
468
655
  onOkCallback={on_new_user_requested}
469
656
  onCancelCallback={on_new_user_canceled}
470
657
  icon={FaUserPlus}>
471
- <Input label='Name'
472
- placeholder=''
473
- self={new_user}
474
- a="name"
475
- validateCb={is_valid_name}
476
- bind:this={name_input}/>
477
658
 
478
659
  <Input label='E-mail'
479
660
  placeholder=''
480
661
  self={new_user}
481
662
  a="email"
482
- validateCb={is_valid_email_address}
663
+ validation={is_valid_email_address}
483
664
  bind:this={email_input}/>
484
665
 
485
- <Checkbox class="mt-2 text-xs font-normal" self={new_user} a="maintainer">
666
+ <Input label='Name'
667
+ placeholder='Optional'
668
+ self={new_user}
669
+ a="name"
670
+ bind:this={name_input}/>
671
+
672
+ <!--Checkbox class="mt-2 text-xs font-normal" self={new_user} a="maintainer">
486
673
  <div class="flex flex-row items-center">
487
674
  <span class="">Maintainer</span>
488
675
  <Icon id="b1" size={4} component={FaInfoCircle} class="text-stone-400 ml-5 pt-0 mt-1"/>
489
- <Popover class="w-64 text-sm font-light" title="Maintainer" triggeredBy="#b1" color="dropdown">
490
- Means that the invited user will be able to add and remove others and manage permissions in this organization.
676
+ <Popover class="w-64 text-sm font-light text-stone-500 bg-white dark:bg-stone-800 dark:border-stone-600 dark:text-stone-400" triggeredBy="#b1" color="dropdown">
677
+ Means that the invited user will be able to add/remove others and manage permissions in this organization.
491
678
  </Popover>
492
679
  </div>
493
- </Checkbox>
680
+ </Checkbox-->
681
+
682
+ <!-- There is problem with dropdown/combo on dialogs (nested fixed stacks) -->
683
+ <!--Combo class="mt-2" label='Privileges' a='auth_group' self={new_user} >
684
+ <ComboItem name='No auth access' key={0} />
685
+ <ComboItem name='Read auth access' key={1} />
686
+ <ComboItem name='Can invite others' key={3} />
687
+ <ComboItem name='Full auth access' key={7} />
688
+ </Combo-->
689
+
690
+ <section class="mt-2 grid grid-cols-2 gap-2">
691
+ <div class="flex flex-col">
692
+ <label for="new_user_auth_group"
693
+ class="text-xs">
694
+ Auth access
695
+ </label>
696
+ <button id="new_user_auth_group"
697
+ class=" w-full mt-0.5
698
+ bg-stone-50 border border-stone-300 text-stone-900 text-base sm:text-sm rounded-lg
699
+ focus:ring-primary-600 focus:border-primary-600 pb-0.5 pt-0.5 px-2.5 dark:bg-stone-700
700
+ dark:border-stone-600 dark:placeholder-stone-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
701
+ on:click={(e) => {
702
+ let owner = e.target;
703
+ while(owner && owner.tagName != 'BUTTON')
704
+ owner = owner.parentElement
705
+
706
+ if(!owner)
707
+ return;
708
+
709
+ let options = [];
710
+ authAccessKinds.forEach(k => options.push({
711
+ caption: k.name,
712
+ action: (f) => { new_user.auth_group=k.key}
713
+ }));
714
+
715
+ let rect = owner.getBoundingClientRect();
716
+ let pt = new DOMPoint(rect.left, rect.bottom)
717
+ showMenu(pt, options);
718
+ }}>
719
+ {authAccessKinds.find(k => k.key == new_user.auth_group)?.name}
720
+ <span class="w-3 h-3 inline-block text-stone-700 dark:text-stone-300 ml-2 mt-2 sm:mt-1">
721
+ <FaChevronDown/>
722
+ </span>
723
+ </button>
724
+ </div>
725
+
726
+ {#if showFiles}
727
+ <div class="flex flex-col">
728
+ <label for="new_user_auth_group"
729
+ class="text-xs">
730
+ Files access
731
+ </label>
732
+ <button
733
+ class=" mt-0.5 w-full
734
+ bg-stone-50 border border-stone-300 text-stone-900 text-base sm:text-sm rounded-lg
735
+ focus:ring-primary-600 focus:border-primary-600 pb-0.5 pt-0.5 px-2.5 dark:bg-stone-700
736
+ dark:border-stone-600 dark:placeholder-stone-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
737
+ on:click={(e) => {
738
+ let owner = e.target;
739
+ while(owner && owner.tagName != 'BUTTON')
740
+ owner = owner.parentElement
741
+
742
+ if(!owner)
743
+ return;
744
+
745
+ let options = [];
746
+ filesAccessKinds.forEach(k => options.push({
747
+ caption: k.name,
748
+ action: (f) => { new_user.files_group=k.key}
749
+ }));
750
+
751
+ let rect = owner.getBoundingClientRect();
752
+ let pt = new DOMPoint(rect.left, rect.bottom)
753
+ showMenu(pt, options);
754
+ }}>
755
+ {filesAccessKinds.find(k => k.key == new_user.files_group)?.name}
756
+ <span class="w-3 h-3 inline-block text-stone-700 dark:text-stone-300 ml-2 mt-2 sm:mt-1">
757
+ <FaChevronDown/>
758
+ </span>
759
+ </button>
760
+ </div>
761
+ {/if}
762
+
763
+ {#if showAccessRoles}
764
+ <div class="flex flex-col">
765
+ <label for="new_user_auth_group"
766
+ class="text-xs">
767
+ App role
768
+ </label>
769
+ <button
770
+ class=" mt-0.5 w-full
771
+ bg-stone-50 border border-stone-300 text-stone-900 text-base sm:text-sm rounded-lg
772
+ focus:ring-primary-600 focus:border-primary-600 pb-0.5 pt-0.5 px-2.5 dark:bg-stone-700
773
+ dark:border-stone-600 dark:placeholder-stone-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
774
+ on:click={(e) => {
775
+ let owner = e.target;
776
+ while(owner && owner.tagName != 'BUTTON')
777
+ owner = owner.parentElement
778
+
779
+ if(!owner)
780
+ return;
781
+
782
+ let options = [];
783
+ access_roles.forEach(k => options.push({
784
+ caption: k.name,
785
+ action: (f) => { new_user.acc_role=k.name}
786
+ }));
787
+
788
+ let rect = owner.getBoundingClientRect();
789
+ let pt = new DOMPoint(rect.left, rect.bottom)
790
+ showMenu(pt, options);
791
+ }}>
792
+ {new_user.acc_role ? new_user.acc_role : '<none>'}
793
+ <span class="w-3 h-3 inline-block text-stone-700 dark:text-stone-300 ml-2 mt-2 sm:mt-1">
794
+ <FaChevronDown/>
795
+ </span>
796
+ </button>
797
+ </div>
798
+ {/if}
799
+ </section>
494
800
  </Modal>
495
801
 
802
+ <Modal title="User removal"
803
+ content="Are you sure you want to remove {userToRemove ? userToRemove[nameAttrib] : 'user'} from the group?"
804
+ icon={FaUserMinus}
805
+ okCaption='Remove'
806
+ onOkCallback={removeUser}
807
+ bind:this={removeModal}
808
+ />