feat: admin - manage groups + permissions + page rules

This commit is contained in:
Nicolas Giard
2018-12-29 21:30:51 -05:00
parent 10940ca230
commit edb97b832d
36 changed files with 1116 additions and 958 deletions

View File

@@ -1,211 +0,0 @@
<template lang="pug">
.criterias-item
//- Type
v-select(
solo
:items='filteredCriteriaTypes'
v-model='item.type'
placeholder='Rule Type'
ref='typeSelect'
hide-details
)
template(slot='item', slot-scope='data')
v-list-tile-avatar
v-avatar(:color='data.item.color', size='40', tile): v-icon(color='white') {{ data.item.icon }}
v-list-tile-content
v-list-tile-title(v-html='data.item.text')
v-list-tile-sub-title.caption(v-html='data.item.description')
//- Operator
v-select(
solo
:items='filteredCriteriaOperators'
v-model='item.operator'
placeholder='Operator'
:disabled='!item.type'
:class='!item.type ? "blue-grey lighten-4" : ""'
hide-details
)
template(slot='item', slot-scope='data')
v-list-tile-avatar
v-avatar.white--text(color='blue', size='30', tile) {{ data.item.icon }}
v-list-tile-content
v-list-tile-title(v-html='data.item.text')
//- Value
v-select(
v-if='item.type === "country"'
solo
:items='countries'
v-model='item.value'
placeholder='Countries...'
multiple
item-text='name'
item-value='code'
hide-details
)
v-text-field(
v-else-if='item.type === "path"'
solo
v-model='item.value'
label='Path (e.g. /section)'
hide-details
)
v-text-field(
v-else-if='item.type === "date"'
solo
@click.native.stop='dateActivator = true'
v-model='item.value'
label='YYYY-MM-DD'
readonly
hide-details
)
v-text-field(
v-else-if='item.type === "time"'
solo
@click.native.stop='timeActivator = true'
v-model='item.value'
label='HH:MM'
readonly
hide-details
)
v-select(
v-else-if='item.type === "group"'
solo
:items='groups'
v-model='item.value'
placeholder='Group...'
item-text='name'
item-value='id'
hide-details
)
v-text-field.blue-grey.lighten-4(
v-else
solo
disabled
hide-details
)
v-dialog(lazy, v-model='dateActivator', width='290px', ref='dateDialog')
v-date-picker(v-model='item.value', scrollable, color='primary')
v-btn(flat, color='primary' @click='$refs.dateDialog.save(date)', block) ok
v-dialog(lazy, v-model='timeActivator', width='300px', ref='timeDialog')
v-time-picker(v-model='item.value', scrollable, color='primary')
v-btn(flat, color='primary' @click='$refs.timeDialog.save(time)', block) ok
v-btn(icon, @click='remove'): v-icon(color='blue-grey') clear
</template>
<script>
import _ from 'lodash'
// import countriesQuery from 'gql/upsells-query-countries.gql'
export default {
inject: ['allowedCriteriaTypes'],
props: {
value: {
type: Object,
default() { return {} }
},
groupIndex: {
type: Number,
default() { return 0 }
},
itemIndex: {
type: Number,
default() { return 0 }
}
},
data() {
return {
item: {
operator: '',
type: '',
value: ''
},
dateActivator: false,
dateDialog: false,
timeActivator: false,
timeDialog: false,
countries: [],
groups: [],
criteriaTypes: [
{ text: 'Path', value: 'path', icon: 'space_bar', color: 'blue', description: 'Match the path of the document being viewed.' },
{ text: 'Date', value: 'date', icon: 'date_range', color: 'blue', description: 'Match the current calendar day.' },
{ text: 'Time', value: 'time', icon: 'access_time', color: 'blue', description: 'Match the current time of day.' },
{ text: 'User Country', value: 'country', icon: 'public', color: 'red', description: `Match the user's country.` },
{ text: 'User Group', value: 'group', icon: 'group', color: 'orange', description: 'Match the user group assignments.' }
],
criteriaOperators: {
country: [
{ text: 'In', value: 'in', icon: '[...]' },
{ text: 'Not In', value: 'notIn', icon: '[ x ]' }
],
path: [
{ text: 'Matches Exactly', value: 'eq', icon: '=' },
{ text: 'NOT Matches Exactly', value: 'ne', icon: '!=' },
{ text: 'Starts With', value: 'sw', icon: 'x...' },
{ text: 'NOT Starts With', value: 'nsw', icon: '!x...' },
{ text: 'Ends With', value: 'ew', icon: '...x' },
{ text: 'NOT Ends With', value: 'new', icon: '!...x' },
{ text: 'Matches Regex', value: 'regexp', icon: '^x$' }
],
date: [
{ text: 'On or After', value: 'gte', icon: '>=' },
{ text: 'On or Before', value: 'lte', icon: '<=' }
],
time: [
{ text: 'At or Later Than', value: 'gte', icon: '>=' },
{ text: 'At or Before', value: 'lte', icon: '<=' }
],
group: [
{ text: 'Is Part Of', value: 'in', icon: '[...]' },
{ text: 'Is Not Part Of', value: 'notIn', icon: '[ x ]' }
]
}
}
},
computed: {
filteredCriteriaOperators() {
return _.get(this.criteriaOperators, this.item.type, [])
},
filteredCriteriaTypes() {
console.info(this.allowedCriteriaTypes)
return _.filter(this.criteriaTypes, c => _.includes(this.allowedCriteriaTypes, c.value))
},
itemType() {
return this.item.type
}
},
watch: {
itemType(newValue, oldValue) {
this.item.operator = _.head(this.criteriaOperators[newValue]).value
this.item.value = ''
},
item: {
handler(newValue, oldValue) {
this.$emit('update', this.groupIndex, this.itemIndex, this.item)
},
deep: true
}
},
mounted() {
if (!this.item.type) {
this.$refs.typeSelect.showMenu()
}
},
methods: {
remove() {
this.$emit('remove', this.groupIndex, this.itemIndex)
}
}
// apollo: {
// countries: {
// query: countriesQuery,
// update: (data) => data.location.countries
// }
// }
}
</script>

View File

@@ -1,173 +0,0 @@
<template lang="pug">
.criterias
transition-group(name='criterias-group', tag='div')
.criterias-group(v-for='(group, g) in groups', :key='g')
transition-group(name='criterias-item', tag='div')
criterias-item(v-for='(item, i) in group', :key='i', :item='item', :group-index='g', :item-index='i', @update='updateItem', @remove='removeItem')
.criterias-item-more
v-btn.ml-0(@click='addItem(group)', small, color='blue-grey lighten-2', dark, depressed)
v-icon(color='white', left) add
| Add condition
.criterias-group-more
v-btn(@click='addGroup', small, color='blue-grey lighten-1', dark, depressed)
v-icon(color='white', left) add
| Add condition group
</template>
<script>
import CriteriasItem from './criterias-item.vue'
export default {
components: {
CriteriasItem
},
props: {
value: {
type: Array,
default() { return [] }
},
types: {
type: Array,
default() {
return ['country', 'path', 'date', 'time', 'group']
}
}
},
provide () {
return {
allowedCriteriaTypes: this.types
}
},
data() {
return {
dataGroups: this.value || []
}
},
computed: {
groups: {
get() { return this.dataGroups },
set(grp) {
this.dataGroups = grp
}
}
},
watch: {
dataGroups(newValue, oldValue) {
if (newValue !== oldValue) {
this.$emit('input', newValue)
}
}
},
methods: {
addGroup() {
this.dataGroups.push([{}])
},
addItem(group) {
group.push({})
},
updateItem(groupIndex, itemIndex, item) {
console.info(item)
this.$set(this.dataGroups[groupIndex], itemIndex, item)
},
removeItem(groupIndex, itemIndex) {
this.dataGroups[groupIndex].splice(itemIndex, 1)
if (this.dataGroups[groupIndex].length < 1) {
this.dataGroups.splice(groupIndex, 1)
}
}
}
}
</script>
<style lang="scss">
.criterias {
&-group {
background-color: mc('blue-grey', '100');
border-radius: 4px;
padding: 1rem;
position: relative;
&-enter-active, &-leave-active {
transition: all .5s ease;
}
&-enter, &-leave-to {
opacity: 0;
}
& + .criterias-group {
margin-top: 1rem;
&::before {
content: 'OR';
position: absolute;
display: inline-flex;
padding: 0 2rem;
top: -1.25rem;
left: 2rem;
background-color: mc('blue-grey', '100');
color: mc('blue-grey', '700');
font-weight: 600;
font-size: .9rem;
}
}
&-more {
margin: .5rem 0 0 .4rem;
}
}
&-item {
display: flex;
background-color: mc('blue-grey', '200');
border-radius: 4px;
padding: .5rem;
&-enter-active, &-leave-active {
transition: all .5s ease;
}
&-enter, &-leave-to {
opacity: 0;
}
& + .criterias-item {
margin-top: .5rem;
position: relative;
&::before {
content: 'AND';
position: absolute;
width: 2rem;
height: 2rem;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-weight: 600;
color: mc('blue-grey', '700');
font-size: .7rem;
background-color: mc('blue-grey', '100');
left: -2rem;
top: -1.3rem;
}
}
.input-group {
&:nth-child(1) {
flex: 0 1 350px;
}
&:nth-child(2) {
flex: 0 1 250px;
}
& + * {
margin-left: .5rem;
}
}
&-more {
margin-top: .15rem;
}
}
}
</style>

View File

@@ -16,117 +16,123 @@
:loading='searchIsLoading',
@keyup.enter='searchEnter'
)
v-menu(open-on-hover, offset-y, bottom, left, min-width='250')
v-toolbar-side-icon.btn-animate-app(slot='activator')
v-icon view_module
v-list(dense, :light='!$vuetify.dark', :dark='$vuetify.dark', :class='$vuetify.dark ? `grey darken-4` : ``').py-0
v-list-tile(avatar, href='/')
v-list-tile-avatar: v-icon(color='blue') home
v-list-tile-content Home
v-list-tile(avatar, @click='pageNew')
v-list-tile-avatar: v-icon(color='green') add_box
v-list-tile-content New Page
template(v-if='path && path.length')
v-divider.my-0
v-subheader Current Page
v-list-tile(avatar, @click='pageView', v-if='mode !== `view`')
v-list-tile-avatar: v-icon(color='indigo') subject
v-list-tile-content View
v-list-tile(avatar, @click='pageEdit', v-if='mode !== `edit`')
v-list-tile-avatar: v-icon(color='indigo') edit
v-list-tile-content Edit
v-list-tile(avatar, @click='pageHistory', v-if='mode !== `history`')
v-list-tile-avatar: v-icon(color='indigo') history
v-list-tile-content History
v-list-tile(avatar, @click='pageSource', v-if='mode !== `source`')
v-list-tile-avatar: v-icon(color='indigo') code
v-list-tile-content View Source
v-list-tile(avatar, @click='pageMove')
v-list-tile-avatar: v-icon(color='indigo') forward
v-list-tile-content Move / Rename
v-list-tile(avatar, @click='pageDelete')
v-list-tile-avatar: v-icon(color='red darken-2') delete
v-list-tile-content Delete
v-divider.my-0
v-subheader Assets
v-list-tile(avatar, @click='')
v-list-tile-avatar: v-icon(color='blue-grey') burst_mode
v-list-tile-content Images &amp; Files
v-toolbar-title(:class='{ "ml-2": $vuetify.breakpoint.mdAndUp, "ml-0": $vuetify.breakpoint.smAndDown }')
span.subheading {{title}}
v-spacer(v-if='searchIsShown && $vuetify.breakpoint.mdAndUp')
transition(name='navHeaderSearch')
v-text-field(
ref='searchField',
v-if='searchIsShown && $vuetify.breakpoint.mdAndUp',
v-model='search',
clearable,
color='white',
label='Search...',
single-line,
solo
flat
hide-details,
prepend-inner-icon='search',
:loading='searchIsLoading',
@keyup.enter='searchEnter'
)
v-progress-linear(
indeterminate,
slot='progress',
height='2',
color='blue'
)
v-spacer
.navHeaderLoading.mr-3
v-progress-circular(indeterminate, color='blue', :size='22', :width='2' v-show='isLoading')
slot(name='actions')
v-btn(
v-if='!hideSearch && $vuetify.breakpoint.smAndDown'
@click='searchToggle'
icon
)
v-icon(color='grey') search
v-tooltip(bottom, v-if='isAuthenticated && isAdmin')
v-btn.btn-animate-rotate(icon, href='/a', slot='activator')
v-icon(color='grey') settings
span Admin
v-menu(offset-y, min-width='300')
v-tooltip(bottom, slot='activator')
v-btn.btn-animate-grow(icon, slot='activator', outline, :color='isAuthenticated ? `blue` : `grey darken-3`')
v-icon(color='grey') account_circle
span Account
v-list.py-0
template(v-if='isAuthenticated')
v-list-tile.py-3.grey(avatar, :class='$vuetify.dark ? `darken-4-l5` : `lighten-5`')
v-list-tile-avatar
v-avatar.blue(v-if='picture.kind === `initials`', :size='40')
span.white--text.subheading {{picture.initials}}
v-avatar(v-else-if='picture.kind === `image`', :size='40')
v-img(:src='picture.url')
v-list-tile-content
v-list-tile-title {{name}}
v-list-tile-sub-title {{email}}
v-divider.my-0
v-list-tile(href='/w')
v-list-tile-action: v-icon(color='blue') web
v-list-tile-title My Wiki
v-divider.my-0
v-list-tile(href='/p')
v-list-tile-action: v-icon(color='blue') person
v-list-tile-title Profile
v-divider.my-0
v-list-tile(@click='logout')
v-list-tile-action: v-icon(color='red') exit_to_app
v-list-tile-title Logout
template(v-else)
v-list-tile(href='/login')
v-list-tile-action: v-icon(color='grey') person
v-list-tile-title Login
v-divider.my-0
v-list-tile(href='/register')
v-list-tile-action: v-icon(color='grey') person_add
v-list-tile-title Register
v-layout(row)
v-flex(xs6, :md4='searchIsShown', :md6='!searchIsShown')
v-toolbar.nav-header-inner(color='black', dark, flat)
v-menu(open-on-hover, offset-y, bottom, left, min-width='250')
v-toolbar-side-icon.btn-animate-app(slot='activator')
v-icon view_module
v-list(dense, :light='!$vuetify.dark', :dark='$vuetify.dark', :class='$vuetify.dark ? `grey darken-4` : ``').py-0
v-list-tile(avatar, href='/')
v-list-tile-avatar: v-icon(color='blue') home
v-list-tile-content Home
v-list-tile(avatar, @click='pageNew')
v-list-tile-avatar: v-icon(color='green') add_box
v-list-tile-content New Page
template(v-if='path && path.length')
v-divider.my-0
v-subheader Current Page
v-list-tile(avatar, @click='pageView', v-if='mode !== `view`')
v-list-tile-avatar: v-icon(color='indigo') subject
v-list-tile-content View
v-list-tile(avatar, @click='pageEdit', v-if='mode !== `edit`')
v-list-tile-avatar: v-icon(color='indigo') edit
v-list-tile-content Edit
v-list-tile(avatar, @click='pageHistory', v-if='mode !== `history`')
v-list-tile-avatar: v-icon(color='indigo') history
v-list-tile-content History
v-list-tile(avatar, @click='pageSource', v-if='mode !== `source`')
v-list-tile-avatar: v-icon(color='indigo') code
v-list-tile-content View Source
v-list-tile(avatar, @click='pageMove')
v-list-tile-avatar: v-icon(color='indigo') forward
v-list-tile-content Move / Rename
v-list-tile(avatar, @click='pageDelete')
v-list-tile-avatar: v-icon(color='red darken-2') delete
v-list-tile-content Delete
v-divider.my-0
v-subheader Assets
v-list-tile(avatar, @click='')
v-list-tile-avatar: v-icon(color='blue-grey') burst_mode
v-list-tile-content Images &amp; Files
v-toolbar-title(:class='{ "ml-2": $vuetify.breakpoint.mdAndUp, "ml-0": $vuetify.breakpoint.smAndDown }')
span.subheading {{title}}
v-flex(md4, v-if='searchIsShown && $vuetify.breakpoint.mdAndUp')
v-toolbar.nav-header-inner(color='black', dark, flat)
transition(name='navHeaderSearch')
v-text-field(
ref='searchField',
v-if='searchIsShown && $vuetify.breakpoint.mdAndUp',
v-model='search',
clearable,
color='white',
label='Search...',
single-line,
solo
flat
hide-details,
prepend-inner-icon='search',
:loading='searchIsLoading',
@keyup.enter='searchEnter'
)
v-progress-linear(
indeterminate,
slot='progress',
height='2',
color='blue'
)
v-flex(xs6, :md4='searchIsShown', :md6='!searchIsShown')
v-toolbar.nav-header-inner(color='black', dark, flat)
v-spacer
.navHeaderLoading.mr-3
v-progress-circular(indeterminate, color='blue', :size='22', :width='2' v-show='isLoading')
slot(name='actions')
v-btn(
v-if='!hideSearch && $vuetify.breakpoint.smAndDown'
@click='searchToggle'
icon
)
v-icon(color='grey') search
v-tooltip(bottom, v-if='isAuthenticated && isAdmin')
v-btn.btn-animate-rotate(icon, href='/a', slot='activator')
v-icon(color='grey') settings
span Admin
v-menu(offset-y, min-width='300')
v-tooltip(bottom, slot='activator')
v-btn.btn-animate-grow(icon, slot='activator', outline, :color='isAuthenticated ? `blue` : `grey darken-3`')
v-icon(color='grey') account_circle
span Account
v-list.py-0
template(v-if='isAuthenticated')
v-list-tile.py-3.grey(avatar, :class='$vuetify.dark ? `darken-4-l5` : `lighten-5`')
v-list-tile-avatar
v-avatar.blue(v-if='picture.kind === `initials`', :size='40')
span.white--text.subheading {{picture.initials}}
v-avatar(v-else-if='picture.kind === `image`', :size='40')
v-img(:src='picture.url')
v-list-tile-content
v-list-tile-title {{name}}
v-list-tile-sub-title {{email}}
v-divider.my-0
v-list-tile(href='/w')
v-list-tile-action: v-icon(color='blue') web
v-list-tile-title My Wiki
v-divider.my-0
v-list-tile(href='/p')
v-list-tile-action: v-icon(color='blue') person
v-list-tile-title Profile
v-divider.my-0
v-list-tile(@click='logout')
v-list-tile-action: v-icon(color='red') exit_to_app
v-list-tile-title Logout
template(v-else)
v-list-tile(href='/login')
v-list-tile-action: v-icon(color='grey') person
v-list-tile-title Login
v-divider.my-0
v-list-tile(href='/register')
v-list-tile-action: v-icon(color='grey') person_add
v-list-tile-title Register
page-selector(mode='create', v-model='newPageModal', :open-handler='pageNewCreate')
</template>
@@ -251,6 +257,12 @@ export default {
padding-right: 14px;
}
}
&-inner {
.v-toolbar__content {
padding: 0;
}
}
}
.navHeaderSearch {

View File

@@ -3,7 +3,7 @@
v-model='dialogOpen'
max-width='650'
)
v-card
v-card.wiki-form
.dialog-header
span Search User
v-spacer
@@ -16,23 +16,25 @@
)
v-card-text
v-text-field(
solo
flat
outline
label='Search Users...'
v-model='search'
prepend-icon='search'
:background-color='$vuetify.dark ? "grey darken-4" : "blue lighten-5"'
prepend-inner-icon='search'
color='primary'
ref='searchIpt'
hide-details
)
v-list(two-line)
v-list.grey.mt-3.py-0.radius-7(
:class='$vuetify.dark ? `darken-3-d5` : `lighten-3`'
two-line
dense
)
template(v-for='(usr, idx) in items')
v-list-tile(:key='usr.id', @click='setUser(usr.id)')
v-list-tile-avatar(size='40', color='primary')
span.body-1.white--text {{usr.name | initials}}
v-list-tile-content
v-list-tile-title {{usr.name}}
v-list-tile-title.body-2 {{usr.name}}
v-list-tile-sub-title {{usr.email}}
v-list-tile-action
v-icon(color='primary') arrow_forward