You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Users.vue 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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. <AppContent app-name="settings" :navigation-class="{ 'icon-loading': loading }">
  24. <template #navigation>
  25. <AppNavigationNew button-id="new-user-button" :text="t('settings','New user')" button-class="icon-add" @click="toggleNewUserMenu" />
  26. <ul id="usergrouplist">
  27. <AppNavigationItem v-for="item in menu" :key="item.key" :item="item" />
  28. </ul>
  29. <AppNavigationSettings>
  30. <div>
  31. <p>{{t('settings', 'Default quota:')}}</p>
  32. <multiselect :value="defaultQuota" :options="quotaOptions"
  33. tag-placeholder="create" :placeholder="t('settings', 'Select default quota')"
  34. label="label" track-by="id" class="multiselect-vue"
  35. :allowEmpty="false" :taggable="true"
  36. @tag="validateQuota" @input="setDefaultQuota">
  37. </multiselect>
  38. </div>
  39. <div>
  40. <input type="checkbox" id="showLanguages" class="checkbox" v-model="showLanguages">
  41. <label for="showLanguages">{{t('settings', 'Show Languages')}}</label>
  42. </div>
  43. <div>
  44. <input type="checkbox" id="showLastLogin" class="checkbox" v-model="showLastLogin">
  45. <label for="showLastLogin">{{t('settings', 'Show last login')}}</label>
  46. </div>
  47. <div>
  48. <input type="checkbox" id="showUserBackend" class="checkbox" v-model="showUserBackend">
  49. <label for="showUserBackend">{{t('settings', 'Show user backend')}}</label>
  50. </div>
  51. <div>
  52. <input type="checkbox" id="showStoragePath" class="checkbox" v-model="showStoragePath">
  53. <label for="showStoragePath">{{t('settings', 'Show storage path')}}</label>
  54. </div>
  55. </AppNavigationSettings>
  56. </template>
  57. <template #content>
  58. <user-list :users="users" :showConfig="showConfig" :selectedGroup="selectedGroup" :externalActions="externalActions" />
  59. </template>
  60. </AppContent>
  61. </template>
  62. <script>
  63. import {
  64. AppContent,
  65. AppNavigationItem,
  66. AppNavigationNew,
  67. AppNavigationSettings,
  68. } from 'nextcloud-vue';
  69. import userList from '../components/userList';
  70. import Vue from 'vue';
  71. import VueLocalStorage from 'vue-localstorage'
  72. import Multiselect from 'vue-multiselect';
  73. import api from '../store/api';
  74. Vue.use(VueLocalStorage)
  75. export default {
  76. name: 'Users',
  77. props: ['selectedGroup'],
  78. components: {
  79. AppContent,
  80. AppNavigationItem,
  81. AppNavigationNew,
  82. AppNavigationSettings,
  83. userList,
  84. Multiselect,
  85. },
  86. beforeMount() {
  87. this.$store.commit('initGroups', {
  88. groups: this.$store.getters.getServerData.groups,
  89. orderBy: this.$store.getters.getServerData.sortGroups,
  90. userCount: this.$store.getters.getServerData.userCount
  91. });
  92. this.$store.dispatch('getPasswordPolicyMinLength');
  93. },
  94. created() {
  95. // init the OCA.Settings.UserList object
  96. // and add the registerAction method
  97. Object.assign(OCA, {
  98. Settings: {
  99. UserList: {
  100. registerAction: this.registerAction
  101. }
  102. }
  103. });
  104. },
  105. data() {
  106. return {
  107. // default quota is set to unlimited
  108. unlimitedQuota: {id: 'none', label: t('settings', 'Unlimited')},
  109. // temporary value used for multiselect change
  110. selectedQuota: false,
  111. externalActions: [],
  112. showAddGroupEntry: false,
  113. loadingAddGroup: false,
  114. showConfig: {
  115. showStoragePath: false,
  116. showUserBackend: false,
  117. showLastLogin: false,
  118. showNewUserForm: false,
  119. showLanguages: false
  120. }
  121. }
  122. },
  123. methods: {
  124. toggleNewUserMenu() {
  125. this.showConfig.showNewUserForm = !this.showConfig.showNewUserForm;
  126. if (this.showConfig.showNewUserForm) {
  127. Vue.nextTick(() => {
  128. window.newusername.focus();
  129. });
  130. }
  131. },
  132. getLocalstorage(key) {
  133. // force initialization
  134. let localConfig = this.$localStorage.get(key);
  135. // if localstorage is null, fallback to original values
  136. this.showConfig[key] = localConfig !== null ? localConfig === 'true' : this.showConfig[key];
  137. return this.showConfig[key];
  138. },
  139. setLocalStorage(key, status) {
  140. this.showConfig[key] = status;
  141. this.$localStorage.set(key, status);
  142. return status;
  143. },
  144. removeGroup(groupid) {
  145. let self = this;
  146. // TODO migrate to a vue js confirm dialog component
  147. OC.dialogs.confirm(
  148. t('settings', 'You are about to remove the group {group}. The users will NOT be deleted.', {group: groupid}),
  149. t('settings','Please confirm the group removal '),
  150. function (success) {
  151. if (success) {
  152. self.$store.dispatch('removeGroup', groupid);
  153. }
  154. }
  155. );
  156. },
  157. /**
  158. * Dispatch default quota set request
  159. *
  160. * @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
  161. * @returns {string}
  162. */
  163. setDefaultQuota(quota = 'none') {
  164. this.$store.dispatch('setAppConfig', {
  165. app: 'files',
  166. key: 'default_quota',
  167. // ensure we only send the preset id
  168. value: quota.id ? quota.id : quota
  169. }).then(() => {
  170. if (typeof quota !== 'object') {
  171. quota = {id: quota, label: quota};
  172. }
  173. this.defaultQuota = quota;
  174. });
  175. },
  176. /**
  177. * Validate quota string to make sure it's a valid human file size
  178. *
  179. * @param {string} quota Quota in readable format '5 GB'
  180. * @returns {Promise|boolean}
  181. */
  182. validateQuota(quota) {
  183. // only used for new presets sent through @Tag
  184. let validQuota = OC.Util.computerFileSize(quota);
  185. if (validQuota === 0) {
  186. return this.setDefaultQuota('none');
  187. } else if (validQuota !== null) {
  188. // unify format output
  189. return this.setDefaultQuota(OC.Util.humanFileSize(OC.Util.computerFileSize(quota)));
  190. }
  191. // if no valid do not change
  192. return false;
  193. },
  194. /**
  195. * Register a new action for the user menu
  196. *
  197. * @param {string} icon the icon class
  198. * @param {string} text the text to display
  199. * @param {function} action the function to run
  200. */
  201. registerAction(icon, text, action) {
  202. this.externalActions.push({
  203. icon: icon,
  204. text: text,
  205. action: action
  206. });
  207. return this.externalActions;
  208. },
  209. /**
  210. * Create a new group
  211. *
  212. * @param {Object} event The form submit event
  213. */
  214. createGroup(event) {
  215. let gid = event.target[0].value;
  216. this.loadingAddGroup = true;
  217. this.$store.dispatch('addGroup', gid)
  218. .then(() => {
  219. this.showAddGroupEntry = false;
  220. this.loadingAddGroup = false;
  221. })
  222. .catch(() => {
  223. this.loadingAddGroup = false;
  224. });
  225. }
  226. },
  227. computed: {
  228. users() {
  229. return this.$store.getters.getUsers;
  230. },
  231. loading() {
  232. return Object.keys(this.users).length === 0;
  233. },
  234. usersOffset() {
  235. return this.$store.getters.getUsersOffset;
  236. },
  237. usersLimit() {
  238. return this.$store.getters.getUsersLimit;
  239. },
  240. // Local settings
  241. showLanguages: {
  242. get: function() {return this.getLocalstorage('showLanguages')},
  243. set: function(status) {
  244. this.setLocalStorage('showLanguages', status);
  245. }
  246. },
  247. showLastLogin: {
  248. get: function() {return this.getLocalstorage('showLastLogin')},
  249. set: function(status) {
  250. this.setLocalStorage('showLastLogin', status);
  251. }
  252. },
  253. showUserBackend: {
  254. get: function() {return this.getLocalstorage('showUserBackend')},
  255. set: function(status) {
  256. this.setLocalStorage('showUserBackend', status);
  257. }
  258. },
  259. showStoragePath: {
  260. get: function() {return this.getLocalstorage('showStoragePath')},
  261. set: function(status) {
  262. this.setLocalStorage('showStoragePath', status);
  263. }
  264. },
  265. userCount() {
  266. return this.$store.getters.getUserCount;
  267. },
  268. settings() {
  269. return this.$store.getters.getServerData;
  270. },
  271. // default quota
  272. quotaOptions() {
  273. // convert the preset array into objects
  274. let quotaPreset = this.settings.quotaPreset.reduce((acc, cur) => acc.concat({id:cur, label:cur}), []);
  275. // add default presets
  276. quotaPreset.unshift(this.unlimitedQuota);
  277. return quotaPreset;
  278. },
  279. // mapping saved values to objects
  280. defaultQuota: {
  281. get: function() {
  282. if (this.selectedQuota !== false) {
  283. return this.selectedQuota;
  284. }
  285. if (OC.Util.computerFileSize(this.settings.defaultQuota) > 0) {
  286. // if value is valid, let's map the quotaOptions or return custom quota
  287. return {id:this.settings.defaultQuota, label:this.settings.defaultQuota};
  288. }
  289. return this.unlimitedQuota; // unlimited
  290. },
  291. set: function(quota) {
  292. this.selectedQuota = quota;
  293. }
  294. },
  295. // BUILD APP NAVIGATION MENU OBJECT
  296. menu() {
  297. // Data provided php side
  298. let self = this;
  299. let groups = this.$store.getters.getGroups;
  300. groups = Array.isArray(groups) ? groups : [];
  301. // Map groups
  302. groups = groups.map(group => {
  303. let item = {};
  304. item.id = group.id.replace(' ', '_');
  305. item.key = item.id;
  306. item.utils = {}
  307. // router link to
  308. item.router = {
  309. name: 'group',
  310. params: {selectedGroup: group.id}
  311. };
  312. // group name
  313. item.text = group.name;
  314. item.title = group.name;
  315. // users count for all groups
  316. if (group.usercount - group.disabled > 0 || group.usercount === -1) {
  317. item.utils.counter = group.usercount - group.disabled;
  318. }
  319. if (item.id !== 'admin' && item.id !== 'disabled' && this.settings.isAdmin) {
  320. // add delete button on real groups
  321. item.utils.actions = [{
  322. icon: 'icon-delete',
  323. text: t('settings', 'Remove group'),
  324. action: function() {
  325. self.removeGroup(group.id)
  326. }
  327. }];
  328. };
  329. return item;
  330. });
  331. // Every item is added on top of the array, so we're going backward
  332. // Groups, separator, disabled, admin, everyone
  333. // Add separator
  334. let realGroups = groups.find((group) => {return group.id !== 'disabled' && group.id !== 'admin'});
  335. realGroups = typeof realGroups === 'undefined' ? [] : realGroups;
  336. realGroups = Array.isArray(realGroups) ? realGroups : [realGroups];
  337. if (realGroups.length > 0) {
  338. let separator = {
  339. caption: true,
  340. text: t('settings', 'Groups')
  341. };
  342. groups.unshift(separator);
  343. }
  344. // Adjust admin and disabled groups
  345. let adminGroup = groups.find(group => group.id == 'admin');
  346. let disabledGroup = groups.find(group => group.id == 'disabled');
  347. // filter out admin and disabled
  348. groups = groups.filter(group => ['admin', 'disabled'].indexOf(group.id) === -1);
  349. if (adminGroup && adminGroup.text) {
  350. adminGroup.text = t('settings', 'Admins'); // rename admin group
  351. adminGroup.icon = 'icon-user-admin'; // set icon
  352. groups.unshift(adminGroup); // add admin group if present
  353. }
  354. if (disabledGroup && disabledGroup.text) {
  355. disabledGroup.text = t('settings', 'Disabled users'); // rename disabled group
  356. disabledGroup.icon = 'icon-disabled-users'; // set icon
  357. if (disabledGroup.utils && (
  358. disabledGroup.utils.counter > 0 // add disabled if not empty
  359. || disabledGroup.utils.counter === -1) // add disabled if ldap enabled
  360. ) {
  361. groups.unshift(disabledGroup);
  362. }
  363. }
  364. // Add everyone group
  365. let everyoneGroup = {
  366. id: 'everyone',
  367. key: 'everyone',
  368. icon: 'icon-contacts-dark',
  369. router: {name:'users'},
  370. text: t('settings', 'Everyone'),
  371. };
  372. // users count
  373. if (this.userCount > 0) {
  374. Vue.set(everyoneGroup, 'utils', {
  375. counter: this.userCount
  376. });
  377. }
  378. groups.unshift(everyoneGroup);
  379. let addGroup = {
  380. id: 'addgroup',
  381. key: 'addgroup',
  382. icon: 'icon-add',
  383. text: t('settings', 'Add group'),
  384. classes: this.loadingAddGroup ? 'icon-loading-small' : ''
  385. };
  386. if (this.showAddGroupEntry) {
  387. Vue.set(addGroup, 'edit', {
  388. text: t('settings', 'Add group'),
  389. action: this.createGroup,
  390. reset: function() {
  391. self.showAddGroupEntry = false
  392. }
  393. });
  394. addGroup.classes = 'editing';
  395. } else {
  396. Vue.set(addGroup, 'action', function() {
  397. self.showAddGroupEntry = true
  398. })
  399. }
  400. groups.unshift(addGroup);
  401. return groups;
  402. },
  403. }
  404. }
  405. </script>