From 8e6b6f454ae047775f98b50d2d263990839c4c83 Mon Sep 17 00:00:00 2001 From: Eliot Berriot <contact@eliotberriot.com> Date: Tue, 19 Jun 2018 18:50:22 +0200 Subject: [PATCH] See #212: front-end list --- front/src/components/Sidebar.vue | 6 + front/src/components/common/ActionTable.vue | 7 +- .../components/manage/users/UsersTable.vue | 216 ++++++++++++++++++ front/src/router/index.js | 13 ++ front/src/views/admin/users/Base.vue | 28 +++ front/src/views/admin/users/UsersList.vue | 23 ++ 6 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 front/src/components/manage/users/UsersTable.vue create mode 100644 front/src/views/admin/users/Base.vue create mode 100644 front/src/views/admin/users/UsersList.vue diff --git a/front/src/components/Sidebar.vue b/front/src/components/Sidebar.vue index d46fb846..03ea4ee0 100644 --- a/front/src/components/Sidebar.vue +++ b/front/src/components/Sidebar.vue @@ -96,6 +96,12 @@ :to="{path: '/manage/settings'}"> <i class="settings icon"></i>{{ $t('Settings') }} </router-link> + <router-link + class="item" + v-if="$store.state.auth.availablePermissions['settings']" + :to="{path: '/manage/users'}"> + <i class="users icon"></i>{{ $t('Users') }} + </router-link> </div> </div> </div> diff --git a/front/src/components/common/ActionTable.vue b/front/src/components/common/ActionTable.vue index 5221c328..f2347906 100644 --- a/front/src/components/common/ActionTable.vue +++ b/front/src/components/common/ActionTable.vue @@ -61,7 +61,7 @@ </th> </tr> <tr> - <th> + <th v-if="actions.length > 0"> <div class="ui checkbox"> <input type="checkbox" @@ -75,7 +75,7 @@ </thead> <tbody v-if="objectsData.count > 0"> <tr v-for="(obj, index) in objectsData.results"> - <td class="collapsing"> + <td v-if="actions.length > 0" class="collapsing"> <input type="checkbox" :disabled="checkable.indexOf(obj.id) === -1" @@ -184,6 +184,9 @@ export default { })[0] }, checkable () { + if (!this.currentAction) { + return [] + } let objs = this.objectsData.results let filter = this.currentAction.filterCheckable if (filter) { diff --git a/front/src/components/manage/users/UsersTable.vue b/front/src/components/manage/users/UsersTable.vue new file mode 100644 index 00000000..746b158a --- /dev/null +++ b/front/src/components/manage/users/UsersTable.vue @@ -0,0 +1,216 @@ +<template> + <div> + <div class="ui inline form"> + <div class="fields"> + <div class="ui field"> + <label>{{ $t('Search') }}</label> + <input type="text" v-model="search" placeholder="Search by username, email, name..." /> + </div> + <div class="field"> + <i18next tag="label" path="Ordering"/> + <select class="ui dropdown" v-model="ordering"> + <option v-for="option in orderingOptions" :value="option[0]"> + {{ option[1] }} + </option> + </select> + </div> + <div class="field"> + <i18next tag="label" path="Ordering direction"/> + <select class="ui dropdown" v-model="orderingDirection"> + <option value="+">{{ $t('Ascending') }}</option> + <option value="-">{{ $t('Descending') }}</option> + </select> + </div> + </div> + </div> + <div class="dimmable"> + <div v-if="isLoading" class="ui active inverted dimmer"> + <div class="ui loader"></div> + </div> + <action-table + v-if="result" + @action-launched="fetchData" + :objects-data="result" + :actions="actions" + :action-url="'manage/library/track-files/action/'" + :filters="actionFilters"> + <template slot="header-cells"> + <th>{{ $t('Username') }}</th> + <th>{{ $t('Email') }}</th> + <th>{{ $t('Account status') }}</th> + <th>{{ $t('Sign-up') }}</th> + <th>{{ $t('Last activity') }}</th> + <th>{{ $t('Permissions') }}</th> + <th>{{ $t('Status') }}</th> + </template> + <template slot="row-cells" slot-scope="scope"> + <td> + <span>{{Â scope.obj.username }}</span> + </td> + <td> + <span>{{Â scope.obj.email }}</span> + </td> + <td> + <span v-if="scope.obj.is_active" class="ui basic green label">{{Â $t('Active') }}</span> + <span v-else class="ui basic grey label">{{Â $t('Inactive') }}</span> + </td> + <td> + <human-date :date="scope.obj.date_joined"></human-date> + </td> + <td> + <human-date v-if="scope.obj.last_activity" :date="scope.obj.last_activity"></human-date> + <template v-else>{{ $t('N/A') }}</template> + </td> + <td> + <template v-for="p in permissions"> + <span class="ui basic tiny label" v-if="scope.obj.permissions[p.code]">{{ p.label }}</span> + </template> + </td> + <td> + <span v-if="scope.obj.is_superuser" class="ui pink label">{{ $t('Admin') }}</span> + <span v-else-if="scope.obj.is_staff" class="ui purple label">{{ $t('Staff member') }}</span> + <span v-else class="ui basic label">{{ $t('regular user') }}</span> + </td> + </template> + </action-table> + </div> + <div> + <pagination + v-if="result && result.results.length > 0" + @page-changed="selectPage" + :compact="true" + :current="page" + :paginate-by="paginateBy" + :total="result.count" + ></pagination> + + <span v-if="result && result.results.length > 0"> + {{ $t('Showing results {%start%}-{%end%} on {%total%}', {start: ((page-1) * paginateBy) + 1 , end: ((page-1) * paginateBy) + result.results.length, total: result.count})}} + </span> + </div> + </div> +</template> + +<script> +import axios from 'axios' +import _ from 'lodash' +import time from '@/utils/time' +import Pagination from '@/components/Pagination' +import ActionTable from '@/components/common/ActionTable' +import OrderingMixin from '@/components/mixins/Ordering' + +export default { + mixins: [OrderingMixin], + props: { + filters: {type: Object, required: false} + }, + components: { + Pagination, + ActionTable + }, + data () { + let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-date_joined') + return { + time, + isLoading: false, + result: null, + page: 1, + paginateBy: 50, + search: '', + orderingDirection: defaultOrdering.direction || '+', + ordering: defaultOrdering.field, + orderingOptions: [ + ['date_joined', 'Sign-up date'], + ['last_activity', 'Last activity'], + ['username', 'Username'] + ] + + } + }, + created () { + this.fetchData() + }, + methods: { + fetchData () { + let params = _.merge({ + 'page': this.page, + 'page_size': this.paginateBy, + 'q': this.search, + 'ordering': this.getOrderingAsString() + }, this.filters) + let self = this + self.isLoading = true + self.checked = [] + axios.get('/manage/users/users/', {params: params}).then((response) => { + self.result = response.data + self.isLoading = false + }, error => { + self.isLoading = false + self.errors = error.backendErrors + }) + }, + selectPage: function (page) { + this.page = page + } + }, + computed: { + privacyLevels () { + return {} + }, + permissions () { + return [ + { + 'code': 'upload', + 'label': this.$t('Upload') + }, + { + 'code': 'library', + 'label': this.$t('Library') + }, + { + 'code': 'federation', + 'label': this.$t('Federation') + }, + { + 'code': 'settings', + 'label': this.$t('Settings') + } + ] + }, + actionFilters () { + var currentFilters = { + q: this.search + } + if (this.filters) { + return _.merge(currentFilters, this.filters) + } else { + return currentFilters + } + }, + actions () { + return [ + // { + // name: 'delete', + // label: this.$t('Delete'), + // isDangerous: true + // } + ] + } + }, + watch: { + search (newValue) { + this.page = 1 + this.fetchData() + }, + page () { + this.fetchData() + }, + ordering () { + this.fetchData() + }, + orderingDirection () { + this.fetchData() + } + } +} +</script> diff --git a/front/src/router/index.js b/front/src/router/index.js index a52070e3..459077d3 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -31,6 +31,8 @@ import Favorites from '@/components/favorites/List' import AdminSettings from '@/views/admin/Settings' import AdminLibraryBase from '@/views/admin/library/Base' import AdminLibraryFilesList from '@/views/admin/library/FilesList' +import AdminUsersBase from '@/views/admin/users/Base' +import AdminUsersList from '@/views/admin/users/UsersList' import FederationBase from '@/views/federation/Base' import FederationScan from '@/views/federation/Scan' import FederationLibraryDetail from '@/views/federation/LibraryDetail' @@ -180,6 +182,17 @@ export default new Router({ } ] }, + { + path: '/manage/users', + component: AdminUsersBase, + children: [ + { + path: '', + name: 'manage.users.list', + component: AdminUsersList + } + ] + }, { path: '/library', component: Library, diff --git a/front/src/views/admin/users/Base.vue b/front/src/views/admin/users/Base.vue new file mode 100644 index 00000000..e545b7f7 --- /dev/null +++ b/front/src/views/admin/users/Base.vue @@ -0,0 +1,28 @@ +<template> + <div class="main pusher" v-title="$t('Manage users')"> + <div class="ui secondary pointing menu"> + <router-link + class="ui item" + :to="{name: 'manage.users.list'}">{{ $t('Users') }}</router-link> + </div> + <router-view :key="$route.fullPath"></router-view> + </div> +</template> + +<script> +export default {} +</script> + +<style lang="scss"> +@import '../../../style/vendor/media'; + +.main.pusher > .ui.secondary.menu { + @include media(">tablet") { + margin: 0 2.5rem; + } + .item { + padding-top: 1.5em; + padding-bottom: 1.5em; + } +} +</style> diff --git a/front/src/views/admin/users/UsersList.vue b/front/src/views/admin/users/UsersList.vue new file mode 100644 index 00000000..b22d4aaf --- /dev/null +++ b/front/src/views/admin/users/UsersList.vue @@ -0,0 +1,23 @@ +<template> + <div v-title="$t('Users')"> + <div class="ui vertical stripe segment"> + <h2 class="ui header">{{ $t('Users') }}</h2> + <div class="ui hidden divider"></div> + <users-table></users-table> + </div> + </div> +</template> + +<script> +import UsersTable from '@/components/manage/users/UsersTable' + +export default { + components: { + UsersTable + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> -- GitLab