kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
See #212: front-end list
rodzic
3ef5f29dd3
commit
8e6b6f454a
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Ładowanie…
Reference in New Issue