Users.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <!--
  2. - @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
  3. -
  4. - @author John Molakvoæ <skjnldsv@protonmail.com>
  5. -
  6. - @license GNU AGPL version 3 or any later version
  7. -
  8. - This program is free software: you can redistribute it and/or modify
  9. - it under the terms of the GNU Affero General Public License as
  10. - published by the Free Software Foundation, either version 3 of the
  11. - License, or (at your option) any later version.
  12. -
  13. - This program is distributed in the hope that it will be useful,
  14. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. - GNU Affero General Public License for more details.
  17. -
  18. - You should have received a copy of the GNU Affero General Public License
  19. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. -
  21. -->
  22. <template>
  23. <Fragment>
  24. <NcContent app-name="settings" :navigation-class="{ 'icon-loading': loadingAddGroup }">
  25. <NcAppNavigation>
  26. <NcAppNavigationNew button-id="new-user-button"
  27. :text="t('settings','New user')"
  28. button-class="icon-add"
  29. @click="showNewUserMenu"
  30. @keyup.enter="showNewUserMenu"
  31. @keyup.space="showNewUserMenu" />
  32. <template #list>
  33. <NcAppNavigationNewItem id="addgroup"
  34. ref="addGroup"
  35. :edit-placeholder="t('settings', 'Enter group name')"
  36. :editable="true"
  37. :loading="loadingAddGroup"
  38. :name="t('settings', 'Add group')"
  39. @click="showAddGroupForm"
  40. @new-item="createGroup">
  41. <template #icon>
  42. <Plus :size="20" />
  43. </template>
  44. </NcAppNavigationNewItem>
  45. <NcAppNavigationItem id="everyone"
  46. :exact="true"
  47. :name="t('settings', 'Active users')"
  48. :to="{ name: 'users' }"
  49. icon="icon-contacts-dark">
  50. <template #counter>
  51. <NcCounterBubble :type="!selectedGroupDecoded ? 'highlighted' : undefined">
  52. {{ userCount }}
  53. </NcCounterBubble>
  54. </template>
  55. </NcAppNavigationItem>
  56. <NcAppNavigationItem v-if="settings.isAdmin"
  57. id="admin"
  58. :exact="true"
  59. :name="t('settings', 'Admins')"
  60. :to="{ name: 'group', params: { selectedGroup: 'admin' } }"
  61. icon="icon-user-admin">
  62. <template v-if="adminGroupMenu.count > 0" #counter>
  63. <NcCounterBubble :type="selectedGroupDecoded === 'admin' ? 'highlighted' : undefined">
  64. {{ adminGroupMenu.count }}
  65. </NcCounterBubble>
  66. </template>
  67. </NcAppNavigationItem>
  68. <!-- Hide the disabled if none, if we don't have the data (-1) show it -->
  69. <NcAppNavigationItem v-if="disabledGroupMenu.usercount > 0 || disabledGroupMenu.usercount === -1"
  70. id="disabled"
  71. :exact="true"
  72. :name="t('settings', 'Disabled users')"
  73. :to="{ name: 'group', params: { selectedGroup: 'disabled' } }"
  74. icon="icon-disabled-users">
  75. <template v-if="disabledGroupMenu.usercount > 0" #counter>
  76. <NcCounterBubble :type="selectedGroupDecoded === 'disabled' ? 'highlighted' : undefined">
  77. {{ disabledGroupMenu.usercount }}
  78. </NcCounterBubble>
  79. </template>
  80. </NcAppNavigationItem>
  81. <NcAppNavigationCaption v-if="groupList.length > 0" :name="t('settings', 'Groups')" />
  82. <GroupListItem v-for="group in groupList"
  83. :id="group.id"
  84. :key="group.id"
  85. :active="selectedGroupDecoded === group.id"
  86. :name="group.title"
  87. :count="group.count" />
  88. </template>
  89. <template #footer>
  90. <ul class="app-navigation-entry__settings">
  91. <NcAppNavigationItem :name="t('settings', 'User management settings')"
  92. @click="isDialogOpen = true">
  93. <template #icon>
  94. <Cog :size="20" />
  95. </template>
  96. </NcAppNavigationItem>
  97. </ul>
  98. </template>
  99. </NcAppNavigation>
  100. <NcAppContent>
  101. <UserList :selected-group="selectedGroupDecoded"
  102. :external-actions="externalActions" />
  103. </NcAppContent>
  104. </NcContent>
  105. <UserSettingsDialog :open.sync="isDialogOpen" />
  106. </Fragment>
  107. </template>
  108. <script>
  109. import Vue from 'vue'
  110. import VueLocalStorage from 'vue-localstorage'
  111. import { Fragment } from 'vue-frag'
  112. import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent.js'
  113. import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
  114. import NcAppNavigationCaption from '@nextcloud/vue/dist/Components/NcAppNavigationCaption.js'
  115. import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
  116. import NcAppNavigationNew from '@nextcloud/vue/dist/Components/NcAppNavigationNew.js'
  117. import NcAppNavigationNewItem from '@nextcloud/vue/dist/Components/NcAppNavigationNewItem.js'
  118. import NcContent from '@nextcloud/vue/dist/Components/NcContent.js'
  119. import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'
  120. import Cog from 'vue-material-design-icons/Cog.vue'
  121. import Plus from 'vue-material-design-icons/Plus.vue'
  122. import GroupListItem from '../components/GroupListItem.vue'
  123. import UserList from '../components/UserList.vue'
  124. import UserSettingsDialog from '../components/Users/UserSettingsDialog.vue'
  125. Vue.use(VueLocalStorage)
  126. export default {
  127. name: 'Users',
  128. components: {
  129. Cog,
  130. Fragment,
  131. GroupListItem,
  132. NcAppContent,
  133. NcAppNavigation,
  134. NcAppNavigationCaption,
  135. NcAppNavigationItem,
  136. NcAppNavigationNew,
  137. NcAppNavigationNewItem,
  138. NcContent,
  139. NcCounterBubble,
  140. Plus,
  141. UserList,
  142. UserSettingsDialog,
  143. },
  144. props: {
  145. selectedGroup: {
  146. type: String,
  147. default: null,
  148. },
  149. },
  150. data() {
  151. return {
  152. // temporary value used for multiselect change
  153. externalActions: [],
  154. loadingAddGroup: false,
  155. isDialogOpen: false,
  156. }
  157. },
  158. computed: {
  159. showConfig() {
  160. return this.$store.getters.getShowConfig
  161. },
  162. selectedGroupDecoded() {
  163. return this.selectedGroup ? decodeURIComponent(this.selectedGroup) : null
  164. },
  165. users() {
  166. return this.$store.getters.getUsers
  167. },
  168. groups() {
  169. return this.$store.getters.getGroups
  170. },
  171. usersOffset() {
  172. return this.$store.getters.getUsersOffset
  173. },
  174. usersLimit() {
  175. return this.$store.getters.getUsersLimit
  176. },
  177. userCount() {
  178. return this.$store.getters.getUserCount
  179. },
  180. settings() {
  181. return this.$store.getters.getServerData
  182. },
  183. groupList() {
  184. const groups = Array.isArray(this.groups) ? this.groups : []
  185. return groups
  186. // filter out disabled and admin
  187. .filter(group => group.id !== 'disabled' && group.id !== 'admin')
  188. .map(group => this.formatGroupMenu(group))
  189. },
  190. adminGroupMenu() {
  191. return this.formatGroupMenu(this.groups.find(group => group.id === 'admin'))
  192. },
  193. disabledGroupMenu() {
  194. return this.formatGroupMenu(this.groups.find(group => group.id === 'disabled'))
  195. },
  196. },
  197. beforeMount() {
  198. this.$store.commit('initGroups', {
  199. groups: this.$store.getters.getServerData.groups,
  200. orderBy: this.$store.getters.getServerData.sortGroups,
  201. userCount: this.$store.getters.getServerData.userCount,
  202. })
  203. this.$store.dispatch('getPasswordPolicyMinLength')
  204. },
  205. created() {
  206. // init the OCA.Settings.UserList object
  207. // and add the registerAction method
  208. Object.assign(OCA, {
  209. Settings: {
  210. UserList: {
  211. registerAction: this.registerAction,
  212. },
  213. },
  214. })
  215. },
  216. methods: {
  217. showNewUserMenu() {
  218. this.$store.commit('setShowConfig', {
  219. key: 'showNewUserForm',
  220. value: true,
  221. })
  222. },
  223. /**
  224. * Register a new action for the user menu
  225. *
  226. * @param {string} icon the icon class
  227. * @param {string} text the text to display
  228. * @param {Function} action the function to run
  229. * @return {Array}
  230. */
  231. registerAction(icon, text, action) {
  232. this.externalActions.push({
  233. icon,
  234. text,
  235. action,
  236. })
  237. return this.externalActions
  238. },
  239. /**
  240. * Create a new group
  241. *
  242. * @param {string} gid The group id
  243. */
  244. async createGroup(gid) {
  245. // group is not valid
  246. if (gid.trim() === '') {
  247. return
  248. }
  249. try {
  250. this.loadingAddGroup = true
  251. await this.$store.dispatch('addGroup', gid.trim())
  252. this.hideAddGroupForm()
  253. await this.$router.push({
  254. name: 'group',
  255. params: {
  256. selectedGroup: encodeURIComponent(gid.trim()),
  257. },
  258. })
  259. } catch {
  260. this.showAddGroupForm()
  261. } finally {
  262. this.loadingAddGroup = false
  263. }
  264. },
  265. showAddGroupForm() {
  266. this.$refs.addGroup.newItemActive = true
  267. this.$nextTick(() => {
  268. this.$refs.addGroup.$refs.newItemInput.focusInput()
  269. })
  270. },
  271. hideAddGroupForm() {
  272. this.$refs.addGroup.newItemActive = false
  273. this.$refs.addGroup.newItemValue = ''
  274. },
  275. /**
  276. * Format a group to a menu entry
  277. *
  278. * @param {object} group the group
  279. * @return {object}
  280. */
  281. formatGroupMenu(group) {
  282. const item = {}
  283. if (typeof group === 'undefined') {
  284. return {}
  285. }
  286. item.id = group.id
  287. item.title = group.name
  288. item.usercount = group.usercount
  289. // users count for all groups
  290. if (group.usercount - group.disabled > 0) {
  291. item.count = group.usercount - group.disabled
  292. }
  293. return item
  294. },
  295. },
  296. }
  297. </script>
  298. <style lang="scss" scoped>
  299. .app-content {
  300. // Virtual list needs to be full height and is scrollable
  301. display: flex;
  302. overflow: hidden;
  303. flex-direction: column;
  304. max-height: 100%;
  305. }
  306. // force hiding the editing action for the add group entry
  307. .app-navigation__list #addgroup::v-deep .app-navigation-entry__utils {
  308. display: none;
  309. }
  310. .app-navigation-entry__settings {
  311. height: auto !important;
  312. // Prevent shrinking or growing
  313. flex: 0 0 auto;
  314. }
  315. </style>