diff --git a/client/app.js b/client/app.js index 9091a7ee..aaf2494f 100644 --- a/client/app.js +++ b/client/app.js @@ -12,6 +12,8 @@ import VueApollo from 'vue-apollo' import Vuetify from 'vuetify' import Velocity from 'velocity-animate' import Hammer from 'hammerjs' +import moment from 'moment' +import VueMoment from 'vue-moment' import store from './store' // ==================================== @@ -64,6 +66,7 @@ Vue.use(localization.VueI18Next) Vue.use(helpers) Vue.use(VeeValidate, { events: '' }) Vue.use(Vuetify) +Vue.use(VueMoment, { moment }) Vue.prototype.Velocity = Velocity diff --git a/client/components/admin-groups-edit.vue b/client/components/admin-groups-edit.vue new file mode 100644 index 00000000..9f2f989f --- /dev/null +++ b/client/components/admin-groups-edit.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/client/components/admin-groups.vue b/client/components/admin-groups.vue index 9d6d7d98..271a4fce 100644 --- a/client/components/admin-groups.vue +++ b/client/components/admin-groups.vue @@ -12,7 +12,7 @@ v-card .dialog-header.is-short New Group v-card-text - v-text-field(v-model='newGroupName', label='Group Name', autofocus, counter='255') + v-text-field(v-model='newGroupName', label='Group Name', autofocus, counter='255', @keyup.enter='createGroup') v-card-actions v-spacer v-btn(flat, @click='newGroupDialog = false') Cancel @@ -20,23 +20,22 @@ v-btn(icon, @click='refresh') v-icon.grey--text refresh v-spacer - v-text-field(append-icon='search', label='Search', single-line, hide-details, v-model='search') + v-text-field(solo, append-icon='search', label='Search', single-line, hide-details, v-model='search') v-data-table( - v-model='selected' :items='groups', :headers='headers', :search='search', :pagination.sync='pagination', :rows-per-page-items='[15]' - hide-actions, - disable-initial-sort + hide-actions ) template(slot='items', slot-scope='props') - tr(:active='props.selected') + tr.is-clickable(:active='props.selected', @click='$router.push("/groups/" + props.item.id)') td.text-xs-right {{ props.item.id }} td {{ props.item.name }} td {{ props.item.userCount }} - td: v-btn(icon): v-icon.grey--text.text--darken-1 more_horiz + td {{ props.item.createdAt | moment('calendar') }} + td {{ props.item.updatedAt | moment('calendar') }} template(slot='no-data') v-alert.ma-3(icon='warning', :value='true', outline) No groups to display. .text-xs-center.py-2(v-if='groups.length > 15') @@ -48,20 +47,22 @@ import _ from 'lodash' import groupsQuery from 'gql/admin-groups-query-list.gql' import createGroupMutation from 'gql/admin-groups-mutation-create.gql' +import deleteGroupMutation from 'gql/admin-groups-mutation-delete.gql' export default { data() { return { newGroupDialog: false, newGroupName: '', - selected: [], + selectedGroup: {}, pagination: {}, groups: [], headers: [ { text: 'ID', value: 'id', width: 50, align: 'right' }, { text: 'Name', value: 'name' }, { text: 'Users', value: 'userCount', width: 200 }, - { text: '', value: 'actions', sortable: false, width: 50 } + { text: 'Created', value: 'createdAt', width: 250 }, + { text: 'Last Updated', value: 'updatedAt', width: 250 } ], search: '' } @@ -96,6 +97,7 @@ export default { const data = _.get(resp, 'data.groups.create', { responseResult: {} }) if (data.responseResult.succeeded === true) { const apolloData = store.readQuery({ query: groupsQuery }) + data.group.userCount = 0 apolloData.groups.list.push(data.group) store.writeQuery({ query: groupsQuery, data: apolloData }) } else { @@ -106,13 +108,48 @@ export default { this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-groups-create') } }) + this.newGroupName = '' this.$store.commit('showNotification', { style: 'success', message: `Group has been created successfully.`, icon: 'check' }) } catch (err) { - + this.$store.commit('showNotification', { + style: 'red', + message: err.message, + icon: 'warning' + }) + } + }, + async deleteGroupConfirm(group) { + this.deleteGroupDialog = true + this.selectedGroup = group + }, + async deleteGroup() { + this.deleteGroupDialog = false + try { + await this.$apollo.mutate({ + mutation: deleteGroupMutation, + variables: { + id: this.selectedGroup.id + }, + watchLoading (isLoading) { + this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'admin-groups-delete') + } + }) + await this.$apollo.queries.groups.refetch() + this.$store.commit('showNotification', { + style: 'success', + message: `Group ${this.selectedGroup.name} has been deleted.`, + icon: 'delete' + }) + } catch (err) { + this.$store.commit('showNotification', { + style: 'red', + message: err.message, + icon: 'warning' + }) } } }, diff --git a/client/components/admin.vue b/client/components/admin.vue index 627967cb..327e25ca 100644 --- a/client/components/admin.vue +++ b/client/components/admin.vue @@ -97,6 +97,7 @@ const router = new VueRouter({ { path: '/stats', component: () => import(/* webpackChunkName: "admin" */ './admin-stats.vue') }, { path: '/theme', component: () => import(/* webpackChunkName: "admin" */ './admin-theme.vue') }, { path: '/groups', component: () => import(/* webpackChunkName: "admin" */ './admin-groups.vue') }, + { path: '/groups/:id', component: () => import(/* webpackChunkName: "admin" */ './admin-groups-edit.vue') }, { path: '/users', component: () => import(/* webpackChunkName: "admin" */ './admin-users.vue') }, { path: '/auth', component: () => import(/* webpackChunkName: "admin" */ './admin-auth.vue') }, { path: '/rendering', component: () => import(/* webpackChunkName: "admin" */ './admin-rendering.vue') }, diff --git a/client/graph/admin-groups-mutation-delete.gql b/client/graph/admin-groups-mutation-delete.gql new file mode 100644 index 00000000..9f87f3b2 --- /dev/null +++ b/client/graph/admin-groups-mutation-delete.gql @@ -0,0 +1,12 @@ +mutation ($id: Int!) { + groups { + delete(id: $id) { + responseResult { + succeeded + errorCode + slug + message + } + } + } +} diff --git a/client/graph/admin-groups-query-single.gql b/client/graph/admin-groups-query-single.gql new file mode 100644 index 00000000..8cfde451 --- /dev/null +++ b/client/graph/admin-groups-query-single.gql @@ -0,0 +1,22 @@ +query ($id: Int!) { + groups { + single(id: $id) { + id + name + rights { + id + path + role + exact + allow + } + users { + id + name + email + } + createdAt + updatedAt + } + } +} diff --git a/client/scss/app.scss b/client/scss/app.scss index 5cc7994d..622eaf57 100644 --- a/client/scss/app.scss +++ b/client/scss/app.scss @@ -7,6 +7,7 @@ // @import "../libs/animate/animate"; @import 'components/markdown-content'; +@import 'components/data-table'; @import 'components/dialog'; // @import '../libs/twemoji/twemoji-awesome'; diff --git a/client/scss/components/_data-table.scss b/client/scss/components/_data-table.scss new file mode 100644 index 00000000..cb874434 --- /dev/null +++ b/client/scss/components/_data-table.scss @@ -0,0 +1,5 @@ +.datatable { + .is-clickable { + cursor: pointer; + } +} diff --git a/client/scss/components/_dialog.scss b/client/scss/components/_dialog.scss index 031fc5cf..f0f3c4a7 100644 --- a/client/scss/components/_dialog.scss +++ b/client/scss/components/_dialog.scss @@ -8,4 +8,10 @@ align-items: center; padding: 0 1rem; font-size: 1.2rem; + + &.is-red { + background-color: mc('red', '700'); + background: radial-gradient(ellipse at top, mc('red', '500'), transparent), + radial-gradient(ellipse at bottom, mc('red', '800'), transparent); + } } diff --git a/package.json b/package.json index be4c5b4d..fbfe4552 100644 --- a/package.json +++ b/package.json @@ -218,6 +218,7 @@ "vue-hot-reload-api": "2.3.0", "vue-loader": "14.2.1", "vue-material-design-icons": "1.2.1", + "vue-moment": "3.2.0", "vue-router": "3.0.1", "vue-simple-breakpoints": "1.0.3", "vue-template-compiler": "2.5.16", diff --git a/server/graph/resolvers/group.js b/server/graph/resolvers/group.js index 75c0aa3f..4fdfc1f8 100644 --- a/server/graph/resolvers/group.js +++ b/server/graph/resolvers/group.js @@ -48,19 +48,21 @@ module.exports = { const group = await WIKI.db.Group.create({ name: args.name }) - console.info(group) return { responseResult: graphHelper.generateSuccess('Group created successfully.'), group } }, - delete(obj, args) { - return WIKI.db.Group.destroy({ + async delete(obj, args) { + await WIKI.db.Group.destroy({ where: { id: args.id }, limit: 1 }) + return { + responseResult: graphHelper.generateSuccess('Group has been deleted.') + } }, unassignUser(obj, args) { return WIKI.db.Group.findById(args.groupId).then(grp => { diff --git a/server/graph/schemas/group.graphql b/server/graph/schemas/group.graphql index fef79841..04e4c883 100644 --- a/server/graph/schemas/group.graphql +++ b/server/graph/schemas/group.graphql @@ -18,7 +18,11 @@ type GroupQuery { list( filter: String orderBy: String - ): [Group] + ): [GroupMinimal] + + single( + id: String! + ): Group } # ----------------------------------------------- @@ -59,12 +63,19 @@ type GroupResponse { group: Group } +type GroupMinimal { + id: Int! + name: String! + userCount: Int + createdAt: Date! + updatedAt: Date! +} + type Group { id: Int! name: String! rights: [String] users: [User] - userCount: Int createdAt: Date! updatedAt: Date! } diff --git a/yarn.lock b/yarn.lock index c8636c1f..44123af3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7992,7 +7992,7 @@ moment-timezone@^0.5.0, moment-timezone@^0.5.x: dependencies: moment ">= 2.9.0" -moment@2.21.0: +moment@2.21.0, moment@^2.11.1: version "2.21.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" @@ -12767,6 +12767,12 @@ vue-material-design-icons@1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/vue-material-design-icons/-/vue-material-design-icons-1.2.1.tgz#3231ffc3c4aadbaf9de06e9c29b6994691f010aa" +vue-moment@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vue-moment/-/vue-moment-3.2.0.tgz#28cd2b313831ae83953f646ac4785a2cc724e412" + dependencies: + moment "^2.11.1" + vue-router@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9"