q-layout(view='hHh lpR fFf', container)
q-header.card-header.q-px-md.q-py-sm
q-icon(name='img:/_assets/icons/fluent-people.svg', left, size='md')
div
span {{t(`admin.groups.edit`)}}
.text-caption {{state.group.name}}
q-space
q-btn-group(push)
q-btn(
push
color='grey-6'
text-color='white'
:aria-label='t(`common.actions.refresh`)'
icon='las la-redo-alt'
@click='refresh'
)
q-tooltip(anchor='center left', self='center right') {{t(`common.actions.refresh`)}}
q-btn(
push
color='white'
text-color='grey-7'
:label='t(`common.actions.close`)'
icon='las la-times'
@click='close'
)
q-btn(
push
color='positive'
text-color='white'
:label='t(`common.actions.save`)'
icon='las la-check'
)
q-drawer.bg-dark-6(:model-value='true', :width='250', dark)
q-list(padding, v-show='!state.isLoading')
q-item(
v-for='sc of sections'
:key='`section-` + sc.key'
clickable
:to='{ params: { section: sc.key } }'
active-class='bg-primary text-white'
:disabled='sc.disabled'
)
q-item-section(side)
q-icon(:name='sc.icon', color='white')
q-item-section {{sc.text}}
q-item-section(side, v-if='sc.usersTotal')
q-badge(color='dark-3', :label='state.usersTotal')
q-item-section(side, v-if='sc.rulesTotal && state.group.rules')
q-badge(color='dark-3', :label='state.group.rules.length')
q-page-container
q-page(v-if='state.isLoading')
//- -----------------------------------------------------------------------
//- OVERVIEW
//- -----------------------------------------------------------------------
q-page(v-else-if='route.params.section === `overview`')
.q-pa-md
.row.q-col-gutter-md
.col-12.col-lg-8
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{t('admin.groups.general')}}
q-item
blueprint-icon(icon='team')
q-item-section
q-item-label {{t(`admin.groups.name`)}}
q-item-label(caption) {{t(`admin.groups.nameHint`)}}
q-item-section
q-input(
outlined
v-model='state.group.name'
dense
:rules='groupNameValidation'
hide-bottom-space
:aria-label='t(`admin.groups.name`)'
)
q-card.shadow-1.q-pb-sm.q-mt-md
q-card-section
.text-subtitle1 {{t('admin.groups.authBehaviors')}}
q-item
blueprint-icon(icon='double-right')
q-item-section
q-item-label {{t(`admin.groups.redirectOnLogin`)}}
q-item-label(caption) {{t(`admin.groups.redirectOnLoginHint`)}}
q-item-section
q-input(
outlined
v-model='state.group.redirectOnLogin'
dense
:aria-label='t(`admin.groups.redirectOnLogin`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='chevron-right')
q-item-section
q-item-label {{t(`admin.groups.redirectOnFirstLogin`)}}
q-item-label(caption) {{t(`admin.groups.redirectOnFirstLoginHint`)}}
q-item-section
q-input(
outlined
v-model='state.group.redirectOnFirstLogin'
dense
:aria-label='t(`admin.groups.redirectOnLogin`)'
)
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='exit')
q-item-section
q-item-label {{t(`admin.groups.redirectOnLogout`)}}
q-item-label(caption) {{t(`admin.groups.redirectOnLogoutHint`)}}
q-item-section
q-input(
outlined
v-model='state.group.redirectOnLogout'
dense
:aria-label='t(`admin.groups.redirectOnLogout`)'
)
.col-12.col-lg-4
q-card.shadow-1.q-pb-sm
q-card-section
.text-subtitle1 {{t('admin.groups.info')}}
q-item
blueprint-icon(icon='team', :hue-rotate='-45')
q-item-section
q-item-label {{t(`common.field.id`)}}
q-item-label: strong {{state.group.id}}
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='calendar-plus', :hue-rotate='-45')
q-item-section
q-item-label {{t(`common.field.createdOn`)}}
q-item-label: strong {{humanizeDate(state.group.createdAt)}}
q-separator.q-my-sm(inset)
q-item
blueprint-icon(icon='summertime', :hue-rotate='-45')
q-item-section
q-item-label {{t(`common.field.lastUpdated`)}}
q-item-label: strong {{humanizeDate(state.group.updatedAt)}}
//- -----------------------------------------------------------------------
//- RULES
//- -----------------------------------------------------------------------
q-page(v-else-if='route.params.section === `rules`')
q-toolbar.q-pl-md(
:class='$q.dark.isActive ? `bg-dark-3` : `bg-white`'
)
.text-subtitle1 {{t('admin.groups.rules')}}
q-space
q-btn.acrylic-btn.q-mr-sm(
icon='las la-question-circle'
flat
color='grey'
type='a'
href='https://docs.js.wiki/admin/groups#rules'
target='_blank'
)
q-btn.acrylic-btn.q-mr-sm(
flat
color='indigo'
icon='las la-file-export'
@click='exportRules'
)
q-tooltip {{t('admin.groups.exportRules')}}
q-btn.acrylic-btn.q-mr-sm(
flat
color='indigo'
icon='las la-file-import'
@click='importRules'
)
q-tooltip {{t('admin.groups.importRules')}}
q-btn(
unelevated
color='primary'
icon='las la-plus'
label='New Rule'
@click='newRule'
)
q-separator
.q-pa-md
q-banner(
v-if='!state.group.rules || state.group.rules.length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-4 text-grey-9`'
) {{t('admin.groups.rulesNone')}}
q-card.shadow-1.q-pb-sm(v-else)
q-card-section
.admin-groups-rule(
v-for='(rule, idx) of state.group.rules'
:key='rule.id'
)
.admin-groups-rule-icon(:class='getRuleModeColor(rule.mode)')
q-icon.cursor-pointer(
:name='getRuleModeIcon(rule.mode)'
color='white'
@click='rule.mode = getNextRuleMode(rule.mode)'
)
.admin-groups-rule-name
.admin-groups-rule-name-text: strong(:class='getRuleModeColor(rule.mode)') {{getRuleModeName(rule.mode)}}
q-separator.q-ml-sm.q-mr-xs(vertical)
input(
type='text'
v-model='rule.name'
placeholder='Rule Name'
)
q-card.admin-groups-rule-card.q-mt-md(flat)
q-card-section.admin-groups-rule-card-permissions(:class='getRuleModeClass(rule.mode)')
q-select.q-mt-xs(
standout
v-model='rule.roles'
emit-value
map-options
dense
:aria-label='t(`admin.groups.ruleSites`)'
:options='rules'
placeholder='Select permissions...'
option-value='permission'
option-label='title'
options-dense
multiple
use-chips
stack-label
)
template(v-slot:option='{ itemProps, itemEvents, opt, selected, toggleOption }')
q-item(v-bind='itemProps', v-on='itemEvents')
q-item-section(side)
q-toggle(
:value='selected'
@input='toggleOption(opt)'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='opt.label'
)
//- q-item-section(side, style='flex-basis: 150px;')
//- q-chip.text-caption(
//- square
//- color='teal'
//- text-color='white'
//- dense
//- ) {{opt.permission}}
q-item-section
q-item-label {{opt.title}}
q-item-label(caption) {{opt.hint}}
q-btn.acrylic-btn.q-ml-md(
flat
icon='las la-trash'
color='negative'
padding='sm sm'
size='md',
@click='deleteRule(rule.id)'
)
q-card-section(horizontal)
q-card-section.admin-groups-rule-card-filters
.text-caption Applies to...
q-select.q-mt-xs(
standout
v-model='rule.sites'
emit-value
map-options
dense
:aria-label='t(`admin.groups.ruleSites`)'
:options='adminStore.sites'
option-value='id'
option-label='title'
multiple
behavior='dialog'
:display-value='t(`admin.groups.selectedSites`, rule.sites.length, { count: rule.sites.length })'
)
template(v-slot:option='{ itemProps, itemEvents, opt, selected, toggleOption }')
q-item(v-bind='itemProps', v-on='itemEvents')
q-item-section
q-item-label {{opt.title}}
q-item-section(side)
q-toggle(
:value='selected'
@input='toggleOption(opt)'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='opt.label'
)
q-select.q-mt-sm(
standout
v-model='rule.locales'
emit-value
map-options
dense
:aria-label='t(`admin.groups.ruleLocales`)'
:options='adminStore.locales'
option-value='code'
option-label='name'
multiple
behavior='dialog'
:display-value='t(`admin.groups.selectedLocales`, rule.locales.length, { count: rule.locales.length, locale: rule.locales.length === 1 ? rule.locales[0].toUpperCase() : `` })'
)
template(v-slot:option='{ itemProps, opt, selected, toggleOption }')
q-item(v-bind='itemProps')
q-item-section
q-item-label {{opt.name}}
q-item-section(side)
q-toggle(
:model-value='selected'
@update:model-value='toggleOption(opt)'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='opt.name'
)
q-card-section.admin-groups-rule-card-pattern
.text-caption Pattern
q-select.q-mt-xs(
standout
v-model='rule.match'
emit-value
map-options
dense
:aria-label='t(`admin.groups.ruleMatch`)'
:options=`[
{ label: t('admin.groups.ruleMatchStart'), value: 'START' },
{ label: t('admin.groups.ruleMatchEnd'), value: 'END' },
{ label: t('admin.groups.ruleMatchRegex'), value: 'REGEX' },
{ label: t('admin.groups.ruleMatchTag'), value: 'TAG' },
{ label: t('admin.groups.ruleMatchTagAll'), value: 'TAGALL' },
{ label: t('admin.groups.ruleMatchExact'), value: 'EXACT' }
]`
)
q-input.q-mt-sm(
standout
v-model='rule.path'
dense
:prefix='[`START`, `REGEX`, `EXACT`].includes(rule.match) ? `/` : null'
:suffix='rule.match === `REGEX` ? `/` : null'
:aria-label='t(`admin.groups.rulePath`)'
)
//- -----------------------------------------------------------------------
//- PERMISSIONS
//- -----------------------------------------------------------------------
q-page(v-else-if='route.params.section === `permissions`')
.q-pa-md
.row.q-col-gutter-md
.col-12.col-lg-6
q-card.shadow-1.q-pb-sm
.flex.justify-between
q-card-section
.text-subtitle1 {{t(`admin.groups.permissions`)}}
q-card-section
q-btn.acrylic-btn(
icon='las la-question-circle'
flat
color='grey'
type='a'
href='https://docs.js.wiki/admin/groups#permissions'
target='_blank'
)
template(v-for='(perm, idx) of permissions', :key='perm.permission')
q-item(tag='label', v-ripple)
q-item-section.items-center(style='flex: 0 0 40px;')
q-icon(
name='las la-comments'
color='primary'
size='sm'
)
q-item-section
q-item-label {{perm.permission}}
q-item-label(caption) {{perm.hint}}
q-item-section(avatar)
q-toggle(
v-model='state.group.permissions'
:val='perm.permission'
color='primary'
checked-icon='las la-check'
unchecked-icon='las la-times'
:aria-label='t(`admin.general.allowComments`)'
)
q-separator.q-my-sm(inset, v-if='idx < permissions.length - 1')
//- -----------------------------------------------------------------------
//- USERS
//- -----------------------------------------------------------------------
q-page(v-else-if='route.params.section === `users`')
q-toolbar(
:class='$q.dark.isActive ? `bg-dark-3` : `bg-white`'
)
.text-subtitle1 {{t('admin.groups.users')}}
q-space
q-btn.acrylic-btn.q-mr-sm(
icon='las la-question-circle'
flat
color='grey'
type='a'
href='https://docs.js.wiki/admin/groups#users'
target='_blank'
)
q-input.denser.fill-outline.q-mr-sm(
outlined
v-model='state.usersFilter'
:placeholder='t(`admin.groups.filterUsers`)'
dense
)
template(#prepend)
q-icon(name='las la-search')
q-btn.q-mr-sm.acrylic-btn(
icon='las la-redo-alt'
flat
color='secondary'
@click='refreshUsers'
)
q-btn.q-mr-xs(
unelevated
icon='las la-user-plus'
:label='t(`admin.groups.assignUser`)'
color='primary'
@click='assignUser'
)
q-separator
.q-pa-md
q-banner(
v-if='!state.users || state.users.length < 1'
rounded
:class='$q.dark.isActive ? `bg-negative text-white` : `bg-grey-4 text-grey-9`'
) {{t('admin.groups.usersNone')}}
q-card.shadow-1
q-table(
:rows='state.users'
:columns='usersHeaders'
row-key='id'
flat
hide-header
hide-bottom
:rows-per-page-options='[0]'
:loading='state.isLoadingUsers'
)
template(v-slot:body-cell-id='props')
q-td(:props='props')
q-icon(name='las la-user', color='primary', size='sm')
template(v-slot:body-cell-name='props')
q-td(:props='props')
.flex.items-center
strong {{props.value}}
q-icon.q-ml-sm(
v-if='props.row.isSystem'
name='las la-lock'
color='pink'
)
q-icon.q-ml-sm(
v-if='!props.row.isActive'
name='las la-ban'
color='pink'
)
template(v-slot:body-cell-email='props')
q-td(:props='props')
em {{ props.value }}
template(v-slot:body-cell-date='props')
q-td(:props='props')
i18n-t.text-caption(keypath='admin.users.createdAt', tag='div')
template(#date)
strong {{ humanizeDate(props.value) }}
i18n-t.text-caption(
v-if='props.row.lastLoginAt'
keypath='admin.users.lastLoginAt'
tag='div'
)
template(#date)
strong {{ humanizeDate(props.row.lastLoginAt) }}
template(v-slot:body-cell-edit='props')
q-td(:props='props')
q-btn.acrylic-btn.q-mr-sm(
v-if='!props.row.isSystem'
flat
:to='`/_admin/users/` + props.row.id'
icon='las la-pen'
color='indigo'
:label='t(`common.actions.edit`)'
no-caps
)
q-btn.acrylic-btn(
v-if='!props.row.isSystem'
flat
icon='las la-user-minus'
color='accent'
@click='unassignUser(props.row)'
)
.flex.flex-center.q-mt-md(v-if='usersTotalPages > 1')
q-pagination(
v-model='state.usersPage'
:max='usersTotalPages'
:max-pages='9'
boundary-numbers
direction-links
)