feat(admin): migrate groups dialogs to vue 3 composable

pull/5698/head
Nicolas Giard 2 years ago
parent 7e344fc6fa
commit 6e303ac648
No known key found for this signature in database
GPG Key ID: 85061B8F9D55B7C8

@ -131,9 +131,9 @@ groups:
- 'read:assets'
- 'read:comments'
- 'write:comments'
defaultPageRules:
- id: default
deny: false
defaultRules:
- name: Default Rule
mode: ALLOW
match: START
roles:
- 'read:pages'
@ -142,6 +142,7 @@ groups:
- 'write:comments'
path: ''
locales: []
sites: []
reservedPaths:
- login
- logout

@ -1,7 +1,7 @@
const graphHelper = require('../../helpers/graph')
const safeRegex = require('safe-regex')
const _ = require('lodash')
const gql = require('graphql')
const { v4: uuid } = require('uuid')
/* global WIKI */
@ -30,13 +30,13 @@ module.exports = {
async assignUserToGroup (obj, args, { req }) {
// Check for guest user
if (args.userId === 2) {
throw new gql.GraphQLError('Cannot assign the Guest user to a group.')
throw new Error('Cannot assign the Guest user to a group.')
}
// Check for valid group
const grp = await WIKI.models.groups.query().findById(args.groupId)
if (!grp) {
throw new gql.GraphQLError('Invalid Group ID')
throw new Error('Invalid Group ID')
}
// Check assigned permissions for write:groups
@ -47,13 +47,13 @@ module.exports = {
return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType)
})
) {
throw new gql.GraphQLError('You are not authorized to assign a user to this elevated group.')
throw new Error('You are not authorized to assign a user to this elevated group.')
}
// Check for valid user
const usr = await WIKI.models.users.query().findById(args.userId)
if (!usr) {
throw new gql.GraphQLError('Invalid User ID')
throw new Error('Invalid User ID')
}
// Check for existing relation
@ -62,7 +62,7 @@ module.exports = {
groupId: args.groupId
}).first()
if (relExist) {
throw new gql.GraphQLError('User is already assigned to group.')
throw new Error('User is already assigned to group.')
}
// Assign user to group
@ -73,7 +73,7 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' })
return {
responseResult: graphHelper.generateSuccess('User has been assigned to group.')
operation: graphHelper.generateSuccess('User has been assigned to group.')
}
},
/**
@ -83,13 +83,16 @@ module.exports = {
const group = await WIKI.models.groups.query().insertAndFetch({
name: args.name,
permissions: JSON.stringify(WIKI.data.groups.defaultPermissions),
pageRules: JSON.stringify(WIKI.data.groups.defaultPageRules),
rules: JSON.stringify(WIKI.data.groups.defaultRules.map(r => ({
id: uuid(),
...r
}))),
isSystem: false
})
await WIKI.auth.reloadGroups()
WIKI.events.outbound.emit('reloadGroups')
return {
responseResult: graphHelper.generateSuccess('Group created successfully.'),
operation: graphHelper.generateSuccess('Group created successfully.'),
group
}
},
@ -98,7 +101,7 @@ module.exports = {
*/
async deleteGroup (obj, args) {
if (args.id === 1 || args.id === 2) {
throw new gql.GraphQLError('Cannot delete this group.')
throw new Error('Cannot delete this group.')
}
await WIKI.models.groups.query().deleteById(args.id)
@ -110,7 +113,7 @@ module.exports = {
WIKI.events.outbound.emit('reloadGroups')
return {
responseResult: graphHelper.generateSuccess('Group has been deleted.')
operation: graphHelper.generateSuccess('Group has been deleted.')
}
},
/**
@ -118,18 +121,18 @@ module.exports = {
*/
async unassignUserFromGroup (obj, args) {
if (args.userId === 2) {
throw new gql.GraphQLError('Cannot unassign Guest user')
throw new Error('Cannot unassign Guest user')
}
if (args.userId === 1 && args.groupId === 1) {
throw new gql.GraphQLError('Cannot unassign Administrator user from Administrators group.')
throw new Error('Cannot unassign Administrator user from Administrators group.')
}
const grp = await WIKI.models.groups.query().findById(args.groupId)
if (!grp) {
throw new gql.GraphQLError('Invalid Group ID')
throw new Error('Invalid Group ID')
}
const usr = await WIKI.models.users.query().findById(args.userId)
if (!usr) {
throw new gql.GraphQLError('Invalid User ID')
throw new Error('Invalid User ID')
}
await grp.$relatedQuery('users').unrelate().where('userId', usr.id)
@ -137,7 +140,7 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: usr.id, kind: 'u' })
return {
responseResult: graphHelper.generateSuccess('User has been unassigned from group.')
operation: graphHelper.generateSuccess('User has been unassigned from group.')
}
},
/**
@ -148,7 +151,7 @@ module.exports = {
if (_.some(args.pageRules, pr => {
return pr.match === 'REGEX' && !safeRegex(pr.path)
})) {
throw new gql.GraphQLError('Some Page Rules contains unsafe or exponential time regex.')
throw new Error('Some Page Rules contains unsafe or exponential time regex.')
}
// Set default redirect on login value
@ -164,7 +167,7 @@ module.exports = {
return ['users', 'groups', 'navigation', 'theme', 'api', 'system'].includes(resType)
})
) {
throw new gql.GraphQLError('You are not authorized to manage this group or assign these permissions.')
throw new Error('You are not authorized to manage this group or assign these permissions.')
}
// Check assigned permissions for manage:groups
@ -172,7 +175,7 @@ module.exports = {
WIKI.auth.checkExclusiveAccess(req.user, ['manage:groups'], ['manage:system']) &&
args.permissions.some(p => _.last(p.split(':')) === 'system')
) {
throw new gql.GraphQLError('You are not authorized to manage this group or assign the manage:system permissions.')
throw new Error('You are not authorized to manage this group or assign the manage:system permissions.')
}
// Update group
@ -192,7 +195,7 @@ module.exports = {
WIKI.events.outbound.emit('reloadGroups')
return {
responseResult: graphHelper.generateSuccess('Group has been updated.')
operation: graphHelper.generateSuccess('Group has been updated.')
}
}
},

@ -1,6 +1,7 @@
{
"compilerOptions": {
"baseUrl": ".",
"jsx": "preserve",
"paths": {
"src/*": [
"src/*"
@ -36,4 +37,4 @@
".quasar",
"node_modules"
]
}
}

@ -1,24 +1,21 @@
<template lang="pug">
q-dialog(ref='dialog', @hide='onDialogHide')
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-plus-plus.svg', left, size='sm')
span {{$t(`admin.groups.create`)}}
span {{t(`admin.groups.create`)}}
q-form.q-py-sm(ref='createGroupForm', @submit='create')
q-item
blueprint-icon(icon='team')
q-item-section
q-input(
outlined
v-model='groupName'
v-model='state.groupName'
dense
:rules=`[
val => val.length > 0 || $t('admin.groups.nameMissing'),
val => /^[^<>"]+$/.test(val) || $t('admin.groups.nameInvalidChars')
]`
:rules='groupNameValidation'
hide-bottom-space
:label='$t(`common.field.name`)'
:aria-label='$t(`common.field.name`)'
:label='t(`common.field.name`)'
:aria-label='t(`common.field.name`)'
lazy-rules='ondemand'
autofocus
)
@ -26,86 +23,103 @@ q-dialog(ref='dialog', @hide='onDialogHide')
q-space
q-btn.acrylic-btn(
flat
:label='$t(`common.actions.cancel`)'
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='hide'
@click='onDialogCancel'
)
q-btn(
unelevated
:label='$t(`common.actions.create`)'
:label='t(`common.actions.create`)'
color='primary'
padding='xs md'
@click='create'
:loading='isLoading'
:loading='state.isLoading'
)
</template>
<script>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { reactive, ref } from 'vue'
export default {
emits: ['ok', 'hide'],
data () {
return {
groupName: '',
isLoading: false
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
groupName: '',
isLoading: false
})
// REFS
const createGroupForm = ref(null)
// VALIDATION RULES
const groupNameValidation = [
val => val.length > 0 || t('admin.groups.nameMissing'),
val => /^[^<>"]+$/.test(val) || t('admin.groups.nameInvalidChars')
]
// METHODS
async function create () {
state.isLoading = true
try {
const isFormValid = await createGroupForm.value.validate(true)
if (!isFormValid) {
throw new Error(t('admin.groups.createInvalidData'))
}
},
methods: {
show () {
this.$refs.dialog.show()
},
hide () {
this.$refs.dialog.hide()
},
onDialogHide () {
this.$emit('hide')
},
async create () {
this.isLoading = true
try {
const isFormValid = await this.$refs.createGroupForm.validate(true)
if (!isFormValid) {
throw new Error(this.$t('admin.groups.createInvalidData'))
}
const resp = await this.$apollo.mutate({
mutation: gql`
mutation createGroup (
$name: String!
) {
createGroup(
name: $name
) {
status {
succeeded
message
}
}
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation createGroup (
$name: String!
) {
createGroup(
name: $name
) {
operation {
succeeded
message
}
`,
variables: {
name: this.groupName
}
})
if (resp?.data?.createGroup?.status?.succeeded) {
this.$q.notify({
type: 'positive',
message: this.$t('admin.groups.createSuccess')
})
this.$emit('ok')
this.hide()
} else {
throw new Error(resp?.data?.createGroup?.status?.message || 'An unexpected error occured.')
}
} catch (err) {
this.$q.notify({
type: 'negative',
message: err.message
})
`,
variables: {
name: state.groupName
}
this.isLoading = false
})
if (resp?.data?.createGroup?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.groups.createSuccess')
})
onDialogOK()
} else {
throw new Error(resp?.data?.createGroup?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
}
state.isLoading = false
}
</script>

@ -1,93 +1,96 @@
<template lang="pug">
q-dialog(ref='dialog', @hide='onDialogHide')
q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-delete-bin.svg', left, size='sm')
span {{$t(`admin.groups.delete`)}}
span {{t(`admin.groups.delete`)}}
q-card-section
.text-body2
i18n-t(keypath='admin.groups.deleteConfirm')
template(#groupName)
strong {{group.name}}
strong {{props.group.name}}
.text-body2.q-mt-md
strong.text-negative {{$t(`admin.groups.deleteConfirmWarn`)}}
strong.text-negative {{t(`admin.groups.deleteConfirmWarn`)}}
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
:label='$t(`common.actions.cancel`)'
:label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
@click='hide'
@click='onDialogCancel'
)
q-btn(
unelevated
:label='$t(`common.actions.delete`)'
:label='t(`common.actions.delete`)'
color='negative'
padding='xs md'
@click='confirm'
)
</template>
<script>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
export default {
props: {
group: {
type: Object,
required: true
}
},
emits: ['ok', 'hide'],
data () {
return {
}
},
methods: {
show () {
this.$refs.dialog.show()
},
hide () {
this.$refs.dialog.hide()
},
onDialogHide () {
this.$emit('hide')
},
async confirm () {
try {
const resp = await this.$apollo.mutate({
mutation: gql`
mutation deleteGroup ($id: UUID!) {
deleteGroup(id: $id) {
status {
succeeded
message
}
}
// PROPS
const props = defineProps({
group: {
type: Object,
required: true
}
})
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// I18N
const { t } = useI18n()
// METHODS
async function confirm () {
try {
const resp = await APOLLO_CLIENT.mutate({
mutation: gql`
mutation deleteGroup ($id: UUID!) {
deleteGroup(id: $id) {
operation {
succeeded
message
}
`,
variables: {
id: this.group.id
}
})
if (resp?.data?.deleteGroup?.status?.succeeded) {
this.$q.notify({
type: 'positive',
message: this.$t('admin.groups.deleteSuccess')
})
this.$emit('ok')
this.hide()
} else {
throw new Error(resp?.data?.deleteGroup?.status?.message || 'An unexpected error occured.')
}
} catch (err) {
this.$q.notify({
type: 'negative',
message: err.message
})
`,
variables: {
id: props.group.id
}
})
if (resp?.data?.deleteGroup?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.groups.deleteSuccess')
})
onDialogOK()
} else {
throw new Error(resp?.data?.deleteGroup?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
}
}
</script>

File diff suppressed because it is too large Load Diff

@ -12,10 +12,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
outlined
v-model='state.siteName'
dense
:rules=`[
val => val.length > 0 || t('admin.sites.nameMissing'),
val => /^[^<>"]+$/.test(val) || t('admin.sites.nameInvalidChars')
]`
:rules='siteNameValidation'
hide-bottom-space
:label='t(`common.field.name`)'
:aria-label='t(`common.field.name`)'
@ -29,10 +26,7 @@ q-dialog(ref='dialogRef', @hide='onDialogHide')
outlined
v-model='state.siteHostname'
dense
:rules=`[
val => val.length > 0 || t('admin.sites.hostnameMissing'),
val => /^(\\*)|([a-z0-9\-.:]+)$/.test(val) || t('admin.sites.hostnameInvalidChars')
]`
:rules='siteHostnameValidation'
:hint='t(`admin.sites.hostnameHint`)'
hide-bottom-space
:label='t(`admin.sites.hostname`)'
@ -97,6 +91,17 @@ const state = reactive({
const createSiteForm = ref(null)
// VALIDATION RULES
const siteNameValidation = [
val => val.length > 0 || t('admin.sites.nameMissing'),
val => /^[^<>"]+$/.test(val) || t('admin.sites.nameInvalidChars')
]
const siteHostnameValidation = [
val => val.length > 0 || t('admin.sites.hostnameMissing'),
val => /^(\\*)|([a-z0-9\-.:]+)$/.test(val) || t('admin.sites.hostnameInvalidChars')
]
// METHODS
async function create () {

@ -81,7 +81,7 @@ async function confirm () {
mutation: gql`
mutation deleteSite ($id: UUID!) {
deleteSite(id: $id) {
status {
operation {
succeeded
message
}
@ -92,7 +92,7 @@ async function confirm () {
id: props.site.id
}
})
if (resp?.data?.deleteSite?.status?.succeeded) {
if (resp?.data?.deleteSite?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.sites.deleteSuccess')
@ -102,7 +102,7 @@ async function confirm () {
})
onDialogOK()
} else {
throw new Error(resp?.data?.deleteSite?.status?.message || 'An unexpected error occured.')
throw new Error(resp?.data?.deleteSite?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({

@ -1421,5 +1421,6 @@
"tags.searchWithinResultsPlaceholder": "Search within results...",
"tags.selectOneMoreTags": "Select one or more tags",
"tags.selectOneMoreTagsHint": "Select one or more tags on the left.",
"admin.general.sitemapHint": "Make a sitemap.xml available to search engines with all pages accessible to guests."
"admin.general.sitemapHint": "Make a sitemap.xml available to search engines with all pages accessible to guests.",
"admin.groups.usersNone": "This group doesn't have any user yet."
}

@ -177,7 +177,7 @@ q-layout.admin(view='hHh Lpr lff')
transition-show='jump-up'
transition-hide='jump-down'
)
component(:is='adminStore.overlay')
component(:is='overlays[adminStore.overlay]')
q-footer.admin-footer
q-bar.justify-center(dense)
span(style='font-size: 11px;') Powered by #[a(href='https://js.wiki', target='_blank'): strong Wiki.js], an open source project.
@ -195,8 +195,10 @@ import { useSiteStore } from '../stores/site'
// COMPONENTS
import AccountMenu from '../components/AccountMenu.vue'
const GroupEditOverlay = defineAsyncComponent(() => import('../components/GroupEditOverlay.vue'))
const UserEditOverlay = defineAsyncComponent(() => import('../components/UserEditOverlay.vue'))
const overlays = {
GroupEditOverlay: defineAsyncComponent(() => import('../components/GroupEditOverlay.vue')),
UserEditOverlay: defineAsyncComponent(() => import('../components/UserEditOverlay.vue'))
}
// STORES

@ -175,7 +175,7 @@ watch(() => adminStore.overlay, (newValue, oldValue) => {
}
})
watch(() => route, () => {
watch(() => route.params.id, () => {
checkOverlay()
})
@ -213,7 +213,7 @@ async function load () {
}
function checkOverlay () {
if (route.params && route.params.id) {
if (route.params?.id) {
adminStore.$patch({
overlayOpts: { id: route.params.id },
overlay: 'GroupEditOverlay'

@ -737,7 +737,7 @@ watch(() => state.targets, (newValue) => {
handleSetupCallback()
}
})
watch(() => route, (to, from) => {
watch(() => route.params.id, (to, from) => {
if (!to.params.id) {
return
}

Loading…
Cancel
Save