Merge pull request #17239 from nextcloud/feature/17208/Move_usersmanagement_to_multiline
Move users management to multi line
This commit is contained in:
commit
42c8f9f679
24 changed files with 740 additions and 401 deletions
|
@ -524,7 +524,6 @@ td, th {
|
|||
visibility: hidden;
|
||||
}
|
||||
&.password,
|
||||
&.displayName,
|
||||
&.mailAddress {
|
||||
min-width: 5em;
|
||||
max-width: 12em;
|
||||
|
@ -705,6 +704,7 @@ span.version {
|
|||
#searchresults {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
#apps-list.store {
|
||||
.section {
|
||||
|
@ -1351,8 +1351,8 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
|
||||
/* USERS LIST -------------------------------------------------------------- */
|
||||
#body-settings {
|
||||
$grid-row-height: 46px;
|
||||
$grid-col-min-width: 120px;
|
||||
$grid-row-height: 60px;
|
||||
$grid-col-min-width: 150px;
|
||||
#app-content.user-list-grid {
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
|
@ -1376,7 +1376,6 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
|
||||
/* grid col width */
|
||||
.name,
|
||||
.displayName,
|
||||
.password,
|
||||
.mailAddress,
|
||||
.languages,
|
||||
|
@ -1384,12 +1383,17 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
.userBackend,
|
||||
.lastLogin {
|
||||
min-width: $grid-col-min-width;
|
||||
display: flex;
|
||||
color: var(--color-text-dark);
|
||||
vertical-align: baseline;
|
||||
}
|
||||
.groups,
|
||||
.subadmins,
|
||||
.quota {
|
||||
.multiselect {
|
||||
min-width: $grid-col-min-width;
|
||||
color: var(--color-text-dark);
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
.obfuscated {
|
||||
|
@ -1399,6 +1403,10 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
.userActions {
|
||||
min-width: 44px;
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--color-text-maxcontrast);
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* various */
|
||||
&#grid-header,
|
||||
|
@ -1427,16 +1435,23 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
&#grid-header {
|
||||
color: var(--color-text-maxcontrast);
|
||||
z-index: 60; /* above new-user */
|
||||
border-bottom-width: thin;
|
||||
|
||||
#headerDisplayName,
|
||||
#headerPassword,
|
||||
#headerAddress,
|
||||
#headerGroups,
|
||||
#headerSubAdmins,
|
||||
#theHeaderUserBackend,
|
||||
#theHeaderLastLogin,
|
||||
#headerQuota,
|
||||
#theHeaderStorageLocation,
|
||||
#headerLanguages {
|
||||
/* Line up header text with column content for when there’s inputs */
|
||||
padding-left: 7px;
|
||||
text-transform: none;
|
||||
color: var(--color-text-maxcontrast);
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
|
@ -1451,8 +1466,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
> form {
|
||||
grid-row: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--color-text);
|
||||
color: var(--color-text-lighter);
|
||||
position: relative;
|
||||
> input:not(:focus):not(:active) {
|
||||
border-color: transparent;
|
||||
|
@ -1478,7 +1492,7 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
}
|
||||
}
|
||||
&.name,
|
||||
&.storageLocation {
|
||||
&.userBackend {
|
||||
/* better multi-line visual */
|
||||
line-height: 1.3em;
|
||||
max-height: 100%;
|
||||
|
@ -1492,16 +1506,14 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
-webkit-box-orient: vertical;
|
||||
}
|
||||
&.quota {
|
||||
.multiselect--active + progress {
|
||||
display: none;
|
||||
}
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
progress {
|
||||
position: absolute;
|
||||
width: calc(100% - 4px); /* minus left and right */
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
width: 100%;
|
||||
margin: 0 10px;
|
||||
height: 3px;
|
||||
z-index: 5; /* above multiselect */
|
||||
}
|
||||
}
|
||||
.icon-confirm {
|
||||
|
@ -1520,16 +1532,22 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
}
|
||||
}
|
||||
&.userActions {
|
||||
.action-item {
|
||||
position: absolute;
|
||||
}
|
||||
#newsubmit {
|
||||
width: 100%;
|
||||
}
|
||||
.toggleUserActions {
|
||||
position: relative;
|
||||
display: block;
|
||||
align-items: center;
|
||||
.icon-more {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
opacity: .5;
|
||||
cursor: pointer;
|
||||
margin-left: 40px;
|
||||
&:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
|
|
BIN
apps/settings/js/vue-1.js
Normal file
BIN
apps/settings/js/vue-1.js
Normal file
Binary file not shown.
BIN
apps/settings/js/vue-1.js.map
Normal file
BIN
apps/settings/js/vue-1.js.map
Normal file
Binary file not shown.
BIN
apps/settings/js/vue-2.js
Normal file
BIN
apps/settings/js/vue-2.js
Normal file
Binary file not shown.
BIN
apps/settings/js/vue-2.js.map
Normal file
BIN
apps/settings/js/vue-2.js.map
Normal file
Binary file not shown.
BIN
apps/settings/js/vue-3.js
Normal file
BIN
apps/settings/js/vue-3.js
Normal file
Binary file not shown.
BIN
apps/settings/js/vue-3.js.map
Normal file
BIN
apps/settings/js/vue-3.js.map
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -29,7 +29,9 @@
|
|||
<button v-if="showUpdateAll"
|
||||
id="app-list-update-all"
|
||||
class="primary"
|
||||
@click="updateAll">{{t('settings', 'Update all')}}</button>
|
||||
@click="updateAll">
|
||||
{{ t('settings', 'Update all') }}
|
||||
</button>
|
||||
</div>
|
||||
<transition-group name="app-list" tag="div" class="apps-list-container">
|
||||
<AppItem v-for="app in apps"
|
||||
|
|
|
@ -22,13 +22,16 @@
|
|||
|
||||
<template>
|
||||
<div id="app-content" class="user-list-grid" @scroll.passive="onScroll">
|
||||
<div id="grid-header" class="row" :class="{'sticky': scrolled && !showConfig.showNewUserForm}">
|
||||
<div id="grid-header"
|
||||
:class="{'sticky': scrolled && !showConfig.showNewUserForm}"
|
||||
class="row">
|
||||
<div id="headerAvatar" class="avatar" />
|
||||
<div id="headerName" class="name">
|
||||
{{ t('settings', 'Username') }}
|
||||
</div>
|
||||
<div id="headerDisplayName" class="displayName">
|
||||
{{ t('settings', 'Display name') }}
|
||||
|
||||
<div class="subtitle">
|
||||
{{ t('settings', 'Display name') }}
|
||||
</div>
|
||||
</div>
|
||||
<div id="headerPassword" class="password">
|
||||
{{ t('settings', 'Password') }}
|
||||
|
@ -52,99 +55,103 @@
|
|||
class="languages">
|
||||
{{ t('settings', 'Language') }}
|
||||
</div>
|
||||
<div v-if="showConfig.showStoragePath"
|
||||
class="headerStorageLocation storageLocation">
|
||||
{{ t('settings', 'Storage location') }}
|
||||
</div>
|
||||
<div v-if="showConfig.showUserBackend"
|
||||
|
||||
<div v-if="showConfig.showUserBackend || showConfig.showStoragePath"
|
||||
class="headerUserBackend userBackend">
|
||||
{{ t('settings', 'User backend') }}
|
||||
<div v-if="showConfig.showUserBackend" class="userBackend">
|
||||
{{ t('settings', 'User backend') }}
|
||||
</div>
|
||||
<div v-if="showConfig.showStoragePath"
|
||||
class="subtitle storageLocation">
|
||||
{{ t('settings', 'Storage location') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showConfig.showLastLogin"
|
||||
class="headerLastLogin lastLogin">
|
||||
{{ t('settings', 'Last login') }}
|
||||
</div>
|
||||
|
||||
<div class="userActions" />
|
||||
</div>
|
||||
|
||||
<form v-show="showConfig.showNewUserForm"
|
||||
id="new-user"
|
||||
class="row"
|
||||
:disabled="loading.all"
|
||||
:class="{'sticky': scrolled && showConfig.showNewUserForm}"
|
||||
:disabled="loading.all"
|
||||
class="row"
|
||||
@submit.prevent="createUser">
|
||||
<div :class="loading.all?'icon-loading-small':'icon-add'" />
|
||||
<div class="name">
|
||||
<input id="newusername"
|
||||
ref="newusername"
|
||||
v-model="newUser.id"
|
||||
type="text"
|
||||
required
|
||||
:disabled="settings.newUserGenerateUserID"
|
||||
:placeholder="settings.newUserGenerateUserID
|
||||
? t('settings', 'Will be autogenerated')
|
||||
: t('settings', 'Username')"
|
||||
name="username"
|
||||
autocomplete="off"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
name="username"
|
||||
pattern="[a-zA-Z0-9 _\.@\-']+"
|
||||
:disabled="settings.newUserGenerateUserID">
|
||||
required
|
||||
type="text">
|
||||
</div>
|
||||
<div class="displayName">
|
||||
<input id="newdisplayname"
|
||||
v-model="newUser.displayName"
|
||||
type="text"
|
||||
:placeholder="t('settings', 'Display name')"
|
||||
name="displayname"
|
||||
autocomplete="off"
|
||||
autocapitalize="none"
|
||||
autocorrect="off">
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
name="displayname"
|
||||
type="text">
|
||||
</div>
|
||||
<div class="password">
|
||||
<input id="newuserpassword"
|
||||
ref="newuserpassword"
|
||||
v-model="newUser.password"
|
||||
type="password"
|
||||
:required="newUser.mailAddress===''"
|
||||
:minlength="minPasswordLength"
|
||||
:placeholder="t('settings', 'Password')"
|
||||
name="password"
|
||||
autocomplete="new-password"
|
||||
:required="newUser.mailAddress===''"
|
||||
autocapitalize="none"
|
||||
autocomplete="new-password"
|
||||
autocorrect="off"
|
||||
:minlength="minPasswordLength">
|
||||
name="password"
|
||||
type="password">
|
||||
</div>
|
||||
<div class="mailAddress">
|
||||
<input id="newemail"
|
||||
v-model="newUser.mailAddress"
|
||||
type="email"
|
||||
:required="newUser.password==='' || settings.newUserRequireEmail"
|
||||
:placeholder="t('settings', 'Email')"
|
||||
name="email"
|
||||
autocomplete="off"
|
||||
:required="newUser.password==='' || settings.newUserRequireEmail"
|
||||
autocapitalize="none"
|
||||
autocorrect="off">
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
name="email"
|
||||
type="email">
|
||||
</div>
|
||||
<div class="groups">
|
||||
<!-- hidden input trick for vanilla html5 form validation -->
|
||||
<input v-if="!settings.isAdmin"
|
||||
id="newgroups"
|
||||
type="text"
|
||||
:class="{'icon-loading-small': loading.groups}"
|
||||
:required="!settings.isAdmin"
|
||||
:value="newUser.groups"
|
||||
tabindex="-1"
|
||||
:required="!settings.isAdmin"
|
||||
:class="{'icon-loading-small': loading.groups}">
|
||||
type="text">
|
||||
<Multiselect v-model="newUser.groups"
|
||||
:options="canAddGroups"
|
||||
:disabled="loading.groups||loading.all"
|
||||
tag-placeholder="create"
|
||||
:placeholder="t('settings', 'Add user in group')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
class="multiselect-vue"
|
||||
:multiple="true"
|
||||
:taggable="true"
|
||||
:close-on-select="false"
|
||||
:disabled="loading.groups||loading.all"
|
||||
:multiple="true"
|
||||
:options="canAddGroups"
|
||||
:placeholder="t('settings', 'Add user in group')"
|
||||
:tag-width="60"
|
||||
:taggable="true"
|
||||
class="multiselect-vue"
|
||||
label="name"
|
||||
tag-placeholder="create"
|
||||
track-by="id"
|
||||
@tag="createGroup">
|
||||
<!-- If user is not admin, he is a subadmin.
|
||||
Subadmins can't create users outside their groups
|
||||
|
@ -152,63 +159,64 @@
|
|||
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||
</Multiselect>
|
||||
</div>
|
||||
<div v-if="subAdminsGroups.length>0 && settings.isAdmin" class="subadmins">
|
||||
<div v-if="subAdminsGroups.length>0 && settings.isAdmin"
|
||||
class="subadmins">
|
||||
<Multiselect v-model="newUser.subAdminsGroups"
|
||||
:close-on-select="false"
|
||||
:multiple="true"
|
||||
:options="subAdminsGroups"
|
||||
:placeholder="t('settings', 'Set user as admin for')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
:tag-width="60"
|
||||
class="multiselect-vue"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:tag-width="60">
|
||||
label="name"
|
||||
track-by="id">
|
||||
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||
</Multiselect>
|
||||
</div>
|
||||
<div class="quota">
|
||||
<Multiselect v-model="newUser.quota"
|
||||
:allow-empty="false"
|
||||
:options="quotaOptions"
|
||||
:placeholder="t('settings', 'Select user quota')"
|
||||
:taggable="true"
|
||||
class="multiselect-vue"
|
||||
label="label"
|
||||
track-by="id"
|
||||
class="multiselect-vue"
|
||||
:allow-empty="false"
|
||||
:taggable="true"
|
||||
@tag="validateQuota" />
|
||||
</div>
|
||||
<div v-if="showConfig.showLanguages" class="languages">
|
||||
<Multiselect v-model="newUser.language"
|
||||
:allow-empty="false"
|
||||
:options="languages"
|
||||
:placeholder="t('settings', 'Default language')"
|
||||
label="name"
|
||||
track-by="code"
|
||||
class="multiselect-vue"
|
||||
:allow-empty="false"
|
||||
group-label="label"
|
||||
group-values="languages"
|
||||
group-label="label" />
|
||||
label="name"
|
||||
track-by="code" />
|
||||
</div>
|
||||
<div v-if="showConfig.showStoragePath" class="storageLocation" />
|
||||
<div v-if="showConfig.showUserBackend" class="userBackend" />
|
||||
<div v-if="showConfig.showLastLogin" class="lastLogin" />
|
||||
<div class="userActions">
|
||||
<input id="newsubmit"
|
||||
type="submit"
|
||||
:title="t('settings', 'Add a new user')"
|
||||
class="button primary icon-checkmark-white has-tooltip"
|
||||
value=""
|
||||
:title="t('settings', 'Add a new user')">
|
||||
type="submit"
|
||||
value="">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<user-row v-for="(user, key) in filteredUsers"
|
||||
:key="key"
|
||||
:user="user"
|
||||
:external-actions="externalActions"
|
||||
:groups="groups"
|
||||
:languages="languages"
|
||||
:quota-options="quotaOptions"
|
||||
:settings="settings"
|
||||
:show-config="showConfig"
|
||||
:groups="groups"
|
||||
:sub-admins-groups="subAdminsGroups"
|
||||
:quota-options="quotaOptions"
|
||||
:languages="languages"
|
||||
:external-actions="externalActions" />
|
||||
:user="user" />
|
||||
<InfiniteLoading ref="infiniteLoading" @infinite="infiniteHandler">
|
||||
<div slot="spinner">
|
||||
<div class="users-icon-loading icon-loading" />
|
||||
|
@ -328,7 +336,10 @@ export default {
|
|||
},
|
||||
quotaOptions() {
|
||||
// convert the preset array into objects
|
||||
let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({ id: cur, label: cur }), [])
|
||||
let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({
|
||||
id: cur,
|
||||
label: cur
|
||||
}), [])
|
||||
// add default presets
|
||||
quotaPreset.unshift(this.unlimitedQuota)
|
||||
quotaPreset.unshift(this.defaultQuota)
|
||||
|
@ -377,9 +388,9 @@ export default {
|
|||
// deleting the last user, reset the list
|
||||
if (val === 0 && old === 1) {
|
||||
this.$refs.infiniteLoading.stateChanger.reset()
|
||||
// adding the first user, warn the infiniteLoader that
|
||||
// the list is not empty anymore (we don't fetch the newly
|
||||
// added user as we already have all the info we need)
|
||||
// adding the first user, warn the infiniteLoader that
|
||||
// the list is not empty anymore (we don't fetch the newly
|
||||
// added user as we already have all the info we need)
|
||||
} else if (val === 1 && old === 0) {
|
||||
this.$refs.infiniteLoading.stateChanger.loaded()
|
||||
}
|
||||
|
@ -437,7 +448,9 @@ export default {
|
|||
group: this.selectedGroup !== 'disabled' ? this.selectedGroup : '',
|
||||
search: this.searchQuery
|
||||
})
|
||||
.then((response) => { response ? $state.loaded() : $state.complete() })
|
||||
.then((response) => {
|
||||
response ? $state.loaded() : $state.complete()
|
||||
})
|
||||
},
|
||||
|
||||
/* SEARCH */
|
||||
|
@ -492,10 +505,10 @@ export default {
|
|||
if (error.response && error.response.data && error.response.data.ocs && error.response.data.ocs.meta) {
|
||||
const statuscode = error.response.data.ocs.meta.statuscode
|
||||
if (statuscode === 102) {
|
||||
// wrong username
|
||||
// wrong username
|
||||
this.$refs.newusername.focus()
|
||||
} else if (statuscode === 107) {
|
||||
// wrong password
|
||||
// wrong password
|
||||
this.$refs.newuserpassword.focus()
|
||||
}
|
||||
}
|
||||
|
@ -542,7 +555,7 @@ export default {
|
|||
redirectIfDisabled() {
|
||||
const allGroups = this.$store.getters.getGroups
|
||||
if (this.selectedGroup === 'disabled'
|
||||
&& allGroups.findIndex(group => group.id === 'disabled' && group.usercount === 0) > -1) {
|
||||
&& allGroups.findIndex(group => group.id === 'disabled' && group.usercount === 0) > -1) {
|
||||
// disabled group is empty, redirection to all users
|
||||
this.$router.push({ name: 'users' })
|
||||
this.$refs.infiniteLoading.stateChanger.reset()
|
||||
|
|
|
@ -24,14 +24,15 @@
|
|||
|
||||
<template>
|
||||
<!-- Obfuscated user: Logged in user does not have permissions to see all of the data -->
|
||||
<div v-if="Object.keys(user).length ===1" class="row" :data-id="user.id">
|
||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
||||
<div v-if="Object.keys(user).length ===1" :data-id="user.id" class="row">
|
||||
<div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}"
|
||||
class="avatar">
|
||||
<img v-if="!loading.delete && !loading.disable && !loading.wipe"
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
:src="generateAvatar(user.id, 32)"
|
||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
|
||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
|
||||
alt=""
|
||||
height="32"
|
||||
width="32">
|
||||
</div>
|
||||
<div class="name">
|
||||
{{ user.id }}
|
||||
|
@ -42,163 +43,189 @@
|
|||
</div>
|
||||
|
||||
<!-- User full data -->
|
||||
<UserRowSimple
|
||||
v-else-if="!editing"
|
||||
:editing.sync="editing"
|
||||
:feedback-message="feedbackMessage"
|
||||
:groups="groups"
|
||||
:languages="languages"
|
||||
:loading="loading"
|
||||
:opened-menu="openedMenu"
|
||||
:settings="settings"
|
||||
:show-config="showConfig"
|
||||
:sub-admins-groups="subAdminsGroups"
|
||||
:user-actions="userActions"
|
||||
:user="user"
|
||||
@hideMenu="hideMenu"
|
||||
@toggleMenu="toggleMenu" />
|
||||
<div v-else
|
||||
class="row"
|
||||
:class="{'disabled': loading.delete || loading.disable}"
|
||||
:data-id="user.id">
|
||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
||||
:data-id="user.id"
|
||||
class="row row--editable">
|
||||
<div :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}"
|
||||
class="avatar">
|
||||
<img v-if="!loading.delete && !loading.disable && !loading.wipe"
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
:src="generateAvatar(user.id, 32)"
|
||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
|
||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"
|
||||
alt=""
|
||||
height="32"
|
||||
width="32">
|
||||
</div>
|
||||
<!-- dirty hack to ellipsis on two lines -->
|
||||
<div class="name">
|
||||
{{ user.id }}
|
||||
<div class="displayName">
|
||||
<form
|
||||
:class="{'icon-loading-small': loading.displayName}"
|
||||
class="displayName"
|
||||
@submit.prevent="updateDisplayName">
|
||||
<template v-if="user.backendCapabilities.setDisplayName">
|
||||
<input v-if="user.backendCapabilities.setDisplayName"
|
||||
:id="'displayName'+user.id+rand"
|
||||
ref="displayName"
|
||||
:disabled="loading.displayName||loading.all"
|
||||
:value="user.displayname"
|
||||
autocapitalize="off"
|
||||
autocomplete="new-password"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
type="text">
|
||||
<input v-if="user.backendCapabilities.setDisplayName"
|
||||
class="icon-confirm"
|
||||
type="submit"
|
||||
value="">
|
||||
</template>
|
||||
<div v-else
|
||||
v-tooltip.auto="t('settings', 'The backend does not support changing the display name')"
|
||||
class="name" />
|
||||
</form>
|
||||
</div>
|
||||
<form class="displayName" :class="{'icon-loading-small': loading.displayName}" @submit.prevent="updateDisplayName">
|
||||
<template v-if="user.backendCapabilities.setDisplayName">
|
||||
<input v-if="user.backendCapabilities.setDisplayName"
|
||||
:id="'displayName'+user.id+rand"
|
||||
ref="displayName"
|
||||
type="text"
|
||||
:disabled="loading.displayName||loading.all"
|
||||
:value="user.displayname"
|
||||
autocomplete="new-password"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false">
|
||||
<input v-if="user.backendCapabilities.setDisplayName"
|
||||
type="submit"
|
||||
class="icon-confirm"
|
||||
value="">
|
||||
</template>
|
||||
<div v-else v-tooltip.auto="t('settings', 'The backend does not support changing the display name')" class="name">
|
||||
{{ user.displayname }}
|
||||
</div>
|
||||
</form>
|
||||
<form v-if="settings.canChangePassword && user.backendCapabilities.setPassword"
|
||||
class="password"
|
||||
:class="{'icon-loading-small': loading.password}"
|
||||
class="password"
|
||||
@submit.prevent="updatePassword">
|
||||
<input :id="'password'+user.id+rand"
|
||||
ref="password"
|
||||
type="password"
|
||||
required
|
||||
:disabled="loading.password||loading.all"
|
||||
:disabled="loading.password || loading.all"
|
||||
:minlength="minPasswordLength"
|
||||
value=""
|
||||
:placeholder="t('settings', 'New password')"
|
||||
:placeholder="t('settings', 'Add new password')"
|
||||
autocapitalize="off"
|
||||
autocomplete="new-password"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false">
|
||||
<input type="submit" class="icon-confirm" value="">
|
||||
required
|
||||
spellcheck="false"
|
||||
type="password"
|
||||
value="">
|
||||
<input class="icon-confirm" type="submit" value="">
|
||||
</form>
|
||||
<div v-else />
|
||||
<form class="mailAddress" :class="{'icon-loading-small': loading.mailAddress}" @submit.prevent="updateEmail">
|
||||
<form :class="{'icon-loading-small': loading.mailAddress}"
|
||||
class="mailAddress"
|
||||
@submit.prevent="updateEmail">
|
||||
<input :id="'mailAddress'+user.id+rand"
|
||||
ref="mailAddress"
|
||||
type="email"
|
||||
:disabled="loading.mailAddress||loading.all"
|
||||
:placeholder="t('settings', 'Add new email address')"
|
||||
:value="user.email"
|
||||
autocapitalize="off"
|
||||
autocomplete="new-password"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false">
|
||||
<input type="submit" class="icon-confirm" value="">
|
||||
spellcheck="false"
|
||||
type="email">
|
||||
<input class="icon-confirm" type="submit" value="">
|
||||
</form>
|
||||
<div class="groups" :class="{'icon-loading-small': loading.groups}">
|
||||
<Multiselect :value="userGroups"
|
||||
:options="availableGroups"
|
||||
<div :class="{'icon-loading-small': loading.groups}" class="groups">
|
||||
<Multiselect :close-on-select="false"
|
||||
:disabled="loading.groups||loading.all"
|
||||
tag-placeholder="create"
|
||||
:limit="2"
|
||||
:multiple="true"
|
||||
:options="availableGroups"
|
||||
:placeholder="t('settings', 'Add user in group')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
class="multiselect-vue"
|
||||
:limit="2"
|
||||
:multiple="true"
|
||||
:tag-width="60"
|
||||
:taggable="settings.isAdmin"
|
||||
:close-on-select="false"
|
||||
:tag-width="60"
|
||||
@tag="createGroup"
|
||||
:value="userGroups"
|
||||
class="multiselect-vue"
|
||||
label="name"
|
||||
tag-placeholder="create"
|
||||
track-by="id"
|
||||
@remove="removeUserGroup"
|
||||
@select="addUserGroup"
|
||||
@remove="removeUserGroup">
|
||||
<span slot="limit" v-tooltip.auto="formatGroupsTitle(userGroups)" class="multiselect__limit">+{{ userGroups.length-2 }}</span>
|
||||
@tag="createGroup">
|
||||
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||
</Multiselect>
|
||||
</div>
|
||||
<div v-if="subAdminsGroups.length>0 && settings.isAdmin" class="subadmins" :class="{'icon-loading-small': loading.subadmins}">
|
||||
<Multiselect :value="userSubAdminsGroups"
|
||||
:options="subAdminsGroups"
|
||||
<div v-if="subAdminsGroups.length>0 && settings.isAdmin"
|
||||
:class="{'icon-loading-small': loading.subadmins}"
|
||||
class="subadmins">
|
||||
<Multiselect :close-on-select="false"
|
||||
:disabled="loading.subadmins||loading.all"
|
||||
:placeholder="t('settings', 'Set user as admin for')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
class="multiselect-vue"
|
||||
:limit="2"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
:options="subAdminsGroups"
|
||||
:placeholder="t('settings', 'Set user as admin for')"
|
||||
:tag-width="60"
|
||||
@select="addUserSubAdmin"
|
||||
@remove="removeUserSubAdmin">
|
||||
<span slot="limit" v-tooltip.auto="formatGroupsTitle(userSubAdminsGroups)" class="multiselect__limit">+{{ userSubAdminsGroups.length-2 }}</span>
|
||||
:value="userSubAdminsGroups"
|
||||
class="multiselect-vue"
|
||||
label="name"
|
||||
track-by="id"
|
||||
@remove="removeUserSubAdmin"
|
||||
@select="addUserSubAdmin">
|
||||
<span slot="noResult">{{ t('settings', 'No results') }}</span>
|
||||
</Multiselect>
|
||||
</div>
|
||||
<div v-tooltip.auto="usedSpace" class="quota" :class="{'icon-loading-small': loading.quota}">
|
||||
<Multiselect :value="userQuota"
|
||||
:options="quotaOptions"
|
||||
<div v-tooltip.auto="usedSpace"
|
||||
:class="{'icon-loading-small': loading.quota}"
|
||||
class="quota">
|
||||
<Multiselect :allow-empty="false"
|
||||
:disabled="loading.quota||loading.all"
|
||||
tag-placeholder="create"
|
||||
:options="quotaOptions"
|
||||
:placeholder="t('settings', 'Select user quota')"
|
||||
label="label"
|
||||
track-by="id"
|
||||
class="multiselect-vue"
|
||||
:allow-empty="false"
|
||||
:taggable="true"
|
||||
@tag="validateQuota"
|
||||
@input="setUserQuota" />
|
||||
<progress class="quota-user-progress"
|
||||
:class="{'warn':usedQuota>80}"
|
||||
:value="usedQuota"
|
||||
max="100" />
|
||||
:value="userQuota"
|
||||
class="multiselect-vue"
|
||||
label="label"
|
||||
tag-placeholder="create"
|
||||
track-by="id"
|
||||
@input="setUserQuota"
|
||||
@tag="validateQuota" />
|
||||
</div>
|
||||
<div v-if="showConfig.showLanguages"
|
||||
class="languages"
|
||||
:class="{'icon-loading-small': loading.languages}">
|
||||
<Multiselect :value="userLanguage"
|
||||
:options="languages"
|
||||
:class="{'icon-loading-small': loading.languages}"
|
||||
class="languages">
|
||||
<Multiselect :allow-empty="false"
|
||||
:disabled="loading.languages||loading.all"
|
||||
:options="languages"
|
||||
:placeholder="t('settings', 'No language set')"
|
||||
:value="userLanguage"
|
||||
class="multiselect-vue"
|
||||
group-label="label"
|
||||
group-values="languages"
|
||||
label="name"
|
||||
track-by="code"
|
||||
class="multiselect-vue"
|
||||
:allow-empty="false"
|
||||
group-values="languages"
|
||||
group-label="label"
|
||||
@input="setUserLanguage" />
|
||||
</div>
|
||||
<div v-if="showConfig.showStoragePath" class="storageLocation">
|
||||
{{ user.storageLocation }}
|
||||
</div>
|
||||
<div v-if="showConfig.showUserBackend" class="userBackend">
|
||||
{{ user.backend }}
|
||||
</div>
|
||||
<div v-if="showConfig.showLastLogin" v-tooltip.auto="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''" class="lastLogin">
|
||||
{{ user.lastLogin>0 ? OC.Util.relativeModifiedDate(user.lastLogin) : t('settings','Never') }}
|
||||
</div>
|
||||
|
||||
<!-- don't show this on edit mode -->
|
||||
<div v-if="showConfig.showStoragePath || showConfig.showUserBackend"
|
||||
class="storageLocation" />
|
||||
<div v-if="showConfig.showLastLogin" />
|
||||
|
||||
<div class="userActions">
|
||||
<div v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all" class="toggleUserActions">
|
||||
<div v-click-outside="hideMenu" class="icon-more" @click="toggleMenu" />
|
||||
<div class="popovermenu" :class="{ 'open': openedMenu }">
|
||||
<div v-if="OC.currentUser !== user.id && user.id !== 'admin' && !loading.all"
|
||||
class="toggleUserActions">
|
||||
<Actions>
|
||||
<ActionButton icon="icon-checkmark"
|
||||
@click="editing = false">
|
||||
{{ t('settings', 'Done') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<div v-click-outside="hideMenu"
|
||||
class="icon-more"
|
||||
@click="toggleMenu" />
|
||||
<div :class="{ 'open': openedMenu }" class="popovermenu">
|
||||
<PopoverMenu :menu="userActions" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}">
|
||||
<div :style="{opacity: feedbackMessage !== '' ? 1 : 0}"
|
||||
class="feedback">
|
||||
<div class="icon-checkmark" />
|
||||
{{ feedbackMessage }}
|
||||
</div>
|
||||
|
@ -210,19 +237,30 @@
|
|||
import ClickOutside from 'vue-click-outside'
|
||||
import Vue from 'vue'
|
||||
import VTooltip from 'v-tooltip'
|
||||
import { PopoverMenu, Multiselect } from 'nextcloud-vue'
|
||||
import {
|
||||
PopoverMenu,
|
||||
Multiselect,
|
||||
Actions,
|
||||
ActionButton
|
||||
} from 'nextcloud-vue'
|
||||
import UserRowSimple from './UserRowSimple'
|
||||
import UserRowMixin from '../../mixins/UserRowMixin'
|
||||
|
||||
Vue.use(VTooltip)
|
||||
|
||||
export default {
|
||||
name: 'UserRow',
|
||||
components: {
|
||||
UserRowSimple,
|
||||
PopoverMenu,
|
||||
Actions,
|
||||
ActionButton,
|
||||
Multiselect
|
||||
},
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
mixins: [UserRowMixin],
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
|
@ -262,6 +300,7 @@ export default {
|
|||
rand: parseInt(Math.random() * 1000),
|
||||
openedMenu: false,
|
||||
feedbackMessage: '',
|
||||
editing: false,
|
||||
loading: {
|
||||
all: false,
|
||||
displayName: false,
|
||||
|
@ -305,92 +344,9 @@ export default {
|
|||
})
|
||||
}
|
||||
return actions.concat(this.externalActions)
|
||||
},
|
||||
|
||||
/* GROUPS MANAGEMENT */
|
||||
userGroups() {
|
||||
let userGroups = this.groups.filter(group => this.user.groups.includes(group.id))
|
||||
return userGroups
|
||||
},
|
||||
userSubAdminsGroups() {
|
||||
let userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id))
|
||||
return userSubAdminsGroups
|
||||
},
|
||||
availableGroups() {
|
||||
return this.groups.map((group) => {
|
||||
// clone object because we don't want
|
||||
// to edit the original groups
|
||||
let groupClone = Object.assign({}, group)
|
||||
|
||||
// two settings here:
|
||||
// 1. user NOT in group but no permission to add
|
||||
// 2. user is in group but no permission to remove
|
||||
groupClone.$isDisabled
|
||||
= (group.canAdd === false
|
||||
&& !this.user.groups.includes(group.id))
|
||||
|| (group.canRemove === false
|
||||
&& this.user.groups.includes(group.id))
|
||||
return groupClone
|
||||
})
|
||||
},
|
||||
|
||||
/* QUOTA MANAGEMENT */
|
||||
usedSpace() {
|
||||
if (this.user.quota.used) {
|
||||
return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
|
||||
}
|
||||
return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
|
||||
},
|
||||
usedQuota() {
|
||||
let quota = this.user.quota.quota
|
||||
if (quota > 0) {
|
||||
quota = Math.min(100, Math.round(this.user.quota.used / quota * 100))
|
||||
} else {
|
||||
var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30))
|
||||
// asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
|
||||
quota = 95 * (1 - (1 / (usedInGB + 1)))
|
||||
}
|
||||
return isNaN(quota) ? 0 : quota
|
||||
},
|
||||
// Mapping saved values to objects
|
||||
userQuota() {
|
||||
if (this.user.quota.quota >= 0) {
|
||||
// if value is valid, let's map the quotaOptions or return custom quota
|
||||
let humanQuota = OC.Util.humanFileSize(this.user.quota.quota)
|
||||
let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota)
|
||||
return userQuota || { id: humanQuota, label: humanQuota }
|
||||
} else if (this.user.quota.quota === 'default') {
|
||||
// default quota is replaced by the proper value on load
|
||||
return this.quotaOptions[0]
|
||||
}
|
||||
return this.quotaOptions[1] // unlimited
|
||||
},
|
||||
|
||||
/* PASSWORD POLICY? */
|
||||
minPasswordLength() {
|
||||
return this.$store.getters.getPasswordPolicyMinLength
|
||||
},
|
||||
|
||||
/* LANGUAGE */
|
||||
userLanguage() {
|
||||
let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages)
|
||||
let userLang = availableLanguages.find(lang => lang.code === this.user.language)
|
||||
if (typeof userLang !== 'object' && this.user.language !== '') {
|
||||
return {
|
||||
code: this.user.language,
|
||||
name: this.user.language
|
||||
}
|
||||
} else if (this.user.language === '') {
|
||||
return false
|
||||
}
|
||||
return userLang
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// required if popup needs to stay opened after menu click
|
||||
// since we only have disable/delete actions, let's close it directly
|
||||
// this.popupItem = this.$el;
|
||||
},
|
||||
|
||||
methods: {
|
||||
/* MENU HANDLING */
|
||||
toggleMenu() {
|
||||
|
@ -400,35 +356,6 @@ export default {
|
|||
this.openedMenu = false
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate avatar url
|
||||
*
|
||||
* @param {string} user The user name
|
||||
* @param {int} size Size integer, default 32
|
||||
* @returns {string}
|
||||
*/
|
||||
generateAvatar(user, size = 32) {
|
||||
return OC.generateUrl(
|
||||
'/avatar/{user}/{size}?v={version}',
|
||||
{
|
||||
user: user,
|
||||
size: size,
|
||||
version: oc_userconfig.avatar.version
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Format array of groups objects to a string for the popup
|
||||
*
|
||||
* @param {array} groups The groups
|
||||
* @returns {string}
|
||||
*/
|
||||
formatGroupsTitle(groups) {
|
||||
let names = groups.map(group => group.name)
|
||||
return names.slice(2).join(', ')
|
||||
},
|
||||
|
||||
wipeUserDevices() {
|
||||
let userid = this.user.id
|
||||
OC.dialogs.confirmDestructive(
|
||||
|
@ -486,7 +413,10 @@ export default {
|
|||
this.loading.all = true
|
||||
let userid = this.user.id
|
||||
let enabled = !this.user.enabled
|
||||
return this.$store.dispatch('enableDisableUser', { userid, enabled })
|
||||
return this.$store.dispatch('enableDisableUser', {
|
||||
userid,
|
||||
enabled
|
||||
})
|
||||
.then(() => {
|
||||
this.loading.delete = false
|
||||
this.loading.all = false
|
||||
|
@ -494,10 +424,10 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Set user displayName
|
||||
*
|
||||
* @param {string} displayName The display name
|
||||
*/
|
||||
* Set user displayName
|
||||
*
|
||||
* @param {string} displayName The display name
|
||||
*/
|
||||
updateDisplayName() {
|
||||
let displayName = this.$refs.displayName.value
|
||||
this.loading.displayName = true
|
||||
|
@ -512,10 +442,10 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Set user password
|
||||
*
|
||||
* @param {string} password The email adress
|
||||
*/
|
||||
* Set user password
|
||||
*
|
||||
* @param {string} password The email adress
|
||||
*/
|
||||
updatePassword() {
|
||||
let password = this.$refs.password.value
|
||||
this.loading.password = true
|
||||
|
@ -530,10 +460,10 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Set user mailAddress
|
||||
*
|
||||
* @param {string} mailAddress The email adress
|
||||
*/
|
||||
* Set user mailAddress
|
||||
*
|
||||
* @param {string} mailAddress The email adress
|
||||
*/
|
||||
updateEmail() {
|
||||
let mailAddress = this.$refs.mailAddress.value
|
||||
this.loading.mailAddress = true
|
||||
|
@ -548,10 +478,10 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Create a new group and add user to it
|
||||
*
|
||||
* @param {string} gid Group id
|
||||
*/
|
||||
* Create a new group and add user to it
|
||||
*
|
||||
* @param {string} gid Group id
|
||||
*/
|
||||
async createGroup(gid) {
|
||||
this.loading = { groups: true, subadmins: true }
|
||||
try {
|
||||
|
@ -567,10 +497,10 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Add user to group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
* Add user to group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
async addUserGroup(group) {
|
||||
if (group.canAdd === false) {
|
||||
return false
|
||||
|
@ -588,10 +518,10 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Remove user from group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
* Remove user from group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
async removeUserGroup(group) {
|
||||
if (group.canRemove === false) {
|
||||
return false
|
||||
|
@ -602,7 +532,10 @@ export default {
|
|||
let gid = group.id
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('removeUserGroup', { userid, gid })
|
||||
await this.$store.dispatch('removeUserGroup', {
|
||||
userid,
|
||||
gid
|
||||
})
|
||||
this.loading.groups = false
|
||||
// remove user from current list if current list is the removed group
|
||||
if (this.$route.params.selectedGroup === gid) {
|
||||
|
@ -614,17 +547,20 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Add user to group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
* Add user to group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
async addUserSubAdmin(group) {
|
||||
this.loading.subadmins = true
|
||||
let userid = this.user.id
|
||||
let gid = group.id
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('addUserSubAdmin', { userid, gid })
|
||||
await this.$store.dispatch('addUserSubAdmin', {
|
||||
userid,
|
||||
gid
|
||||
})
|
||||
this.loading.subadmins = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -632,17 +568,20 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Remove user from group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
* Remove user from group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
*/
|
||||
async removeUserSubAdmin(group) {
|
||||
this.loading.subadmins = true
|
||||
let userid = this.user.id
|
||||
let gid = group.id
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('removeUserSubAdmin', { userid, gid })
|
||||
await this.$store.dispatch('removeUserSubAdmin', {
|
||||
userid,
|
||||
gid
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
|
@ -651,11 +590,11 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Dispatch quota set request
|
||||
*
|
||||
* @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
|
||||
* @returns {string}
|
||||
*/
|
||||
* Dispatch quota set request
|
||||
*
|
||||
* @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
|
||||
* @returns {string}
|
||||
*/
|
||||
async setUserQuota(quota = 'none') {
|
||||
this.loading.quota = true
|
||||
// ensure we only send the preset id
|
||||
|
@ -676,11 +615,11 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Validate quota string to make sure it's a valid human file size
|
||||
*
|
||||
* @param {string} quota Quota in readable format '5 GB'
|
||||
* @returns {Promise|boolean}
|
||||
*/
|
||||
* Validate quota string to make sure it's a valid human file size
|
||||
*
|
||||
* @param {string} quota Quota in readable format '5 GB'
|
||||
* @returns {Promise|boolean}
|
||||
*/
|
||||
validateQuota(quota) {
|
||||
// only used for new presets sent through @Tag
|
||||
let validQuota = OC.Util.computerFileSize(quota)
|
||||
|
@ -693,11 +632,11 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Dispatch language set request
|
||||
*
|
||||
* @param {Object} lang language object {code:'en', name:'English'}
|
||||
* @returns {Object}
|
||||
*/
|
||||
* Dispatch language set request
|
||||
*
|
||||
* @param {Object} lang language object {code:'en', name:'English'}
|
||||
* @returns {Object}
|
||||
*/
|
||||
async setUserLanguage(lang) {
|
||||
this.loading.languages = true
|
||||
// ensure we only send the preset id
|
||||
|
@ -716,8 +655,8 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Dispatch new welcome mail request
|
||||
*/
|
||||
* Dispatch new welcome mail request
|
||||
*/
|
||||
sendWelcomeMail() {
|
||||
this.loading.all = true
|
||||
this.$store.dispatch('sendWelcomeMail', this.user.id)
|
||||
|
|
159
apps/settings/src/components/UserList/UserRowSimple.vue
Normal file
159
apps/settings/src/components/UserList/UserRowSimple.vue
Normal file
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<div
|
||||
class="row"
|
||||
:class="{'disabled': loading.delete || loading.disable}"
|
||||
:data-id="user.id">
|
||||
<div class="avatar" :class="{'icon-loading-small': loading.delete || loading.disable || loading.wipe}">
|
||||
<img v-if="!loading.delete && !loading.disable && !loading.wipe"
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
:src="generateAvatar(user.id, 32)"
|
||||
:srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'">
|
||||
</div>
|
||||
<!-- dirty hack to ellipsis on two lines -->
|
||||
<div class="name">
|
||||
{{ user.id }}
|
||||
<div class="displayName subtitle">
|
||||
{{ user.displayname }}
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
<div class="mailAddress">
|
||||
{{ user.email }}
|
||||
</div>
|
||||
<div class="groups">
|
||||
{{ userGroupsLabels }}
|
||||
</div>
|
||||
<div v-if="subAdminsGroups.length > 0 && settings.isAdmin" class="subAdminsGroups">
|
||||
{{ userSubAdminsGroupsLabels }}
|
||||
</div>
|
||||
<div v-tooltip.auto="usedSpace" class="quota">
|
||||
<progress
|
||||
class="quota-user-progress"
|
||||
:class="{'warn': usedQuota > 80}"
|
||||
:value="usedQuota"
|
||||
max="100" />
|
||||
</div>
|
||||
<div v-if="showConfig.showLanguages" class="languages">
|
||||
{{ userLanguage.name }}
|
||||
</div>
|
||||
<div v-if="showConfig.showUserBackend || showConfig.showStoragePath" class="userBackend">
|
||||
<div v-if="showConfig.showUserBackend" class="userBackend">
|
||||
{{ user.backend }}
|
||||
</div>
|
||||
<div v-if="showConfig.showStoragePath" class="storageLocation subtitle">
|
||||
{{ user.storageLocation }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showConfig.showLastLogin" v-tooltip.auto="userLastLoginTooltip" class="lastLogin">
|
||||
{{ userLastLogin }}
|
||||
</div>
|
||||
|
||||
<div class="userActions">
|
||||
<div v-if="canEdit && !loading.all" class="toggleUserActions">
|
||||
<Actions>
|
||||
<ActionButton icon="icon-rename" @click="toggleEdit">
|
||||
{{ t('settings', 'Edit User') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<div v-click-outside="hideMenu" class="icon-more" @click="$emit('toggleMenu')" />
|
||||
<div class="popovermenu" :class="{ 'open': openedMenu }">
|
||||
<PopoverMenu :menu="userActions" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="feedback" :style="{opacity: feedbackMessage !== '' ? 1 : 0}">
|
||||
<div class="icon-checkmark" />
|
||||
{{ feedbackMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { PopoverMenu, Actions, ActionButton } from 'nextcloud-vue'
|
||||
import ClickOutside from 'vue-click-outside'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
||||
import UserRowMixin from '../../mixins/UserRowMixin'
|
||||
export default {
|
||||
name: 'UserRowSimple',
|
||||
components: {
|
||||
PopoverMenu,
|
||||
ActionButton,
|
||||
Actions
|
||||
},
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
mixins: [UserRowMixin],
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
showConfig: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
userActions: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
openedMenu: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
feedbackMessage: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
subAdminsGroups: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
settings: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userGroupsLabels() {
|
||||
return this.userGroups
|
||||
.map(group => group.name)
|
||||
.join(', ')
|
||||
},
|
||||
userSubAdminsGroupsLabels() {
|
||||
return this.userSubAdminsGroups
|
||||
.map(group => group.name)
|
||||
.join(', ')
|
||||
},
|
||||
usedSpace() {
|
||||
if (this.user.quota.used) {
|
||||
return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
|
||||
}
|
||||
return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
|
||||
},
|
||||
canEdit() {
|
||||
return getCurrentUser().uid !== this.user.id && this.user.id !== 'admin'
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
hideMenu() {
|
||||
this.$emit('hideMenu')
|
||||
},
|
||||
toggleEdit() {
|
||||
this.$emit('update:editing', true)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
171
apps/settings/src/mixins/UserRowMixin.js
Normal file
171
apps/settings/src/mixins/UserRowMixin.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
export default {
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
settings: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
groups: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
subAdminsGroups: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
quotaOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
showConfig: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
languages: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
externalActions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/* GROUPS MANAGEMENT */
|
||||
userGroups() {
|
||||
const userGroups = this.groups.filter(group => this.user.groups.includes(group.id))
|
||||
return userGroups
|
||||
},
|
||||
userSubAdminsGroups() {
|
||||
const userSubAdminsGroups = this.subAdminsGroups.filter(group => this.user.subadmin.includes(group.id))
|
||||
return userSubAdminsGroups
|
||||
},
|
||||
availableGroups() {
|
||||
return this.groups.map((group) => {
|
||||
// clone object because we don't want
|
||||
// to edit the original groups
|
||||
let groupClone = Object.assign({}, group)
|
||||
|
||||
// two settings here:
|
||||
// 1. user NOT in group but no permission to add
|
||||
// 2. user is in group but no permission to remove
|
||||
groupClone.$isDisabled
|
||||
= (group.canAdd === false
|
||||
&& !this.user.groups.includes(group.id))
|
||||
|| (group.canRemove === false
|
||||
&& this.user.groups.includes(group.id))
|
||||
return groupClone
|
||||
})
|
||||
},
|
||||
|
||||
/* QUOTA MANAGEMENT */
|
||||
usedSpace() {
|
||||
if (this.user.quota.used) {
|
||||
return t('settings', '{size} used', { size: OC.Util.humanFileSize(this.user.quota.used) })
|
||||
}
|
||||
return t('settings', '{size} used', { size: OC.Util.humanFileSize(0) })
|
||||
},
|
||||
usedQuota() {
|
||||
let quota = this.user.quota.quota
|
||||
if (quota > 0) {
|
||||
quota = Math.min(100, Math.round(this.user.quota.used / quota * 100))
|
||||
} else {
|
||||
var usedInGB = this.user.quota.used / (10 * Math.pow(2, 30))
|
||||
// asymptotic curve approaching 50% at 10GB to visualize used stace with infinite quota
|
||||
quota = 95 * (1 - (1 / (usedInGB + 1)))
|
||||
}
|
||||
return isNaN(quota) ? 0 : quota
|
||||
},
|
||||
// Mapping saved values to objects
|
||||
userQuota() {
|
||||
if (this.user.quota.quota >= 0) {
|
||||
// if value is valid, let's map the quotaOptions or return custom quota
|
||||
let humanQuota = OC.Util.humanFileSize(this.user.quota.quota)
|
||||
let userQuota = this.quotaOptions.find(quota => quota.id === humanQuota)
|
||||
return userQuota || { id: humanQuota, label: humanQuota }
|
||||
} else if (this.user.quota.quota === 'default') {
|
||||
// default quota is replaced by the proper value on load
|
||||
return this.quotaOptions[0]
|
||||
}
|
||||
return this.quotaOptions[1] // unlimited
|
||||
},
|
||||
|
||||
/* PASSWORD POLICY? */
|
||||
minPasswordLength() {
|
||||
return this.$store.getters.getPasswordPolicyMinLength
|
||||
},
|
||||
|
||||
/* LANGUAGE */
|
||||
userLanguage() {
|
||||
let availableLanguages = this.languages[0].languages.concat(this.languages[1].languages)
|
||||
let userLang = availableLanguages.find(lang => lang.code === this.user.language)
|
||||
if (typeof userLang !== 'object' && this.user.language !== '') {
|
||||
return {
|
||||
code: this.user.language,
|
||||
name: this.user.language
|
||||
}
|
||||
} else if (this.user.language === '') {
|
||||
return false
|
||||
}
|
||||
return userLang
|
||||
},
|
||||
|
||||
/* LAST LOGIN */
|
||||
userLastLoginTooltip() {
|
||||
if (this.user.lastLogin > 0) {
|
||||
return OC.Util.formatDate(this.user.lastLogin)
|
||||
}
|
||||
return ''
|
||||
},
|
||||
userLastLogin() {
|
||||
if (this.user.lastLogin > 0) {
|
||||
return OC.Util.relativeModifiedDate(this.user.lastLogin)
|
||||
}
|
||||
return t('settings', 'Never')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Generate avatar url
|
||||
*
|
||||
* @param {string} user The user name
|
||||
* @param {int} size Size integer, default 32
|
||||
* @returns {string}
|
||||
*/
|
||||
generateAvatar(user, size = 32) {
|
||||
return OC.generateUrl(
|
||||
'/avatar/{user}/{size}?v={version}',
|
||||
{
|
||||
user: user,
|
||||
size: size,
|
||||
version: oc_userconfig.avatar.version
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
|
||||
* @copyright Copyright (c) 2018, John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
||||
* @copyright Copyright (c) 2019, Greta Doci <gretadoci@gmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
|
@ -33,7 +34,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function newUserForm() {
|
||||
return Locator::forThe()->id("new-user")->
|
||||
describedAs("New user form in Users Settings");
|
||||
describedAs("New user form in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,7 +42,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function userNameFieldForNewUser() {
|
||||
return Locator::forThe()->field("newusername")->
|
||||
describedAs("User name field for new user in Users Settings");
|
||||
describedAs("User name field for new user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,7 +50,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function displayNameFieldForNewUser() {
|
||||
return Locator::forThe()->field("newdisplayname")->
|
||||
describedAs("Display name field for new user in Users Settings");
|
||||
describedAs("Display name field for new user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,7 +58,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function passwordFieldForNewUser() {
|
||||
return Locator::forThe()->field("newuserpassword")->
|
||||
describedAs("Password field for new user in Users Settings");
|
||||
describedAs("Password field for new user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,7 +66,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function newUserButton() {
|
||||
return Locator::forThe()->id("new-user-button")->
|
||||
describedAs("New user button in Users Settings");
|
||||
describedAs("New user button in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,26 +74,26 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function createNewUserButton() {
|
||||
return Locator::forThe()->xpath("//form[@id = 'new-user']//input[@type = 'submit']")->
|
||||
describedAs("Create user button in Users Settings");
|
||||
describedAs("Create user button in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function rowForUser($user) {
|
||||
return Locator::forThe()->xpath("//div[@id='app-content']/div/div[normalize-space() = '$user']/..")->
|
||||
describedAs("Row for user $user in Users Settings");
|
||||
return Locator::forThe()->css("div.user-list-grid div.row[data-id=$user]")->
|
||||
describedAs("Row for user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning: you need to watch out for the proper classes order
|
||||
*
|
||||
*
|
||||
* @return Locator
|
||||
*/
|
||||
public static function classCellForUser($class, $user) {
|
||||
return Locator::forThe()->xpath("//*[contains(concat(' ', normalize-space(@class), ' '), ' $class ')]")->
|
||||
descendantOf(self::rowForUser($user))->
|
||||
describedAs("$class cell for user $user in Users Settings");
|
||||
descendantOf(self::rowForUser($user))->
|
||||
describedAs("$class cell for user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,8 +101,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function inputForUserInCell($cell, $user) {
|
||||
return Locator::forThe()->css("input")->
|
||||
descendantOf(self::classCellForUser($cell, $user))->
|
||||
describedAs("$cell input for user $user in Users Settings");
|
||||
descendantOf(self::classCellForUser($cell, $user))->
|
||||
describedAs("$cell input for user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,8 +117,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function optionInInputForUser($cell, $user) {
|
||||
return Locator::forThe()->css(".multiselect__option--highlight")->
|
||||
descendantOf(self::classCellForUser($cell, $user))->
|
||||
describedAs("Selected $cell option in $cell input for user $user in Users Settings");
|
||||
descendantOf(self::classCellForUser($cell, $user))->
|
||||
describedAs("Selected $cell option in $cell input for user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,8 +126,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function actionsMenuOf($user) {
|
||||
return Locator::forThe()->css(".icon-more")->
|
||||
descendantOf(self::rowForUser($user))->
|
||||
describedAs("Actions menu for user $user in Users Settings");
|
||||
descendantOf(self::rowForUser($user))->
|
||||
describedAs("Actions menu for user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -134,8 +135,8 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function theAction($action, $user) {
|
||||
return Locator::forThe()->xpath("//button[normalize-space() = '$action']")->
|
||||
descendantOf(self::rowForUser($user))->
|
||||
describedAs("$action action for the user $user row in Users Settings");
|
||||
descendantOf(self::rowForUser($user))->
|
||||
describedAs("$action action for the user $user row in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,7 +144,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function theColumn($column) {
|
||||
return Locator::forThe()->xpath("//div[@class='user-list-grid']//div[normalize-space() = '$column']")->
|
||||
describedAs("The $column column in Users Settings");
|
||||
describedAs("The $column column in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -151,8 +152,25 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public static function selectedSelectOption($cell, $user) {
|
||||
return Locator::forThe()->css(".multiselect__single")->
|
||||
descendantOf(self::classCellForUser($cell, $user))->
|
||||
describedAs("The selected option of the $cell select for the user $user in Users Settings");
|
||||
descendantOf(self::classCellForUser($cell, $user))->
|
||||
describedAs("The selected option of the $cell select for the user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function editModeToggle($user) {
|
||||
return Locator::forThe()->css(".toggleUserActions button.icon-rename")->
|
||||
descendantOf(self::rowForUser($user))->
|
||||
describedAs("The edit toggle button for the user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Locator
|
||||
*/
|
||||
public static function editModeOn($user) {
|
||||
return Locator::forThe()->css("div.user-list-grid div.row.row--editable[data-id=$user]")->
|
||||
describedAs("I see the edit mode is on for the user $user in Users Settings");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -204,6 +222,13 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
$this->actor->find(self::createNewUserButton(), 10)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I toggle the edit mode for the user :user
|
||||
*/
|
||||
public function iToggleTheEditModeForUser($user) {
|
||||
$this->actor->find(self::editModeToggle($user), 10)->click();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When I create user :user with password :password
|
||||
*/
|
||||
|
@ -258,7 +283,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public function iSeeThatTheNewUserFormIsShown() {
|
||||
PHPUnit_Framework_Assert::assertTrue(
|
||||
$this->actor->find(self::newUserForm(), 10)->isVisible());
|
||||
$this->actor->find(self::newUserForm(), 10)->isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -266,7 +291,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public function iSeeTheAction($action, $user) {
|
||||
PHPUnit_Framework_Assert::assertTrue(
|
||||
$this->actor->find(self::theAction($action, $user), 10)->isVisible());
|
||||
$this->actor->find(self::theAction($action, $user), 10)->isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,7 +299,7 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
*/
|
||||
public function iSeeThatTheColumnIsShown($column) {
|
||||
PHPUnit_Framework_Assert::assertTrue(
|
||||
$this->actor->find(self::theColumn($column), 10)->isVisible());
|
||||
$this->actor->find(self::theColumn($column), 10)->isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -289,15 +314,16 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
* @Then I see that the display name for the user :user is :displayName
|
||||
*/
|
||||
public function iSeeThatTheDisplayNameForTheUserIs($user, $displayName) {
|
||||
PHPUnit_Framework_Assert::assertEquals($displayName, $this->actor->find(self::displayNameCellForUser($user), 10)->getValue());
|
||||
PHPUnit_Framework_Assert::assertEquals(
|
||||
$displayName, $this->actor->find(self::displayNameCellForUser($user), 10)->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then I see that the :cell cell for user :user is done loading
|
||||
*/
|
||||
public function iSeeThatTheCellForUserIsDoneLoading($cell, $user) {
|
||||
WaitFor::elementToBeEventuallyShown($this->actor, self::classCellForUser($cell.' icon-loading-small', $user));
|
||||
WaitFor::elementToBeEventuallyNotShown($this->actor, self::classCellForUser($cell.' icon-loading-small', $user));
|
||||
WaitFor::elementToBeEventuallyShown($this->actor, self::classCellForUser($cell . ' icon-loading-small', $user));
|
||||
WaitFor::elementToBeEventuallyNotShown($this->actor, self::classCellForUser($cell . ' icon-loading-small', $user));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -307,6 +333,11 @@ class UsersSettingsContext implements Context, ActorAwareInterface {
|
|||
PHPUnit_Framework_Assert::assertEquals(
|
||||
$this->actor->find(self::selectedSelectOption('quota', $user), 2)->getText(), $quota);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Then I see that the edit mode is on for user :user
|
||||
*/
|
||||
public function iSeeThatTheEditModeIsOn($user) {
|
||||
WaitFor::elementToBeEventuallyShown($this->actor, self::editModeOn($user));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,18 +63,20 @@ Feature: users
|
|||
And I am logged in as the admin
|
||||
And I open the User settings
|
||||
And I see that the list of users contains the user user0
|
||||
# disabled because we need the TAB patch:
|
||||
When I toggle the edit mode for the user user0
|
||||
Then I see that the edit mode is on for user user0
|
||||
# disabled because we need the TAB patch:
|
||||
# https://github.com/minkphp/MinkSelenium2Driver/pull/244
|
||||
# When I assign the user user0 to the group admin
|
||||
# Then I see that the section Admins is shown
|
||||
# And I see that the section Admins has a count of 2
|
||||
|
||||
|
||||
Scenario: create and delete a group
|
||||
Given I act as Jane
|
||||
And I am logged in as the admin
|
||||
And I open the User settings
|
||||
And I see that the list of users contains the user user0
|
||||
# disabled because we need the TAB patch:
|
||||
# disabled because we need the TAB patch:
|
||||
# https://github.com/minkphp/MinkSelenium2Driver/pull/244
|
||||
# And I assign the user user0 to the group Group1
|
||||
# And I see that the section Group1 is shown
|
||||
|
@ -112,7 +114,7 @@ Feature: users
|
|||
Then I see that the "Storage location" column is shown
|
||||
When I toggle the showUserBackend checkbox in the settings
|
||||
Then I see that the "User backend" column is shown
|
||||
|
||||
|
||||
# Scenario: change display name
|
||||
# Given I act as Jane
|
||||
# And I am logged in as the admin
|
||||
|
@ -128,6 +130,8 @@ Feature: users
|
|||
And I am logged in as the admin
|
||||
And I open the User settings
|
||||
And I see that the list of users contains the user user0
|
||||
When I toggle the edit mode for the user user0
|
||||
Then I see that the edit mode is on for user user0
|
||||
And I see that the password of user0 is ""
|
||||
When I set the password for user0 to 123456
|
||||
And I see that the password cell for user user0 is done loading
|
||||
|
@ -149,8 +153,10 @@ Feature: users
|
|||
And I am logged in as the admin
|
||||
And I open the User settings
|
||||
And I see that the list of users contains the user user0
|
||||
When I toggle the edit mode for the user user0
|
||||
Then I see that the edit mode is on for user user0
|
||||
And I see that the user quota of user0 is Unlimited
|
||||
# disabled because we need the TAB patch:
|
||||
# disabled because we need the TAB patch:
|
||||
# https://github.com/minkphp/MinkSelenium2Driver/pull/244
|
||||
# When I set the user user0 quota to 1GB
|
||||
# And I see that the quota cell for user user0 is done loading
|
||||
|
@ -163,4 +169,4 @@ Feature: users
|
|||
# Then I see that the user quota of user0 is "0 B"
|
||||
# When I set the user user0 quota to Default
|
||||
# And I see that the quota cell for user user0 is done loading
|
||||
# Then I see that the user quota of user0 is "Default quota"
|
||||
# Then I see that the user quota of user0 is "Default quota"
|
||||
|
|
Loading…
Reference in a new issue