|
@@ -3,26 +3,26 @@ q-layout(view='hHh lpR fFf', container)
|
|
q-header.card-header.q-px-md.q-py-sm
|
|
q-header.card-header.q-px-md.q-py-sm
|
|
q-icon(name='img:/_assets/icons/fluent-account.svg', left, size='md')
|
|
q-icon(name='img:/_assets/icons/fluent-account.svg', left, size='md')
|
|
div
|
|
div
|
|
- span {{$t(`admin.users.edit`)}}
|
|
|
|
- .text-caption {{user.name}}
|
|
|
|
|
|
+ span {{t(`admin.users.edit`)}}
|
|
|
|
+ .text-caption {{state.user.name}}
|
|
q-space
|
|
q-space
|
|
q-btn-group(push)
|
|
q-btn-group(push)
|
|
q-btn(
|
|
q-btn(
|
|
push
|
|
push
|
|
color='grey-6'
|
|
color='grey-6'
|
|
text-color='white'
|
|
text-color='white'
|
|
- :aria-label='$t(`common.actions.refresh`)'
|
|
|
|
|
|
+ :aria-label='t(`common.actions.refresh`)'
|
|
icon='las la-redo-alt'
|
|
icon='las la-redo-alt'
|
|
- @click='load'
|
|
|
|
- :loading='loading > 0'
|
|
|
|
|
|
+ @click='fetchUser'
|
|
|
|
+ :loading='state.loading > 0'
|
|
)
|
|
)
|
|
- q-tooltip(anchor='center left', self='center right') {{$t(`common.actions.refresh`)}}
|
|
|
|
|
|
+ q-tooltip(anchor='center left', self='center right') {{t(`common.actions.refresh`)}}
|
|
q-btn(
|
|
q-btn(
|
|
push
|
|
push
|
|
color='white'
|
|
color='white'
|
|
text-color='grey-7'
|
|
text-color='grey-7'
|
|
- :label='$t(`common.actions.close`)'
|
|
|
|
- :aria-label='$t(`common.actions.close`)'
|
|
|
|
|
|
+ :label='t(`common.actions.close`)'
|
|
|
|
+ :aria-label='t(`common.actions.close`)'
|
|
icon='las la-times'
|
|
icon='las la-times'
|
|
@click='close'
|
|
@click='close'
|
|
)
|
|
)
|
|
@@ -30,14 +30,14 @@ q-layout(view='hHh lpR fFf', container)
|
|
push
|
|
push
|
|
color='positive'
|
|
color='positive'
|
|
text-color='white'
|
|
text-color='white'
|
|
- :label='$t(`common.actions.save`)'
|
|
|
|
- :aria-label='$t(`common.actions.save`)'
|
|
|
|
|
|
+ :label='t(`common.actions.save`)'
|
|
|
|
+ :aria-label='t(`common.actions.save`)'
|
|
icon='las la-check'
|
|
icon='las la-check'
|
|
@click='save()'
|
|
@click='save()'
|
|
- :disabled='loading > 0'
|
|
|
|
|
|
+ :disabled='state.loading > 0'
|
|
)
|
|
)
|
|
q-drawer.bg-dark-6(:model-value='true', :width='250', dark)
|
|
q-drawer.bg-dark-6(:model-value='true', :width='250', dark)
|
|
- q-list(padding, v-if='loading < 1')
|
|
|
|
|
|
+ q-list(padding, v-if='state.loading < 1')
|
|
q-item(
|
|
q-item(
|
|
v-for='sc of sections'
|
|
v-for='sc of sections'
|
|
:key='`section-` + sc.key'
|
|
:key='`section-` + sc.key'
|
|
@@ -50,111 +50,111 @@ q-layout(view='hHh lpR fFf', container)
|
|
q-icon(:name='sc.icon', color='white')
|
|
q-icon(:name='sc.icon', color='white')
|
|
q-item-section {{sc.text}}
|
|
q-item-section {{sc.text}}
|
|
q-page-container
|
|
q-page-container
|
|
- q-page(v-if='loading > 0')
|
|
|
|
|
|
+ q-page(v-if='state.loading > 0')
|
|
.flex.q-pa-lg.items-center
|
|
.flex.q-pa-lg.items-center
|
|
q-spinner-tail(color='primary', size='32px', :thickness='2')
|
|
q-spinner-tail(color='primary', size='32px', :thickness='2')
|
|
- .text-caption.text-primary.q-pl-md: strong {{$t('admin.users.loading')}}
|
|
|
|
- q-page(v-else-if='$route.params.section === `overview`')
|
|
|
|
|
|
+ .text-caption.text-primary.q-pl-md: strong {{t('admin.users.loading')}}
|
|
|
|
+ q-page(v-else-if='route.params.section === `overview`')
|
|
.q-pa-md
|
|
.q-pa-md
|
|
.row.q-col-gutter-md
|
|
.row.q-col-gutter-md
|
|
.col-12.col-lg-8
|
|
.col-12.col-lg-8
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.profile')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.profile')}}
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='contact')
|
|
blueprint-icon(icon='contact')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.name`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.nameHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.name`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.nameHint`)}}
|
|
q-item-section
|
|
q-item-section
|
|
q-input(
|
|
q-input(
|
|
outlined
|
|
outlined
|
|
- v-model='user.name'
|
|
|
|
|
|
+ v-model='state.user.name'
|
|
dense
|
|
dense
|
|
:rules=`[
|
|
:rules=`[
|
|
- val => invalidCharsRegex.test(val) || $t('admin.users.nameInvalidChars')
|
|
|
|
|
|
+ val => invalidCharsRegex.test(val) || t('admin.users.nameInvalidChars')
|
|
]`
|
|
]`
|
|
hide-bottom-space
|
|
hide-bottom-space
|
|
- :aria-label='$t(`admin.users.name`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.name`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='envelope')
|
|
blueprint-icon(icon='envelope')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.email`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.emailHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.email`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.emailHint`)}}
|
|
q-item-section
|
|
q-item-section
|
|
q-input(
|
|
q-input(
|
|
outlined
|
|
outlined
|
|
- v-model='user.email'
|
|
|
|
|
|
+ v-model='state.user.email'
|
|
dense
|
|
dense
|
|
- :aria-label='$t(`admin.users.email`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.email`)'
|
|
)
|
|
)
|
|
- template(v-if='user.meta')
|
|
|
|
|
|
+ template(v-if='state.user.meta')
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='address')
|
|
blueprint-icon(icon='address')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.location`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.locationHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.location`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.locationHint`)}}
|
|
q-item-section
|
|
q-item-section
|
|
q-input(
|
|
q-input(
|
|
outlined
|
|
outlined
|
|
- v-model='user.meta.location'
|
|
|
|
|
|
+ v-model='state.user.meta.location'
|
|
dense
|
|
dense
|
|
- :aria-label='$t(`admin.users.location`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.location`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='new-job')
|
|
blueprint-icon(icon='new-job')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.jobTitle`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.jobTitleHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.jobTitle`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.jobTitleHint`)}}
|
|
q-item-section
|
|
q-item-section
|
|
q-input(
|
|
q-input(
|
|
outlined
|
|
outlined
|
|
- v-model='user.meta.jobTitle'
|
|
|
|
|
|
+ v-model='state.user.meta.jobTitle'
|
|
dense
|
|
dense
|
|
- :aria-label='$t(`admin.users.jobTitle`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.jobTitle`)'
|
|
)
|
|
)
|
|
|
|
|
|
- q-card.shadow-1.q-pb-sm.q-mt-md(v-if='user.meta')
|
|
|
|
|
|
+ q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.user.meta')
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.preferences')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.preferences')}}
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='timezone')
|
|
blueprint-icon(icon='timezone')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.timezone`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.timezoneHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.timezone`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.timezoneHint`)}}
|
|
q-item-section
|
|
q-item-section
|
|
q-select(
|
|
q-select(
|
|
outlined
|
|
outlined
|
|
- v-model='user.prefs.timezone'
|
|
|
|
- :options='timezones'
|
|
|
|
|
|
+ v-model='state.user.prefs.timezone'
|
|
|
|
+ :options='dataStore.timezones'
|
|
option-value='value'
|
|
option-value='value'
|
|
option-label='text'
|
|
option-label='text'
|
|
emit-value
|
|
emit-value
|
|
map-options
|
|
map-options
|
|
dense
|
|
dense
|
|
options-dense
|
|
options-dense
|
|
- :aria-label='$t(`admin.users.timezone`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.timezone`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='calendar')
|
|
blueprint-icon(icon='calendar')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.dateFormat`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.dateFormatHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.dateFormat`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.dateFormatHint`)}}
|
|
q-item-section
|
|
q-item-section
|
|
q-select(
|
|
q-select(
|
|
outlined
|
|
outlined
|
|
- v-model='user.prefs.dateFormat'
|
|
|
|
|
|
+ v-model='state.user.prefs.dateFormat'
|
|
emit-value
|
|
emit-value
|
|
map-options
|
|
map-options
|
|
dense
|
|
dense
|
|
- :aria-label='$t(`admin.users.dateFormat`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.dateFormat`)'
|
|
:options=`[
|
|
:options=`[
|
|
- { label: $t('profile.localeDefault'), value: '' },
|
|
|
|
|
|
+ { label: t('profile.localeDefault'), value: '' },
|
|
{ label: 'DD/MM/YYYY', value: 'DD/MM/YYYY' },
|
|
{ label: 'DD/MM/YYYY', value: 'DD/MM/YYYY' },
|
|
{ label: 'DD.MM.YYYY', value: 'DD.MM.YYYY' },
|
|
{ label: 'DD.MM.YYYY', value: 'DD.MM.YYYY' },
|
|
{ label: 'MM/DD/YYYY', value: 'MM/DD/YYYY' },
|
|
{ label: 'MM/DD/YYYY', value: 'MM/DD/YYYY' },
|
|
@@ -166,168 +166,168 @@ q-layout(view='hHh lpR fFf', container)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='clock')
|
|
blueprint-icon(icon='clock')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.timeFormat`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.timeFormatHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.timeFormat`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.timeFormatHint`)}}
|
|
q-item-section.col-auto
|
|
q-item-section.col-auto
|
|
q-btn-toggle(
|
|
q-btn-toggle(
|
|
- v-model='user.prefs.timeFormat'
|
|
|
|
|
|
+ v-model='state.user.prefs.timeFormat'
|
|
push
|
|
push
|
|
glossy
|
|
glossy
|
|
no-caps
|
|
no-caps
|
|
toggle-color='primary'
|
|
toggle-color='primary'
|
|
:options=`[
|
|
:options=`[
|
|
- { label: $t('profile.timeFormat12h'), value: '12h' },
|
|
|
|
- { label: $t('profile.timeFormat24h'), value: '24h' }
|
|
|
|
|
|
+ { label: t('profile.timeFormat12h'), value: '12h' },
|
|
|
|
+ { label: t('profile.timeFormat24h'), value: '24h' }
|
|
]`
|
|
]`
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item(tag='label', v-ripple)
|
|
q-item(tag='label', v-ripple)
|
|
blueprint-icon(icon='light-on')
|
|
blueprint-icon(icon='light-on')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.darkMode`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.darkModeHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.darkMode`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.darkModeHint`)}}
|
|
q-item-section(avatar)
|
|
q-item-section(avatar)
|
|
q-toggle(
|
|
q-toggle(
|
|
- v-model='user.prefs.darkMode'
|
|
|
|
|
|
+ v-model='state.user.prefs.darkMode'
|
|
color='primary'
|
|
color='primary'
|
|
checked-icon='las la-check'
|
|
checked-icon='las la-check'
|
|
unchecked-icon='las la-times'
|
|
unchecked-icon='las la-times'
|
|
- :aria-label='$t(`admin.users.darkMode`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.darkMode`)'
|
|
)
|
|
)
|
|
|
|
|
|
.col-12.col-lg-4
|
|
.col-12.col-lg-4
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.info')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.info')}}
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='person', :hue-rotate='-45')
|
|
blueprint-icon(icon='person', :hue-rotate='-45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`common.field.id`)}}
|
|
|
|
- q-item-label: strong {{userId}}
|
|
|
|
|
|
+ q-item-label {{t(`common.field.id`)}}
|
|
|
|
+ q-item-label: strong {{state.user.id}}
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='calendar-plus', :hue-rotate='-45')
|
|
blueprint-icon(icon='calendar-plus', :hue-rotate='-45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`common.field.createdOn`)}}
|
|
|
|
- q-item-label: strong {{humanizeDate(user.createdAt)}}
|
|
|
|
|
|
+ q-item-label {{t(`common.field.createdOn`)}}
|
|
|
|
+ q-item-label: strong {{humanizeDate(state.user.createdAt)}}
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='summertime', :hue-rotate='-45')
|
|
blueprint-icon(icon='summertime', :hue-rotate='-45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`common.field.lastUpdated`)}}
|
|
|
|
- q-item-label: strong {{humanizeDate(user.updatedAt)}}
|
|
|
|
|
|
+ q-item-label {{t(`common.field.lastUpdated`)}}
|
|
|
|
+ q-item-label: strong {{humanizeDate(state.user.updatedAt)}}
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='enter', :hue-rotate='-45')
|
|
blueprint-icon(icon='enter', :hue-rotate='-45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.lastLoginAt`)}}
|
|
|
|
- q-item-label: strong {{humanizeDate(user.lastLoginAt)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.lastLoginAt`)}}
|
|
|
|
+ q-item-label: strong {{humanizeDate(state.user.lastLoginAt)}}
|
|
|
|
|
|
- q-card.shadow-1.q-pb-sm.q-mt-md(v-if='user.meta')
|
|
|
|
|
|
+ q-card.shadow-1.q-pb-sm.q-mt-md(v-if='state.user.meta')
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.notes')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.notes')}}
|
|
q-input.q-mt-sm(
|
|
q-input.q-mt-sm(
|
|
outlined
|
|
outlined
|
|
- v-model='user.meta.notes'
|
|
|
|
|
|
+ v-model='state.user.meta.notes'
|
|
type='textarea'
|
|
type='textarea'
|
|
- :aria-label='$t(`admin.users.notes`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.notes`)'
|
|
input-style='min-height: 243px'
|
|
input-style='min-height: 243px'
|
|
- :hint='$t(`admin.users.noteHint`)'
|
|
|
|
|
|
+ :hint='t(`admin.users.noteHint`)'
|
|
)
|
|
)
|
|
|
|
|
|
- q-page(v-else-if='$route.params.section === `activity`')
|
|
|
|
|
|
+ q-page(v-else-if='route.params.section === `activity`')
|
|
span ---
|
|
span ---
|
|
|
|
|
|
- q-page(v-else-if='$route.params.section === `auth`')
|
|
|
|
|
|
+ q-page(v-else-if='route.params.section === `auth`')
|
|
.q-pa-md
|
|
.q-pa-md
|
|
.row.q-col-gutter-md
|
|
.row.q-col-gutter-md
|
|
.col-12.col-lg-7
|
|
.col-12.col-lg-7
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.passAuth')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.passAuth')}}
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='password', :hue-rotate='45')
|
|
blueprint-icon(icon='password', :hue-rotate='45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.changePassword`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.changePasswordHint`)}}
|
|
|
|
- q-item-label(caption): strong(:class='localAuth.password ? `text-positive` : `text-negative`') {{localAuth.password ? $t(`admin.users.pwdSet`) : $t(`admin.users.pwdNotSet`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.changePassword`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.changePasswordHint`)}}
|
|
|
|
+ q-item-label(caption): strong(:class='localAuth.password ? `text-positive` : `text-negative`') {{localAuth.password ? t(`admin.users.pwdSet`) : t(`admin.users.pwdNotSet`)}}
|
|
q-item-section(side)
|
|
q-item-section(side)
|
|
q-btn.acrylic-btn(
|
|
q-btn.acrylic-btn(
|
|
flat
|
|
flat
|
|
icon='las la-arrow-circle-right'
|
|
icon='las la-arrow-circle-right'
|
|
color='primary'
|
|
color='primary'
|
|
@click='changePassword'
|
|
@click='changePassword'
|
|
- :label='$t(`common.actions.proceed`)'
|
|
|
|
|
|
+ :label='t(`common.actions.proceed`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item(tag='label', v-ripple)
|
|
q-item(tag='label', v-ripple)
|
|
blueprint-icon(icon='password-reset')
|
|
blueprint-icon(icon='password-reset')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.mustChangePwd`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.mustChangePwdHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.mustChangePwd`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.mustChangePwdHint`)}}
|
|
q-item-section(avatar)
|
|
q-item-section(avatar)
|
|
q-toggle(
|
|
q-toggle(
|
|
v-model='localAuth.mustChangePwd'
|
|
v-model='localAuth.mustChangePwd'
|
|
color='primary'
|
|
color='primary'
|
|
checked-icon='las la-check'
|
|
checked-icon='las la-check'
|
|
unchecked-icon='las la-times'
|
|
unchecked-icon='las la-times'
|
|
- :aria-label='$t(`admin.users.mustChangePwd`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.mustChangePwd`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item(tag='label', v-ripple)
|
|
q-item(tag='label', v-ripple)
|
|
blueprint-icon(icon='key')
|
|
blueprint-icon(icon='key')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.pwdAuthRestrict`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.pwdAuthRestrictHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.pwdAuthRestrict`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.pwdAuthRestrictHint`)}}
|
|
q-item-section(avatar)
|
|
q-item-section(avatar)
|
|
q-toggle(
|
|
q-toggle(
|
|
v-model='localAuth.restrictLogin'
|
|
v-model='localAuth.restrictLogin'
|
|
color='primary'
|
|
color='primary'
|
|
checked-icon='las la-check'
|
|
checked-icon='las la-check'
|
|
unchecked-icon='las la-times'
|
|
unchecked-icon='las la-times'
|
|
- :aria-label='$t(`admin.users.pwdAuthRestrict`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.pwdAuthRestrict`)'
|
|
)
|
|
)
|
|
|
|
|
|
q-card.shadow-1.q-pb-sm.q-mt-md
|
|
q-card.shadow-1.q-pb-sm.q-mt-md
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.tfa')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.tfa')}}
|
|
q-item(tag='label', v-ripple)
|
|
q-item(tag='label', v-ripple)
|
|
blueprint-icon(icon='key')
|
|
blueprint-icon(icon='key')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.tfaRequired`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.tfaRequiredHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.tfaRequired`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.tfaRequiredHint`)}}
|
|
q-item-section(avatar)
|
|
q-item-section(avatar)
|
|
q-toggle(
|
|
q-toggle(
|
|
v-model='localAuth.tfaRequired'
|
|
v-model='localAuth.tfaRequired'
|
|
color='primary'
|
|
color='primary'
|
|
checked-icon='las la-check'
|
|
checked-icon='las la-check'
|
|
unchecked-icon='las la-times'
|
|
unchecked-icon='las la-times'
|
|
- :aria-label='$t(`admin.users.tfaRequired`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.tfaRequired`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='password', :hue-rotate='45')
|
|
blueprint-icon(icon='password', :hue-rotate='45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.tfaInvalidate`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.tfaInvalidateHint`)}}
|
|
|
|
- q-item-label(caption): strong(:class='localAuth.tfaSecret ? `text-positive` : `text-negative`') {{localAuth.tfaSecret ? $t(`admin.users.tfaSet`) : $t(`admin.users.tfaNotSet`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.tfaInvalidate`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.tfaInvalidateHint`)}}
|
|
|
|
+ q-item-label(caption): strong(:class='localAuth.tfaSecret ? `text-positive` : `text-negative`') {{localAuth.tfaSecret ? t(`admin.users.tfaSet`) : t(`admin.users.tfaNotSet`)}}
|
|
q-item-section(side)
|
|
q-item-section(side)
|
|
q-btn.acrylic-btn(
|
|
q-btn.acrylic-btn(
|
|
flat
|
|
flat
|
|
icon='las la-arrow-circle-right'
|
|
icon='las la-arrow-circle-right'
|
|
color='primary'
|
|
color='primary'
|
|
@click='invalidateTFA'
|
|
@click='invalidateTFA'
|
|
- :label='$t(`common.actions.proceed`)'
|
|
|
|
|
|
+ :label='t(`common.actions.proceed`)'
|
|
)
|
|
)
|
|
.col-12.col-lg-5
|
|
.col-12.col-lg-5
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.linkedProviders')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.linkedProviders')}}
|
|
q-banner.q-mt-md(
|
|
q-banner.q-mt-md(
|
|
v-if='linkedAuthProviders.length < 1'
|
|
v-if='linkedAuthProviders.length < 1'
|
|
rounded
|
|
rounded
|
|
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
|
|
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-2 text-grey-7`'
|
|
- ) {{$t('admin.users.noLinkedProviders')}}
|
|
|
|
|
|
+ ) {{t('admin.users.noLinkedProviders')}}
|
|
template(
|
|
template(
|
|
v-for='(prv, idx) in linkedAuthProviders'
|
|
v-for='(prv, idx) in linkedAuthProviders'
|
|
:key='prv._id'
|
|
:key='prv._id'
|
|
@@ -339,15 +339,15 @@ q-layout(view='hHh lpR fFf', container)
|
|
q-item-label {{prv._moduleName}}
|
|
q-item-label {{prv._moduleName}}
|
|
q-item-label(caption) {{prv.key}}
|
|
q-item-label(caption) {{prv.key}}
|
|
|
|
|
|
- q-page(v-else-if='$route.params.section === `groups`')
|
|
|
|
|
|
+ q-page(v-else-if='route.params.section === `groups`')
|
|
.q-pa-md
|
|
.q-pa-md
|
|
.row.q-col-gutter-md
|
|
.row.q-col-gutter-md
|
|
.col-12.col-lg-8
|
|
.col-12.col-lg-8
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.groups')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.groups')}}
|
|
template(
|
|
template(
|
|
- v-for='(grp, idx) of user.groups'
|
|
|
|
|
|
+ v-for='(grp, idx) of state.user.groups'
|
|
:key='grp.id'
|
|
:key='grp.id'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset, v-if='idx > 0')
|
|
q-separator.q-my-sm(inset, v-if='idx > 0')
|
|
@@ -361,17 +361,17 @@ q-layout(view='hHh lpR fFf', container)
|
|
icon='las la-times'
|
|
icon='las la-times'
|
|
color='accent'
|
|
color='accent'
|
|
@click='unassignGroup(grp.id)'
|
|
@click='unassignGroup(grp.id)'
|
|
- :aria-label='$t(`admin.users.unassignGroup`)'
|
|
|
|
|
|
+ :aria-label='t(`admin.users.unassignGroup`)'
|
|
)
|
|
)
|
|
- q-tooltip(anchor='center left' self='center right') {{$t('admin.users.unassignGroup')}}
|
|
|
|
|
|
+ q-tooltip(anchor='center left' self='center right') {{t('admin.users.unassignGroup')}}
|
|
q-card.shadow-1.q-py-sm.q-mt-md
|
|
q-card.shadow-1.q-py-sm.q-mt-md
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='join')
|
|
blueprint-icon(icon='join')
|
|
q-item-section
|
|
q-item-section
|
|
q-select(
|
|
q-select(
|
|
outlined
|
|
outlined
|
|
- :options='groups'
|
|
|
|
- v-model='groupToAdd'
|
|
|
|
|
|
+ :options='state.groups'
|
|
|
|
+ v-model='state.groupToAdd'
|
|
map-options
|
|
map-options
|
|
emit-value
|
|
emit-value
|
|
option-value='id'
|
|
option-value='id'
|
|
@@ -379,33 +379,33 @@ q-layout(view='hHh lpR fFf', container)
|
|
options-dense
|
|
options-dense
|
|
dense
|
|
dense
|
|
hide-bottom-space
|
|
hide-bottom-space
|
|
- :label='$t(`admin.users.groups`)'
|
|
|
|
- :aria-label='$t(`admin.users.groups`)'
|
|
|
|
- :loading='loading > 0'
|
|
|
|
|
|
+ :label='t(`admin.users.groups`)'
|
|
|
|
+ :aria-label='t(`admin.users.groups`)'
|
|
|
|
+ :loading='state.loading > 0'
|
|
)
|
|
)
|
|
q-item-section(side)
|
|
q-item-section(side)
|
|
q-btn(
|
|
q-btn(
|
|
unelevated
|
|
unelevated
|
|
icon='las la-plus'
|
|
icon='las la-plus'
|
|
- :label='$t(`admin.users.assignGroup`)'
|
|
|
|
|
|
+ :label='t(`admin.users.assignGroup`)'
|
|
color='primary'
|
|
color='primary'
|
|
@click='assignGroup'
|
|
@click='assignGroup'
|
|
)
|
|
)
|
|
|
|
|
|
- q-page(v-else-if='$route.params.section === `metadata`')
|
|
|
|
|
|
+ q-page(v-else-if='route.params.section === `metadata`')
|
|
.q-pa-md
|
|
.q-pa-md
|
|
.row.q-col-gutter-md
|
|
.row.q-col-gutter-md
|
|
.col-12.col-lg-8
|
|
.col-12.col-lg-8
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card-section.flex.items-center
|
|
q-card-section.flex.items-center
|
|
- .text-subtitle1 {{$t('admin.users.metadata')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.metadata')}}
|
|
q-space
|
|
q-space
|
|
q-badge(
|
|
q-badge(
|
|
- v-if='metadataInvalidJSON'
|
|
|
|
|
|
+ v-if='state.metadataInvalidJSON'
|
|
color='negative'
|
|
color='negative'
|
|
)
|
|
)
|
|
q-icon.q-mr-xs(name='las la-exclamation-triangle', size='20px')
|
|
q-icon.q-mr-xs(name='las la-exclamation-triangle', size='20px')
|
|
- span {{$t('admin.users.invalidJSON')}}
|
|
|
|
|
|
+ span {{t('admin.users.invalidJSON')}}
|
|
q-badge.q-py-xs(
|
|
q-badge.q-py-xs(
|
|
v-else
|
|
v-else
|
|
label='JSON'
|
|
label='JSON'
|
|
@@ -413,80 +413,79 @@ q-layout(view='hHh lpR fFf', container)
|
|
)
|
|
)
|
|
q-item
|
|
q-item
|
|
q-item-section
|
|
q-item-section
|
|
- q-no-ssr(:placeholder='$t(`common.loading`)')
|
|
|
|
- util-code-editor.admin-theme-cm(
|
|
|
|
|
|
+ q-no-ssr(:placeholder='t(`common.loading`)')
|
|
|
|
+ codemirror.metadata-codemirror(
|
|
v-model='metadata'
|
|
v-model='metadata'
|
|
- language='json'
|
|
|
|
- :min-height='500'
|
|
|
|
|
|
+ :extensions='[json()]'
|
|
)
|
|
)
|
|
|
|
|
|
- q-page(v-else-if='$route.params.section === `operations`')
|
|
|
|
|
|
+ q-page(v-else-if='route.params.section === `operations`')
|
|
.q-pa-md
|
|
.q-pa-md
|
|
.row.q-col-gutter-md
|
|
.row.q-col-gutter-md
|
|
.col-12.col-lg-8
|
|
.col-12.col-lg-8
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card.shadow-1.q-pb-sm
|
|
q-card-section
|
|
q-card-section
|
|
- .text-subtitle1 {{$t('admin.users.operations')}}
|
|
|
|
|
|
+ .text-subtitle1 {{t('admin.users.operations')}}
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='email-open', :hue-rotate='45')
|
|
blueprint-icon(icon='email-open', :hue-rotate='45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.sendWelcomeEmail`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.sendWelcomeEmailAltHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.sendWelcomeEmail`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.sendWelcomeEmailAltHint`)}}
|
|
q-item-section(side)
|
|
q-item-section(side)
|
|
q-btn.acrylic-btn(
|
|
q-btn.acrylic-btn(
|
|
flat
|
|
flat
|
|
icon='las la-arrow-circle-right'
|
|
icon='las la-arrow-circle-right'
|
|
color='primary'
|
|
color='primary'
|
|
@click='sendWelcomeEmail'
|
|
@click='sendWelcomeEmail'
|
|
- :label='$t(`common.actions.proceed`)'
|
|
|
|
|
|
+ :label='t(`common.actions.proceed`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='apply', :hue-rotate='45')
|
|
blueprint-icon(icon='apply', :hue-rotate='45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{user.isVerified ? $t(`admin.users.unverify`) : $t(`admin.users.verify`)}}
|
|
|
|
- q-item-label(caption) {{user.isVerified ? $t(`admin.users.unverifyHint`) : $t(`admin.users.verifyHint`)}}
|
|
|
|
- q-item-label(caption): strong(:class='user.isVerified ? `text-positive` : `text-negative`') {{user.isVerified ? $t(`admin.users.verified`) : $t(`admin.users.unverified`)}}
|
|
|
|
|
|
+ q-item-label {{state.user.isVerified ? t(`admin.users.unverify`) : t(`admin.users.verify`)}}
|
|
|
|
+ q-item-label(caption) {{state.user.isVerified ? t(`admin.users.unverifyHint`) : t(`admin.users.verifyHint`)}}
|
|
|
|
+ q-item-label(caption): strong(:class='state.user.isVerified ? `text-positive` : `text-negative`') {{state.user.isVerified ? t(`admin.users.verified`) : t(`admin.users.unverified`)}}
|
|
q-item-section(side)
|
|
q-item-section(side)
|
|
q-btn.acrylic-btn(
|
|
q-btn.acrylic-btn(
|
|
flat
|
|
flat
|
|
icon='las la-arrow-circle-right'
|
|
icon='las la-arrow-circle-right'
|
|
color='primary'
|
|
color='primary'
|
|
@click='toggleVerified'
|
|
@click='toggleVerified'
|
|
- :label='$t(`common.actions.proceed`)'
|
|
|
|
|
|
+ :label='t(`common.actions.proceed`)'
|
|
)
|
|
)
|
|
q-separator.q-my-sm(inset)
|
|
q-separator.q-my-sm(inset)
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='unfriend', :hue-rotate='45')
|
|
blueprint-icon(icon='unfriend', :hue-rotate='45')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{user.isActive ? $t(`admin.users.ban`) : $t(`admin.users.unban`)}}
|
|
|
|
- q-item-label(caption) {{user.isActive ? $t(`admin.users.banHint`) : $t(`admin.users.unbanHint`)}}
|
|
|
|
- q-item-label(caption): strong(:class='user.isActive ? `text-positive` : `text-negative`') {{user.isActive ? $t(`admin.users.active`) : $t(`admin.users.banned`)}}
|
|
|
|
|
|
+ q-item-label {{state.user.isActive ? t(`admin.users.ban`) : t(`admin.users.unban`)}}
|
|
|
|
+ q-item-label(caption) {{state.user.isActive ? t(`admin.users.banHint`) : t(`admin.users.unbanHint`)}}
|
|
|
|
+ q-item-label(caption): strong(:class='state.user.isActive ? `text-positive` : `text-negative`') {{state.user.isActive ? t(`admin.users.active`) : t(`admin.users.banned`)}}
|
|
q-item-section(side)
|
|
q-item-section(side)
|
|
q-btn.acrylic-btn(
|
|
q-btn.acrylic-btn(
|
|
flat
|
|
flat
|
|
icon='las la-arrow-circle-right'
|
|
icon='las la-arrow-circle-right'
|
|
color='primary'
|
|
color='primary'
|
|
@click='toggleBan'
|
|
@click='toggleBan'
|
|
- :label='$t(`common.actions.proceed`)'
|
|
|
|
|
|
+ :label='t(`common.actions.proceed`)'
|
|
)
|
|
)
|
|
q-card.shadow-1.q-py-sm.q-mt-md
|
|
q-card.shadow-1.q-py-sm.q-mt-md
|
|
q-item
|
|
q-item
|
|
blueprint-icon(icon='denied', :hue-rotate='140')
|
|
blueprint-icon(icon='denied', :hue-rotate='140')
|
|
q-item-section
|
|
q-item-section
|
|
- q-item-label {{$t(`admin.users.delete`)}}
|
|
|
|
- q-item-label(caption) {{$t(`admin.users.deleteHint`)}}
|
|
|
|
|
|
+ q-item-label {{t(`admin.users.delete`)}}
|
|
|
|
+ q-item-label(caption) {{t(`admin.users.deleteHint`)}}
|
|
q-item-section(side)
|
|
q-item-section(side)
|
|
q-btn.acrylic-btn(
|
|
q-btn.acrylic-btn(
|
|
flat
|
|
flat
|
|
icon='las la-arrow-circle-right'
|
|
icon='las la-arrow-circle-right'
|
|
color='negative'
|
|
color='negative'
|
|
@click='deleteUser'
|
|
@click='deleteUser'
|
|
- :label='$t(`common.actions.proceed`)'
|
|
|
|
|
|
+ :label='t(`common.actions.proceed`)'
|
|
)
|
|
)
|
|
</template>
|
|
</template>
|
|
|
|
|
|
-<script>
|
|
|
|
|
|
+<script setup>
|
|
import gql from 'graphql-tag'
|
|
import gql from 'graphql-tag'
|
|
import cloneDeep from 'lodash/cloneDeep'
|
|
import cloneDeep from 'lodash/cloneDeep'
|
|
import some from 'lodash/some'
|
|
import some from 'lodash/some'
|
|
@@ -494,284 +493,339 @@ import find from 'lodash/find'
|
|
import findKey from 'lodash/findKey'
|
|
import findKey from 'lodash/findKey'
|
|
import _get from 'lodash/get'
|
|
import _get from 'lodash/get'
|
|
import map from 'lodash/map'
|
|
import map from 'lodash/map'
|
|
-import { get } from 'vuex-pathify'
|
|
|
|
import { DateTime } from 'luxon'
|
|
import { DateTime } from 'luxon'
|
|
-import UtilCodeEditor from './UtilCodeEditor.vue'
|
|
|
|
|
|
+
|
|
|
|
+import { useI18n } from 'vue-i18n'
|
|
|
|
+import { useQuasar } from 'quasar'
|
|
|
|
+import { computed, onMounted, reactive, watch } from 'vue'
|
|
|
|
+import { useRouter, useRoute } from 'vue-router'
|
|
|
|
+
|
|
|
|
+import { useAdminStore } from 'src/stores/admin'
|
|
|
|
+import { useDataStore } from 'src/stores/data'
|
|
|
|
+
|
|
import UserChangePwdDialog from './UserChangePwdDialog.vue'
|
|
import UserChangePwdDialog from './UserChangePwdDialog.vue'
|
|
|
|
+import { Codemirror } from 'vue-codemirror'
|
|
|
|
+import { json } from '@codemirror/lang-json'
|
|
|
|
+// import { oneDark } from '@codemirror/theme-one-dark'
|
|
|
|
+
|
|
|
|
+// QUASAR
|
|
|
|
+
|
|
|
|
+const $q = useQuasar()
|
|
|
|
+
|
|
|
|
+// STORES
|
|
|
|
+
|
|
|
|
+const adminStore = useAdminStore()
|
|
|
|
+const dataStore = useDataStore()
|
|
|
|
|
|
-export default {
|
|
|
|
- components: {
|
|
|
|
- UtilCodeEditor
|
|
|
|
|
|
+// ROUTER
|
|
|
|
+
|
|
|
|
+const router = useRouter()
|
|
|
|
+const route = useRoute()
|
|
|
|
+
|
|
|
|
+// I18N
|
|
|
|
+
|
|
|
|
+const { t } = useI18n()
|
|
|
|
+
|
|
|
|
+// DATA
|
|
|
|
+
|
|
|
|
+const state = reactive({
|
|
|
|
+ invalidCharsRegex: /^[^<>"]+$/,
|
|
|
|
+ user: {
|
|
|
|
+ meta: {},
|
|
|
|
+ prefs: {},
|
|
|
|
+ groups: []
|
|
},
|
|
},
|
|
- data () {
|
|
|
|
- return {
|
|
|
|
- invalidCharsRegex: /^[^<>"]+$/,
|
|
|
|
- sections: [
|
|
|
|
- { key: 'overview', text: this.$t('admin.users.overview'), icon: 'las la-user' },
|
|
|
|
- { key: 'activity', text: this.$t('admin.users.activity'), icon: 'las la-chart-area' },
|
|
|
|
- { key: 'auth', text: this.$t('admin.users.auth'), icon: 'las la-key' },
|
|
|
|
- { key: 'groups', text: this.$t('admin.users.groups'), icon: 'las la-users' },
|
|
|
|
- { key: 'metadata', text: this.$t('admin.users.metadata'), icon: 'las la-clipboard-list' },
|
|
|
|
- { key: 'operations', text: this.$t('admin.users.operations'), icon: 'las la-tools' }
|
|
|
|
- ],
|
|
|
|
- user: {
|
|
|
|
- meta: {},
|
|
|
|
- prefs: {},
|
|
|
|
- groups: []
|
|
|
|
- },
|
|
|
|
- groups: [],
|
|
|
|
- groupToAdd: null,
|
|
|
|
- loading: 0,
|
|
|
|
- metadataInvalidJSON: false
|
|
|
|
|
|
+ groups: [],
|
|
|
|
+ groupToAdd: null,
|
|
|
|
+ loading: 0,
|
|
|
|
+ metadataInvalidJSON: false
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const sections = [
|
|
|
|
+ { key: 'overview', text: t('admin.users.overview'), icon: 'las la-user' },
|
|
|
|
+ { key: 'activity', text: t('admin.users.activity'), icon: 'las la-chart-area' },
|
|
|
|
+ { key: 'auth', text: t('admin.users.auth'), icon: 'las la-key' },
|
|
|
|
+ { key: 'groups', text: t('admin.users.groups'), icon: 'las la-users' },
|
|
|
|
+ { key: 'metadata', text: t('admin.users.metadata'), icon: 'las la-clipboard-list' },
|
|
|
|
+ { key: 'operations', text: t('admin.users.operations'), icon: 'las la-tools' }
|
|
|
|
+]
|
|
|
|
+
|
|
|
|
+// COMPUTED
|
|
|
|
+
|
|
|
|
+const metadata = computed({
|
|
|
|
+ get () { return JSON.stringify(state.user.meta ?? {}, null, 2) },
|
|
|
|
+ set (val) {
|
|
|
|
+ try {
|
|
|
|
+ state.user.meta = JSON.parse(val)
|
|
|
|
+ state.metadataInvalidJSON = false
|
|
|
|
+ } catch (err) {
|
|
|
|
+ state.metadataInvalidJSON = true
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const localAuthId = computed(() => {
|
|
|
|
+ return findKey(state.user.auth, ['module', 'local'])
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const localAuth = computed({
|
|
|
|
+ get () {
|
|
|
|
+ return localAuthId.value ? _get(state.user.auth, localAuthId.value, {}) : {}
|
|
},
|
|
},
|
|
- computed: {
|
|
|
|
- timezones: get('data/timezones', false),
|
|
|
|
- userId: get('admin/overlayOpts@id', false),
|
|
|
|
- metadata: {
|
|
|
|
- get () { return JSON.stringify(this.user.meta ?? {}, null, 2) },
|
|
|
|
- set (val) {
|
|
|
|
- try {
|
|
|
|
- this.user.meta = JSON.parse(val)
|
|
|
|
- this.metadataInvalidJSON = false
|
|
|
|
- } catch (err) {
|
|
|
|
- this.metadataInvalidJSON = true
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- localAuthId () {
|
|
|
|
- return findKey(this.user.auth, ['module', 'local'])
|
|
|
|
- },
|
|
|
|
- localAuth: {
|
|
|
|
- get () {
|
|
|
|
- return this.localAuthId ? _get(this.user.auth, this.localAuthId, {}) : {}
|
|
|
|
- },
|
|
|
|
- set (val) {
|
|
|
|
- if (this.localAuthId) {
|
|
|
|
- this.user.auth[this.localAuthId] = val
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- linkedAuthProviders () {
|
|
|
|
- if (!this.user?.auth) { return [] }
|
|
|
|
-
|
|
|
|
- return map(this.user.auth, (obj, key) => {
|
|
|
|
- return {
|
|
|
|
- ...obj,
|
|
|
|
- _id: key
|
|
|
|
- }
|
|
|
|
- }).filter(prv => prv.module !== 'local')
|
|
|
|
|
|
+ set (val) {
|
|
|
|
+ if (localAuthId.value) {
|
|
|
|
+ state.user.auth[localAuthId.value] = val
|
|
}
|
|
}
|
|
- },
|
|
|
|
- watch: {
|
|
|
|
- $route: 'checkRoute'
|
|
|
|
- },
|
|
|
|
- mounted () {
|
|
|
|
- this.checkRoute()
|
|
|
|
- this.load()
|
|
|
|
- },
|
|
|
|
- methods: {
|
|
|
|
- async load () {
|
|
|
|
- this.loading++
|
|
|
|
- this.$q.loading.show()
|
|
|
|
- try {
|
|
|
|
- const resp = await this.$apollo.query({
|
|
|
|
- query: gql`
|
|
|
|
- query adminFetchUser (
|
|
|
|
- $id: UUID!
|
|
|
|
- ) {
|
|
|
|
- groups {
|
|
|
|
- id
|
|
|
|
- name
|
|
|
|
- }
|
|
|
|
- userById(
|
|
|
|
- id: $id
|
|
|
|
- ) {
|
|
|
|
- id
|
|
|
|
- email
|
|
|
|
- name
|
|
|
|
- isSystem
|
|
|
|
- isVerified
|
|
|
|
- isActive
|
|
|
|
- auth
|
|
|
|
- meta
|
|
|
|
- prefs
|
|
|
|
- lastLoginAt
|
|
|
|
- createdAt
|
|
|
|
- updatedAt
|
|
|
|
- groups {
|
|
|
|
- id
|
|
|
|
- name
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+const linkedAuthProviders = computed(() => {
|
|
|
|
+ if (!state.user?.auth) { return [] }
|
|
|
|
+
|
|
|
|
+ return map(state.user.auth, (obj, key) => {
|
|
|
|
+ return {
|
|
|
|
+ ...obj,
|
|
|
|
+ _id: key
|
|
|
|
+ }
|
|
|
|
+ }).filter(prv => prv.module !== 'local')
|
|
|
|
+})
|
|
|
|
+
|
|
|
|
+// WATCHERS
|
|
|
|
+
|
|
|
|
+watch(() => route.params.section, checkRoute)
|
|
|
|
+
|
|
|
|
+// METHODS
|
|
|
|
+
|
|
|
|
+async function fetchUser () {
|
|
|
|
+ state.loading++
|
|
|
|
+ $q.loading.show()
|
|
|
|
+ try {
|
|
|
|
+ const resp = await APOLLO_CLIENT.query({
|
|
|
|
+ query: gql`
|
|
|
|
+ query adminFetchUser (
|
|
|
|
+ $id: UUID!
|
|
|
|
+ ) {
|
|
|
|
+ groups {
|
|
|
|
+ id
|
|
|
|
+ name
|
|
|
|
+ }
|
|
|
|
+ userById(
|
|
|
|
+ id: $id
|
|
|
|
+ ) {
|
|
|
|
+ id
|
|
|
|
+ email
|
|
|
|
+ name
|
|
|
|
+ isSystem
|
|
|
|
+ isVerified
|
|
|
|
+ isActive
|
|
|
|
+ auth
|
|
|
|
+ meta
|
|
|
|
+ prefs
|
|
|
|
+ lastLoginAt
|
|
|
|
+ createdAt
|
|
|
|
+ updatedAt
|
|
|
|
+ groups {
|
|
|
|
+ id
|
|
|
|
+ name
|
|
}
|
|
}
|
|
- `,
|
|
|
|
- variables: {
|
|
|
|
- id: this.userId
|
|
|
|
- },
|
|
|
|
- fetchPolicy: 'network-only'
|
|
|
|
- })
|
|
|
|
- this.groups = resp?.data?.groups?.filter(g => g.id !== '10000000-0000-4000-0000-000000000001') ?? []
|
|
|
|
- if (resp?.data?.userById) {
|
|
|
|
- this.user = cloneDeep(resp.data.userById)
|
|
|
|
- } else {
|
|
|
|
- throw new Error('An unexpected error occured while fetching user details.')
|
|
|
|
- }
|
|
|
|
- } catch (err) {
|
|
|
|
- this.$q.notify({
|
|
|
|
- type: 'negative',
|
|
|
|
- message: err.message
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
- this.$q.loading.hide()
|
|
|
|
- this.loading--
|
|
|
|
- },
|
|
|
|
- close () {
|
|
|
|
- this.$store.set('admin/overlay', '')
|
|
|
|
- },
|
|
|
|
- checkRoute () {
|
|
|
|
- if (!this.$route.params.section) {
|
|
|
|
- this.$router.replace({ params: { section: 'overview' } })
|
|
|
|
- }
|
|
|
|
- if (this.$route.params.section === 'metadata') {
|
|
|
|
- this.metadataInvalidJSON = false
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- humanizeDate (val) {
|
|
|
|
- if (!val) { return '---' }
|
|
|
|
- return DateTime.fromISO(val).toLocaleString(DateTime.DATETIME_FULL)
|
|
|
|
- },
|
|
|
|
- assignGroup () {
|
|
|
|
- if (!this.groupToAdd) {
|
|
|
|
- this.$q.notify({
|
|
|
|
- type: 'negative',
|
|
|
|
- message: this.$t('admin.users.noGroupSelected')
|
|
|
|
- })
|
|
|
|
- } else if (some(this.user.groups, gr => gr.id === this.groupToAdd)) {
|
|
|
|
- this.$q.notify({
|
|
|
|
- type: 'warning',
|
|
|
|
- message: this.$t('admin.users.groupAlreadyAssigned')
|
|
|
|
- })
|
|
|
|
- } else {
|
|
|
|
- const newGroup = find(this.groups, ['id', this.groupToAdd])
|
|
|
|
- this.user.groups = [...this.user.groups, newGroup]
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- unassignGroup (id) {
|
|
|
|
- if (this.user.groups.length <= 1) {
|
|
|
|
- this.$q.notify({
|
|
|
|
- type: 'negative',
|
|
|
|
- message: this.$t('admin.users.minimumGroupRequired')
|
|
|
|
- })
|
|
|
|
- } else {
|
|
|
|
- this.user.groups = this.user.groups.filter(gr => gr.id === id)
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- async save (patch, { silent, keepOpen } = { silent: false, keepOpen: false }) {
|
|
|
|
- this.$q.loading.show()
|
|
|
|
- if (!patch) {
|
|
|
|
- patch = {
|
|
|
|
- name: this.user.name,
|
|
|
|
- email: this.user.email,
|
|
|
|
- isVerified: this.user.isVerified,
|
|
|
|
- isActive: this.user.isActive,
|
|
|
|
- meta: this.user.meta,
|
|
|
|
- prefs: this.user.prefs,
|
|
|
|
- groups: this.user.groups.map(gr => gr.id)
|
|
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- }
|
|
|
|
- try {
|
|
|
|
- const resp = await this.$apollo.mutate({
|
|
|
|
- mutation: gql`
|
|
|
|
- mutation adminSaveUser (
|
|
|
|
- $id: UUID!
|
|
|
|
- $patch: UserUpdateInput!
|
|
|
|
- ) {
|
|
|
|
- updateUser (
|
|
|
|
- id: $id
|
|
|
|
- patch: $patch
|
|
|
|
- ) {
|
|
|
|
- status {
|
|
|
|
- succeeded
|
|
|
|
- message
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ `,
|
|
|
|
+ variables: {
|
|
|
|
+ id: adminStore.overlayOpts.id
|
|
|
|
+ },
|
|
|
|
+ fetchPolicy: 'network-only'
|
|
|
|
+ })
|
|
|
|
+ state.groups = resp?.data?.groups?.filter(g => g.id !== '10000000-0000-4000-0000-000000000001') ?? []
|
|
|
|
+ if (resp?.data?.userById) {
|
|
|
|
+ state.user = cloneDeep(resp.data.userById)
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error('An unexpected error occured while fetching user details.')
|
|
|
|
+ }
|
|
|
|
+ } catch (err) {
|
|
|
|
+ $q.notify({
|
|
|
|
+ type: 'negative',
|
|
|
|
+ message: err.message
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+ $q.loading.hide()
|
|
|
|
+ state.loading--
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function close () {
|
|
|
|
+ adminStore.$patch({ overlay: '' })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function checkRoute () {
|
|
|
|
+ if (!route.params.section) {
|
|
|
|
+ router.replace({ params: { section: 'overview' } })
|
|
|
|
+ }
|
|
|
|
+ if (route.params.section === 'metadata') {
|
|
|
|
+ state.metadataInvalidJSON = false
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function humanizeDate (val) {
|
|
|
|
+ if (!val) { return '---' }
|
|
|
|
+ return DateTime.fromISO(val).toLocaleString(DateTime.DATETIME_FULL)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function assignGroup () {
|
|
|
|
+ if (!state.groupToAdd) {
|
|
|
|
+ $q.notify({
|
|
|
|
+ type: 'negative',
|
|
|
|
+ message: t('admin.users.noGroupSelected')
|
|
|
|
+ })
|
|
|
|
+ } else if (some(state.user.groups, gr => gr.id === state.groupToAdd)) {
|
|
|
|
+ $q.notify({
|
|
|
|
+ type: 'warning',
|
|
|
|
+ message: t('admin.users.groupAlreadyAssigned')
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ const newGroup = find(state.groups, ['id', state.groupToAdd])
|
|
|
|
+ state.user.groups = [...state.user.groups, newGroup]
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function unassignGroup (id) {
|
|
|
|
+ if (state.user.groups.length <= 1) {
|
|
|
|
+ $q.notify({
|
|
|
|
+ type: 'negative',
|
|
|
|
+ message: t('admin.users.minimumGroupRequired')
|
|
|
|
+ })
|
|
|
|
+ } else {
|
|
|
|
+ state.user.groups = state.user.groups.filter(gr => gr.id === id)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function save (patch, { silent, keepOpen } = { silent: false, keepOpen: false }) {
|
|
|
|
+ $q.loading.show()
|
|
|
|
+ if (!patch) {
|
|
|
|
+ patch = {
|
|
|
|
+ name: state.user.name,
|
|
|
|
+ email: state.user.email,
|
|
|
|
+ isVerified: state.user.isVerified,
|
|
|
|
+ isActive: state.user.isActive,
|
|
|
|
+ meta: state.user.meta,
|
|
|
|
+ prefs: state.user.prefs,
|
|
|
|
+ groups: state.user.groups.map(gr => gr.id)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ try {
|
|
|
|
+ const resp = await APOLLO_CLIENT.mutate({
|
|
|
|
+ mutation: gql`
|
|
|
|
+ mutation adminSaveUser (
|
|
|
|
+ $id: UUID!
|
|
|
|
+ $patch: UserUpdateInput!
|
|
|
|
+ ) {
|
|
|
|
+ updateUser (
|
|
|
|
+ id: $id
|
|
|
|
+ patch: $patch
|
|
|
|
+ ) {
|
|
|
|
+ operation {
|
|
|
|
+ succeeded
|
|
|
|
+ message
|
|
}
|
|
}
|
|
- `,
|
|
|
|
- variables: {
|
|
|
|
- id: this.userId,
|
|
|
|
- patch
|
|
|
|
}
|
|
}
|
|
- })
|
|
|
|
- if (resp?.data?.updateUser?.status?.succeeded) {
|
|
|
|
- if (!silent) {
|
|
|
|
- this.$q.notify({
|
|
|
|
- type: 'positive',
|
|
|
|
- message: this.$t('admin.users.saveSuccess')
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
- if (!keepOpen) {
|
|
|
|
- this.close()
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- throw new Error(resp?.data?.updateUser?.status?.message || 'An unexpected error occured.')
|
|
|
|
}
|
|
}
|
|
- } catch (err) {
|
|
|
|
- this.$q.notify({
|
|
|
|
- type: 'negative',
|
|
|
|
- message: err.message
|
|
|
|
- })
|
|
|
|
|
|
+ `,
|
|
|
|
+ variables: {
|
|
|
|
+ id: adminStore.overlayOpts.id,
|
|
|
|
+ patch
|
|
}
|
|
}
|
|
- this.$q.loading.hide()
|
|
|
|
- },
|
|
|
|
- changePassword () {
|
|
|
|
- this.$q.dialog({
|
|
|
|
- component: UserChangePwdDialog,
|
|
|
|
- componentProps: {
|
|
|
|
- userId: this.userId
|
|
|
|
- }
|
|
|
|
- }).onOk(({ mustChangePassword }) => {
|
|
|
|
- this.localAuth = {
|
|
|
|
- ...this.localAuth,
|
|
|
|
- mustChangePwd: mustChangePassword
|
|
|
|
- }
|
|
|
|
- })
|
|
|
|
- },
|
|
|
|
- invalidateTFA () {
|
|
|
|
- this.$q.dialog({
|
|
|
|
- title: this.$t('admin.users.tfaInvalidate'),
|
|
|
|
- message: this.$t('admin.users.tfaInvalidateConfirm'),
|
|
|
|
- cancel: true,
|
|
|
|
- persistent: true,
|
|
|
|
- ok: {
|
|
|
|
- label: this.$t('common.actions.confirm')
|
|
|
|
- }
|
|
|
|
- }).onOk(() => {
|
|
|
|
- this.localAuth.tfaSecret = ''
|
|
|
|
- this.$q.notify({
|
|
|
|
|
|
+ })
|
|
|
|
+ if (resp?.data?.updateUser?.operation?.succeeded) {
|
|
|
|
+ if (!silent) {
|
|
|
|
+ $q.notify({
|
|
type: 'positive',
|
|
type: 'positive',
|
|
- message: this.$t('admin.users.tfaInvalidateSuccess')
|
|
|
|
|
|
+ message: t('admin.users.saveSuccess')
|
|
})
|
|
})
|
|
- })
|
|
|
|
- },
|
|
|
|
- async sendWelcomeEmail () {
|
|
|
|
-
|
|
|
|
- },
|
|
|
|
- toggleVerified () {
|
|
|
|
- this.user.isVerified = !this.user.isVerified
|
|
|
|
- this.save({
|
|
|
|
- isVerified: this.user.isVerified
|
|
|
|
- }, { silent: true, keepOpen: true })
|
|
|
|
- },
|
|
|
|
- toggleBan () {
|
|
|
|
- this.user.isActive = !this.user.isActive
|
|
|
|
- this.save({
|
|
|
|
- isActive: this.user.isActive
|
|
|
|
- }, { silent: true, keepOpen: true })
|
|
|
|
- },
|
|
|
|
- async deleteUser () {
|
|
|
|
-
|
|
|
|
|
|
+ }
|
|
|
|
+ if (!keepOpen) {
|
|
|
|
+ close()
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ throw new Error(resp?.data?.updateUser?.operation?.message || 'An unexpected error occured.')
|
|
}
|
|
}
|
|
|
|
+ } catch (err) {
|
|
|
|
+ $q.notify({
|
|
|
|
+ type: 'negative',
|
|
|
|
+ message: err.message
|
|
|
|
+ })
|
|
}
|
|
}
|
|
|
|
+ $q.loading.hide()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function changePassword () {
|
|
|
|
+ $q.dialog({
|
|
|
|
+ component: UserChangePwdDialog,
|
|
|
|
+ componentProps: {
|
|
|
|
+ userId: adminStore.overlayOpts.id
|
|
|
|
+ }
|
|
|
|
+ }).onOk(({ mustChangePassword }) => {
|
|
|
|
+ localAuth.value = {
|
|
|
|
+ ...localAuth.value,
|
|
|
|
+ mustChangePwd: mustChangePassword
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function invalidateTFA () {
|
|
|
|
+ $q.dialog({
|
|
|
|
+ title: t('admin.users.tfaInvalidate'),
|
|
|
|
+ message: t('admin.users.tfaInvalidateConfirm'),
|
|
|
|
+ cancel: true,
|
|
|
|
+ persistent: true,
|
|
|
|
+ ok: {
|
|
|
|
+ label: t('common.actions.confirm')
|
|
|
|
+ }
|
|
|
|
+ }).onOk(() => {
|
|
|
|
+ localAuth.value.tfaSecret = ''
|
|
|
|
+ $q.notify({
|
|
|
|
+ type: 'positive',
|
|
|
|
+ message: t('admin.users.tfaInvalidateSuccess')
|
|
|
|
+ })
|
|
|
|
+ })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function sendWelcomeEmail () {
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+function toggleVerified () {
|
|
|
|
+ state.user.isVerified = !state.user.isVerified
|
|
|
|
+ save({
|
|
|
|
+ isVerified: state.user.isVerified
|
|
|
|
+ }, { silent: true, keepOpen: true })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function toggleBan () {
|
|
|
|
+ state.user.isActive = !state.user.isActive
|
|
|
|
+ save({
|
|
|
|
+ isActive: state.user.isActive
|
|
|
|
+ }, { silent: true, keepOpen: true })
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function deleteUser () {
|
|
|
|
+
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// MOUNTED
|
|
|
|
+
|
|
|
|
+onMounted(() => {
|
|
|
|
+ checkRoute()
|
|
|
|
+ fetchUser()
|
|
|
|
+})
|
|
|
|
+
|
|
</script>
|
|
</script>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
+.metadata-codemirror {
|
|
|
|
+ &:deep(.cm-editor) {
|
|
|
|
+ height: 150px;
|
|
|
|
+ min-height: 100px;
|
|
|
|
+ border-radius: 5px;
|
|
|
|
+ border: 1px solid #CCC;
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+</style>
|