Settings to vuejs
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
This commit is contained in:
parent
7de6c06c66
commit
c8f670dd8f
42 changed files with 2654 additions and 6609 deletions
|
@ -67,6 +67,13 @@ div[contenteditable=true],
|
|||
cursor: default;
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:required {
|
||||
box-shadow: none;
|
||||
}
|
||||
&:invalid {
|
||||
box-shadow: none !important;
|
||||
border-color: $color-error;
|
||||
}
|
||||
/* Primary action button, use sparingly */
|
||||
&.primary {
|
||||
background-color: $color-primary-element;
|
||||
|
@ -216,7 +223,8 @@ input {
|
|||
margin-left: -8px !important;
|
||||
border-left-color: transparent !important;
|
||||
border-radius: 0 $border-radius $border-radius 0 !important;
|
||||
background-clip: padding-box; /* Avoid background under border */
|
||||
background-clip: padding-box;
|
||||
/* Avoid background under border */
|
||||
background-color: $color-main-background !important;
|
||||
opacity: 1;
|
||||
width: 34px;
|
||||
|
@ -227,6 +235,7 @@ input {
|
|||
background-image: url('../img/actions/confirm-fade.svg?v=2') !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* only show confirm borders if input is not focused */
|
||||
&:not(:active):not(:hover):not(:focus){
|
||||
+ .icon-confirm {
|
||||
|
@ -244,14 +253,19 @@ input {
|
|||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
&:invalid {
|
||||
+ .icon-confirm {
|
||||
border-color: $color-error;
|
||||
}
|
||||
}
|
||||
+ .icon-confirm {
|
||||
border-color: $color-primary-element !important;
|
||||
border-left-color: transparent !important;
|
||||
z-index: 2; /* above previous input */
|
||||
/* above previous input */
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -606,6 +620,169 @@ input {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/* Vue multiselect */
|
||||
.multiselect.multiselect-vue {
|
||||
margin: 1px 2px;
|
||||
padding: 0 !important;
|
||||
display: inline-block;
|
||||
min-width: 160px;
|
||||
width: 160px;
|
||||
position: relative;
|
||||
background-color: $color-main-background;
|
||||
&.multiselect--active {
|
||||
/* Opened: force display the input */
|
||||
input.multiselect__input {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
&.multiselect--disabled {
|
||||
background-color: nc-darken($color-main-background, 8%);
|
||||
}
|
||||
.multiselect__tags {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
border: 1px solid nc-darken($color-main-background, 14%);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
border-radius: 3px;
|
||||
height: 38px;
|
||||
/* tag wrapper */
|
||||
.multiselect__tags-wrap {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
padding: 3px 5px;
|
||||
/* no tags or simple select? Show input directly
|
||||
input is used to display single value */
|
||||
&:empty ~ input.multiselect__input {
|
||||
opacity: 1 !important;
|
||||
/* hide default empty text, show input instead */
|
||||
+ span:not(.multiselect__single) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/* selected tag */
|
||||
.multiselect__tag {
|
||||
flex: 0 0 auto;
|
||||
line-height: 20px;
|
||||
padding: 1px 5px;
|
||||
background-image: none;
|
||||
color: nc-lighten($color-main-text, 33%);
|
||||
border: 1px solid nc-darken($color-main-background, 14%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
/* Single select default value */
|
||||
.multiselect__single {
|
||||
padding: 8px 10px;
|
||||
flex: 0 0 100%;
|
||||
z-index: 5;
|
||||
background-color: $color-main-background;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* displayed text if tag limit reached */
|
||||
.multiselect__strong {
|
||||
flex: 0 0 auto;
|
||||
line-height: 20px;
|
||||
color: nc-lighten($color-main-text, 33%);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
opacity: .7;
|
||||
}
|
||||
/* default multiselect input for search and placeholder */
|
||||
input.multiselect__input {
|
||||
width: 100% !important;
|
||||
position: absolute !important;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
/* let's leave it on top of tags but hide it */
|
||||
height: 100%;
|
||||
border: none;
|
||||
/* override hide to force show the placeholder */
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
/* results wrapper */
|
||||
.multiselect__content-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
margin-top: -1px;
|
||||
border: 1px solid nc-darken($color-main-background, 14%);
|
||||
background: $color-main-background;
|
||||
z-index: 50;
|
||||
.multiselect__content {
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
}
|
||||
li {
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
&,
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
> span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
min-height: 1em;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: transparent !important;
|
||||
color: nc-lighten($color-main-text, 33%);
|
||||
width: 100%;
|
||||
/* selected checkmark icon */
|
||||
&::before {
|
||||
content: ' ';
|
||||
background-image: url('../img/actions/checkmark.svg?v=1');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
display: block;
|
||||
opacity: 0.5;
|
||||
margin-right: 5px;
|
||||
visibility: hidden;
|
||||
}
|
||||
/* add the prop tag-placeholder="create" to add the +
|
||||
* icon on top of an unknown-and-ready-to-be-created entry
|
||||
*/
|
||||
&[data-select='create'] {
|
||||
&::before {
|
||||
background-image: url('../img/actions/add.svg?v=1');
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
&.multiselect__option--highlight {
|
||||
color: $color-main-text;
|
||||
}
|
||||
&.multiselect__option--selected {
|
||||
&::before {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Progressbar */
|
||||
progress {
|
||||
display: block;
|
||||
|
|
6
settings/.babelrc
Normal file
6
settings/.babelrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"presets": [
|
||||
["env", { "modules": false }],
|
||||
"stage-3"
|
||||
]
|
||||
}
|
9
settings/.editorconfig
Normal file
9
settings/.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
12
settings/.gitignore
vendored
Normal file
12
settings/.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
File diff suppressed because it is too large
Load diff
18
settings/README.md
Normal file
18
settings/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# settings
|
||||
|
||||
> A Vue.js project
|
||||
|
||||
## Build Setup
|
||||
|
||||
``` bash
|
||||
# install dependencies
|
||||
npm install
|
||||
|
||||
# serve with hot reload at localhost:8080
|
||||
npm run dev
|
||||
|
||||
# build for production with minification
|
||||
npm run build
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader).
|
|
@ -675,101 +675,6 @@ span.usersLastLoginTooltip {
|
|||
}
|
||||
}
|
||||
|
||||
tr:hover > td {
|
||||
&.password > span, &.displayName > span, &.mailAddress > span {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
&.password > img, &.displayName > img, &.mailAddress > img {
|
||||
visibility: visible;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
td.userActions {
|
||||
.toggleUserActions {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
position: relative;
|
||||
.action {
|
||||
display: block;
|
||||
padding: 14px;
|
||||
opacity: 0.5;
|
||||
.icon-more {
|
||||
display: inline-block;
|
||||
}
|
||||
&:hover,
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.recoveryPassword {
|
||||
left: 50em;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
input#recoveryPassword {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
#controls select.quota {
|
||||
margin: 3px;
|
||||
margin-right: 10px;
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
#userlist td.quota {
|
||||
position: relative;
|
||||
width: 10em;
|
||||
progress.quota-user-progress {
|
||||
position: absolute;
|
||||
width: calc(10em + 0px);
|
||||
margin-top: -7px;
|
||||
z-index: 0;
|
||||
margin-left: 1px;
|
||||
height: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
&.quota-user {
|
||||
width: 10em;
|
||||
height: 34px;
|
||||
z-index: 50;
|
||||
position: relative;
|
||||
}
|
||||
+ progress.quota-user-progress {
|
||||
position: absolute;
|
||||
width: calc(10em + 0px);
|
||||
margin-top: -7px;
|
||||
z-index: 0;
|
||||
margin-left: 1px;
|
||||
height: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
input.userFilter {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#newusergroups + input[type='submit'] {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
#headerGroups, #headerSubAdmins, #headerQuota {
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
#headerAvatar {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
/* used to highlight a user row in red */
|
||||
|
||||
#userlist tr.row-warning {
|
||||
|
@ -1350,3 +1255,174 @@ doesnotexist:-o-prefocus, .strengthify-wrapper {
|
|||
margin-top: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* USERS LIST -------------------------------------------------------------- */
|
||||
#body-settings {
|
||||
#app-navigation {
|
||||
/* Hack to override the javascript orderBy */
|
||||
#usergrouplist > li {
|
||||
order: 4;
|
||||
&#_everyone {
|
||||
order:1;
|
||||
}
|
||||
&#admin {
|
||||
order:2;
|
||||
}
|
||||
&#_disabled {
|
||||
order:3;
|
||||
}
|
||||
}
|
||||
}
|
||||
$grid-row-height: 46px;
|
||||
#app-content.user-list-grid {
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-auto-rows: $grid-row-height;
|
||||
grid-column-gap: 20px;
|
||||
.row {
|
||||
display: grid;
|
||||
grid-row-start: span 1;
|
||||
align-items: center;
|
||||
/* let's define the column until storage path,
|
||||
what follows will be manually defined */
|
||||
grid-template-columns: 44px;
|
||||
grid-auto-columns: min-content;
|
||||
border-top: $color-border 1px solid;
|
||||
.name,
|
||||
.displayName,
|
||||
.password {
|
||||
width: 150px;
|
||||
}
|
||||
.mailAddress{
|
||||
width: 200px;
|
||||
}
|
||||
.groups,
|
||||
.subadmins,
|
||||
.quota {
|
||||
width: 170px;
|
||||
}
|
||||
.storageLocation {
|
||||
width: 250px;
|
||||
}
|
||||
.userBackend,
|
||||
.lastLogin,
|
||||
.userActions {
|
||||
width: 100px;
|
||||
}
|
||||
&#grid-header,
|
||||
&#new-user {
|
||||
position: sticky;
|
||||
align-self: normal;
|
||||
background-color: $color-main-background;
|
||||
z-index: 55; /* above multiselect */
|
||||
top: 0;
|
||||
&.sticky {
|
||||
box-shadow: 0 -2px 10px 1px $color-box-shadow;
|
||||
}
|
||||
}
|
||||
&#grid-header {
|
||||
color: nc-lighten($color-main-text, 60%);
|
||||
z-index: 60; /* above new-user */
|
||||
}
|
||||
&#new-user {
|
||||
top: $grid-row-height;
|
||||
}
|
||||
&:hover {
|
||||
input:not([type='submit']):not(:focus):not(:active) {
|
||||
border-color: nc-darken($color-main-background, 14%) !important;
|
||||
}
|
||||
}
|
||||
> div,
|
||||
> form {
|
||||
grid-row: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: nc-lighten($color-main-text, 33%);
|
||||
position: relative;
|
||||
> input:not(:focus):not(:active) {
|
||||
border-color: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
> input:focus, >input:active {
|
||||
+ .icon-confirm {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
&:not(.userActions) > input:not([type='submit']) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
&.quota {
|
||||
.multiselect--active + progress {
|
||||
display: none;
|
||||
}
|
||||
progress {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
height: 3px;
|
||||
}
|
||||
}
|
||||
.icon-confirm {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
cursor: pointer;
|
||||
&:not(:active) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.avatar {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin: 6px;
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.toggleUserActions {
|
||||
position: relative;
|
||||
.icon-more {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
opacity: .5;
|
||||
cursor: pointer;
|
||||
:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
}
|
||||
.v-select {
|
||||
&.open .selected-tag-wrap {
|
||||
display: none;
|
||||
}
|
||||
.dropdown-toggle .selected-tag {
|
||||
padding-right: 5px;
|
||||
.close {
|
||||
/* no delete on tags*/
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.dropdown-menu li a .icon-add {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
opacity: .5;
|
||||
left: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.infinite-loading-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.users-list-end {
|
||||
opacity: .5;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
11
settings/index.html
Normal file
11
settings/index.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>settings</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/dist/build.js"></script>
|
||||
</body>
|
||||
</html>
|
568
settings/js/main.js
Normal file
568
settings/js/main.js
Normal file
File diff suppressed because one or more lines are too long
1
settings/js/main.js.map
Normal file
1
settings/js/main.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,213 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* takes care of deleting things represented by an ID
|
||||
*
|
||||
* @class
|
||||
* @param {string} endpoint the corresponding ajax PHP script. Currently limited
|
||||
* to settings - ajax path.
|
||||
* @param {string} paramID the by the script expected parameter name holding the
|
||||
* ID of the object to delete
|
||||
* @param {markCallback} markCallback function to be called after successfully
|
||||
* marking the object for deletion.
|
||||
* @param {removeCallback} removeCallback the function to be called after
|
||||
* successful delete.
|
||||
*/
|
||||
|
||||
/* globals escapeHTML */
|
||||
|
||||
function DeleteHandler(endpoint, paramID, markCallback, removeCallback) {
|
||||
this.oidToDelete = false;
|
||||
this.canceled = false;
|
||||
|
||||
this.ajaxEndpoint = endpoint;
|
||||
this.ajaxParamID = paramID;
|
||||
|
||||
this.markCallback = markCallback;
|
||||
this.removeCallback = removeCallback;
|
||||
this.undoCallback = false;
|
||||
|
||||
this.notifier = false;
|
||||
this.notificationDataID = false;
|
||||
this.notificationMessage = false;
|
||||
this.notificationPlaceholder = '%oid';
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of milliseconds after which the operation is performed.
|
||||
*/
|
||||
DeleteHandler.TIMEOUT_MS = 7000;
|
||||
|
||||
/**
|
||||
* Timer after which the action will be performed anyway.
|
||||
*/
|
||||
DeleteHandler.prototype._timeout = null;
|
||||
|
||||
/**
|
||||
* The function to be called after successfully marking the object for deletion
|
||||
* @callback markCallback
|
||||
* @param {string} oid the ID of the specific user or group
|
||||
*/
|
||||
|
||||
/**
|
||||
* The function to be called after successful delete. The id of the object will
|
||||
* be passed as argument. Unsuccessful operations will display an error using
|
||||
* OC.dialogs, no callback is fired.
|
||||
* @callback removeCallback
|
||||
* @param {string} oid the ID of the specific user or group
|
||||
*/
|
||||
|
||||
/**
|
||||
* This callback is fired after "undo" was clicked so the consumer can update
|
||||
* the web interface
|
||||
* @callback undoCallback
|
||||
* @param {string} oid the ID of the specific user or group
|
||||
*/
|
||||
|
||||
/**
|
||||
* enabled the notification system. Required for undo UI.
|
||||
*
|
||||
* @param {object} notifier Usually OC.Notification
|
||||
* @param {string} dataID an identifier for the notifier, e.g. 'deleteuser'
|
||||
* @param {string} message the message that should be shown upon delete. %oid
|
||||
* will be replaced with the affected id of the item to be deleted
|
||||
* @param {undoCallback} undoCallback called after "undo" was clicked
|
||||
*/
|
||||
DeleteHandler.prototype.setNotification = function(notifier, dataID, message, undoCallback) {
|
||||
this.notifier = notifier;
|
||||
this.notificationDataID = dataID;
|
||||
this.notificationMessage = message;
|
||||
this.undoCallback = undoCallback;
|
||||
|
||||
var dh = this;
|
||||
|
||||
$('#notification')
|
||||
.off('click.deleteHandler_' + dataID)
|
||||
.on('click.deleteHandler_' + dataID, '.undo', function () {
|
||||
if ($('#notification').data(dh.notificationDataID)) {
|
||||
var oid = dh.oidToDelete;
|
||||
dh.cancel();
|
||||
if(typeof dh.undoCallback !== 'undefined') {
|
||||
dh.undoCallback(oid);
|
||||
}
|
||||
}
|
||||
dh.notifier.hide();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* shows the Undo Notification (if configured)
|
||||
*/
|
||||
DeleteHandler.prototype.showNotification = function() {
|
||||
if(this.notifier !== false) {
|
||||
if(!this.notifier.isHidden()) {
|
||||
this.hideNotification();
|
||||
}
|
||||
$('#notification').data(this.notificationDataID, true);
|
||||
var msg = this.notificationMessage.replace(
|
||||
this.notificationPlaceholder, escapeHTML(this.oidToDelete));
|
||||
this.notifier.showHtml(msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* hides the Undo Notification
|
||||
*/
|
||||
DeleteHandler.prototype.hideNotification = function() {
|
||||
if(this.notifier !== false) {
|
||||
$('#notification').removeData(this.notificationDataID);
|
||||
this.notifier.hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* initializes the delete operation for a given object id
|
||||
*
|
||||
* @param {string} oid the object id
|
||||
*/
|
||||
DeleteHandler.prototype.mark = function(oid) {
|
||||
if(this.oidToDelete !== false) {
|
||||
// passing true to avoid hiding the notification
|
||||
// twice and causing the second notification
|
||||
// to disappear immediately
|
||||
this.deleteEntry(true);
|
||||
}
|
||||
this.oidToDelete = oid;
|
||||
this.canceled = false;
|
||||
this.markCallback(oid);
|
||||
this.showNotification();
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
if (DeleteHandler.TIMEOUT_MS > 0) {
|
||||
this._timeout = window.setTimeout(
|
||||
_.bind(this.deleteEntry, this),
|
||||
DeleteHandler.TIMEOUT_MS
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* cancels a delete operation
|
||||
*/
|
||||
DeleteHandler.prototype.cancel = function() {
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
|
||||
this.canceled = true;
|
||||
this.oidToDelete = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* executes a delete operation. Requires that the operation has been
|
||||
* initialized by mark(). On error, it will show a message via
|
||||
* OC.dialogs.alert. On success, a callback is fired so that the client can
|
||||
* update the web interface accordingly.
|
||||
*
|
||||
* @param {boolean} [keepNotification] true to keep the notification, false to hide
|
||||
* it, defaults to false
|
||||
*/
|
||||
DeleteHandler.prototype.deleteEntry = function(keepNotification) {
|
||||
var deferred = $.Deferred();
|
||||
if(this.canceled || this.oidToDelete === false) {
|
||||
return deferred.resolve().promise();
|
||||
}
|
||||
|
||||
var dh = this;
|
||||
if(!keepNotification && $('#notification').data(this.notificationDataID) === true) {
|
||||
dh.hideNotification();
|
||||
}
|
||||
|
||||
if (this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
|
||||
var payload = {};
|
||||
payload[dh.ajaxParamID] = dh.oidToDelete;
|
||||
return $.ajax({
|
||||
type: 'DELETE',
|
||||
url: OC.generateUrl(dh.ajaxEndpoint+'/{oid}',{oid: this.oidToDelete}),
|
||||
// FIXME: do not use synchronous ajax calls as they block the browser !
|
||||
async: false,
|
||||
success: function (result) {
|
||||
// Remove undo option, & remove user from table
|
||||
|
||||
//TODO: following line
|
||||
dh.removeCallback(dh.oidToDelete);
|
||||
dh.canceled = true;
|
||||
},
|
||||
error: function (jqXHR) {
|
||||
OC.dialogs.alert(jqXHR.responseJSON.data.message, t('settings', 'Unable to delete {objName}', {objName: dh.oidToDelete}));
|
||||
dh.undoCallback(dh.oidToDelete);
|
||||
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,78 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief this object takes care of the filter functionality on the user
|
||||
* management page
|
||||
* @param {UserList} userList the UserList object
|
||||
* @param {GroupList} groupList the GroupList object
|
||||
*/
|
||||
function UserManagementFilter (userList, groupList) {
|
||||
this.userList = userList;
|
||||
this.groupList = groupList;
|
||||
this.oldFilter = '';
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief sets up when the filter action shall be triggered
|
||||
*/
|
||||
UserManagementFilter.prototype.init = function () {
|
||||
OC.Plugins.register('OCA.Search', this);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief the filter action needs to be done, here the accurate steps are being
|
||||
* taken care of
|
||||
*/
|
||||
UserManagementFilter.prototype.run = _.debounce(function (filter) {
|
||||
if (filter === this.oldFilter) {
|
||||
return;
|
||||
}
|
||||
this.oldFilter = filter;
|
||||
this.userList.filter = filter;
|
||||
this.userList.empty();
|
||||
this.userList.update(GroupList.getCurrentGID());
|
||||
if (this.groupList.filterGroups) {
|
||||
// user counts are being updated nevertheless
|
||||
this.groupList.empty();
|
||||
}
|
||||
this.groupList.update();
|
||||
},
|
||||
300
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief returns the filter String
|
||||
* @returns string
|
||||
*/
|
||||
UserManagementFilter.prototype.getPattern = function () {
|
||||
var input = this.filterInput.val(),
|
||||
html = $('html'),
|
||||
isIE8or9 = html.hasClass('lte9');
|
||||
// FIXME - TODO - once support for IE8 and IE9 is dropped
|
||||
if (isIE8or9 && input == this.filterInput.attr('placeholder')) {
|
||||
input = '';
|
||||
}
|
||||
return input;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief adds reset functionality to an HTML element
|
||||
* @param jQuery the jQuery representation of that element
|
||||
*/
|
||||
UserManagementFilter.prototype.addResetButton = function (button) {
|
||||
var umf = this;
|
||||
button.click(function () {
|
||||
umf.filterInput.val('');
|
||||
umf.run();
|
||||
});
|
||||
};
|
||||
|
||||
UserManagementFilter.prototype.attach = function (search) {
|
||||
search.setFilter('settings', this.run.bind(this));
|
||||
};
|
|
@ -1,385 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014, Raghu Nayyar <beingminimal@gmail.com>
|
||||
* Copyright (c) 2014, Arthur Schiwon <blizzz@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
/* globals escapeHTML, UserList, DeleteHandler */
|
||||
|
||||
var $userGroupList,
|
||||
$sortGroupBy;
|
||||
|
||||
var GroupList;
|
||||
GroupList = {
|
||||
activeGID: '',
|
||||
everyoneGID: '_everyone',
|
||||
filter: '',
|
||||
filterGroups: false,
|
||||
|
||||
addGroup: function (gid, displayName, usercount) {
|
||||
if (_.isUndefined(displayName)) {
|
||||
displayName = gid;
|
||||
}
|
||||
var $li = $userGroupList.find('.isgroup:last-child').clone();
|
||||
$li
|
||||
.data('gid', gid)
|
||||
.find('.groupname').text(displayName);
|
||||
GroupList.setUserCount($li, usercount);
|
||||
|
||||
$li.appendTo($userGroupList);
|
||||
|
||||
GroupList.sortGroups();
|
||||
|
||||
return $li;
|
||||
},
|
||||
|
||||
setUserCount: function (groupLiElement, usercount) {
|
||||
if ($sortGroupBy !== 1) {
|
||||
// If we don't sort by group count we don't display them either
|
||||
return;
|
||||
}
|
||||
|
||||
var $groupLiElement = $(groupLiElement);
|
||||
if (usercount === undefined || usercount === 0 || usercount < 0) {
|
||||
usercount = '';
|
||||
$groupLiElement.data('usercount', 0);
|
||||
} else {
|
||||
$groupLiElement.data('usercount', usercount);
|
||||
}
|
||||
$groupLiElement.find('.usercount').text(usercount);
|
||||
},
|
||||
|
||||
getUserCount: function ($groupLiElement) {
|
||||
var count = parseInt($groupLiElement.data('usercount'), 10);
|
||||
return isNaN(count) ? 0 : count;
|
||||
},
|
||||
|
||||
modGroupCount: function(gid, diff) {
|
||||
var $li = GroupList.getGroupLI(gid);
|
||||
var count = GroupList.getUserCount($li) + diff;
|
||||
GroupList.setUserCount($li, count);
|
||||
},
|
||||
|
||||
incEveryoneCount: function() {
|
||||
GroupList.modGroupCount(GroupList.everyoneGID, 1);
|
||||
},
|
||||
|
||||
decEveryoneCount: function() {
|
||||
GroupList.modGroupCount(GroupList.everyoneGID, -1);
|
||||
},
|
||||
|
||||
incGroupCount: function(gid) {
|
||||
GroupList.modGroupCount(gid, 1);
|
||||
},
|
||||
|
||||
decGroupCount: function(gid) {
|
||||
GroupList.modGroupCount(gid, -1);
|
||||
},
|
||||
|
||||
getCurrentGID: function () {
|
||||
return GroupList.activeGID;
|
||||
},
|
||||
|
||||
sortGroups: function () {
|
||||
var lis = $userGroupList.find('.isgroup').get();
|
||||
|
||||
lis.sort(function (a, b) {
|
||||
// "Everyone" always at the top
|
||||
if ($(a).data('gid') === '_everyone') {
|
||||
return -1;
|
||||
} else if ($(b).data('gid') === '_everyone') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// "admin" always as second
|
||||
if ($(a).data('gid') === 'admin') {
|
||||
return -1;
|
||||
} else if ($(b).data('gid') === 'admin') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($sortGroupBy === 1) {
|
||||
// Sort by user count first
|
||||
var $usersGroupA = $(a).data('usercount'),
|
||||
$usersGroupB = $(b).data('usercount');
|
||||
if ($usersGroupA > 0 && $usersGroupA > $usersGroupB) {
|
||||
return -1;
|
||||
}
|
||||
if ($usersGroupB > 0 && $usersGroupB > $usersGroupA) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback or sort by group name
|
||||
return UserList.alphanum(
|
||||
$(a).find('a span').text(),
|
||||
$(b).find('a span').text()
|
||||
);
|
||||
});
|
||||
|
||||
var items = [];
|
||||
$.each(lis, function (index, li) {
|
||||
items.push(li);
|
||||
if (items.length === 100) {
|
||||
$userGroupList.append(items);
|
||||
items = [];
|
||||
}
|
||||
});
|
||||
if (items.length > 0) {
|
||||
$userGroupList.append(items);
|
||||
}
|
||||
},
|
||||
|
||||
createGroup: function (groupid) {
|
||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.createGroup, this, groupid));
|
||||
return;
|
||||
}
|
||||
|
||||
$.post(
|
||||
OC.generateUrl('/settings/users/groups'),
|
||||
{
|
||||
id: groupid
|
||||
},
|
||||
function (result) {
|
||||
if (result.groupname) {
|
||||
var addedGroup = result.groupname;
|
||||
UserList.availableGroups[groupid] = {displayName: result.groupname};
|
||||
GroupList.addGroup(groupid, result.groupname);
|
||||
}
|
||||
GroupList.toggleAddGroup();
|
||||
}).fail(function(result) {
|
||||
OC.Notification.showTemporary(t('settings', 'Error creating group: {message}', {message: result.responseJSON.message}));
|
||||
});
|
||||
},
|
||||
|
||||
update: function () {
|
||||
if (GroupList.updating) {
|
||||
return;
|
||||
}
|
||||
GroupList.updating = true;
|
||||
$.get(
|
||||
OC.generateUrl('/settings/users/groups'),
|
||||
{
|
||||
pattern: this.filter,
|
||||
filterGroups: this.filterGroups ? 1 : 0,
|
||||
sortGroups: $sortGroupBy
|
||||
},
|
||||
function (result) {
|
||||
|
||||
var lis = [];
|
||||
if (result.status === 'success') {
|
||||
$.each(result.data, function (i, subset) {
|
||||
$.each(subset, function (index, group) {
|
||||
if (GroupList.getGroupLI(group.name).length > 0) {
|
||||
GroupList.setUserCount(GroupList.getGroupLI(group.name).first(), group.usercount);
|
||||
}
|
||||
else {
|
||||
var $li = GroupList.addGroup(group.id, group.name, group.usercount);
|
||||
|
||||
$li.addClass('appear transparent');
|
||||
lis.push($li);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (result.data.length > 0) {
|
||||
GroupList.doSort();
|
||||
}
|
||||
else {
|
||||
GroupList.noMoreEntries = true;
|
||||
}
|
||||
_.defer(function () {
|
||||
$(lis).each(function () {
|
||||
this.removeClass('transparent');
|
||||
});
|
||||
});
|
||||
}
|
||||
GroupList.updating = false;
|
||||
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
elementBelongsToAddGroup: function (el) {
|
||||
return !(el !== $('#newgroup-form').get(0) &&
|
||||
$('#newgroup-form').find($(el)).length === 0);
|
||||
},
|
||||
|
||||
hasAddGroupNameText: function () {
|
||||
var name = $('#newgroupname').val();
|
||||
return $.trim(name) !== '';
|
||||
|
||||
},
|
||||
|
||||
showDisabledUsers: function () {
|
||||
UserList.empty();
|
||||
UserList.update('_disabledUsers');
|
||||
$userGroupList.find('li').removeClass('active');
|
||||
GroupList.getGroupLI('_disabledUsers').addClass('active');
|
||||
},
|
||||
|
||||
showGroup: function (gid) {
|
||||
GroupList.activeGID = gid;
|
||||
UserList.empty();
|
||||
UserList.update(gid === '_everyone' ? '' : gid);
|
||||
$userGroupList.find('li').removeClass('active');
|
||||
if (gid !== undefined) {
|
||||
GroupList.getGroupLI(gid).addClass('active');
|
||||
}
|
||||
},
|
||||
|
||||
isAddGroupButtonVisible: function () {
|
||||
return !$('#newgroup-entry').hasClass('editing');
|
||||
},
|
||||
|
||||
toggleAddGroup: function (event) {
|
||||
if (GroupList.isAddGroupButtonVisible()) {
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
$('#newgroup-entry').addClass('editing');
|
||||
$('#newgroupname').select();
|
||||
GroupList.handleAddGroupInput('');
|
||||
}
|
||||
else {
|
||||
$('#newgroup-entry').removeClass('editing');
|
||||
$('#newgroupname').val('');
|
||||
}
|
||||
},
|
||||
|
||||
handleAddGroupInput: function (input) {
|
||||
if(input.length) {
|
||||
$('#newgroup-form input[type="submit"]').attr('disabled', null);
|
||||
} else {
|
||||
$('#newgroup-form input[type="submit"]').attr('disabled', 'disabled');
|
||||
}
|
||||
},
|
||||
|
||||
isGroupNameValid: function (groupname) {
|
||||
if ($.trim(groupname) === '') {
|
||||
OC.Notification.showTemporary(t('settings', 'Error creating group: {message}', {
|
||||
message: t('settings', 'A valid group name must be provided')
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
hide: function (gid) {
|
||||
GroupList.getGroupLI(gid).hide();
|
||||
},
|
||||
show: function (gid) {
|
||||
GroupList.getGroupLI(gid).show();
|
||||
},
|
||||
remove: function (gid) {
|
||||
GroupList.getGroupLI(gid).remove();
|
||||
},
|
||||
empty: function () {
|
||||
$userGroupList.find('.isgroup').filter(function(index, item){
|
||||
return $(item).data('gid') !== '';
|
||||
}).remove();
|
||||
},
|
||||
initDeleteHandling: function () {
|
||||
//set up handler
|
||||
var GroupDeleteHandler = new DeleteHandler('/settings/users/groups', 'groupname',
|
||||
GroupList.hide, GroupList.remove);
|
||||
|
||||
//configure undo
|
||||
OC.Notification.hide();
|
||||
var msg = escapeHTML(t('settings', 'deleted {groupName}', {groupName: '%oid'})) + '<span class="undo">' +
|
||||
escapeHTML(t('settings', 'undo')) + '</span>';
|
||||
GroupDeleteHandler.setNotification(OC.Notification, 'deletegroup', msg,
|
||||
GroupList.show);
|
||||
|
||||
//when to mark user for delete
|
||||
var deleteAction = function () {
|
||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||
OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(deleteAction, this));
|
||||
return;
|
||||
}
|
||||
|
||||
// Call function for handling delete/undo
|
||||
GroupDeleteHandler.mark(GroupList.getElementGID($(this).parent()));
|
||||
};
|
||||
$userGroupList.on('click', '.delete', deleteAction);
|
||||
|
||||
//delete a marked user when leaving the page
|
||||
$(window).on('beforeunload', function () {
|
||||
GroupDeleteHandler.deleteEntry();
|
||||
});
|
||||
},
|
||||
|
||||
getGroupLI: function (gid) {
|
||||
return $userGroupList.find('li.isgroup').filter(function () {
|
||||
return GroupList.getElementGID(this) === gid;
|
||||
});
|
||||
},
|
||||
|
||||
getElementGID: function (element) {
|
||||
return ($(element).closest('li').data('gid') || '').toString();
|
||||
},
|
||||
getEveryoneCount: function () {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
url: OC.generateUrl('/settings/users/stats')
|
||||
}).success(function (data) {
|
||||
$('#everyonegroup').data('usercount', data.totalUsers);
|
||||
$('#everyonecount').text(data.totalUsers);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready( function () {
|
||||
$userGroupList = $('#usergrouplist');
|
||||
GroupList.initDeleteHandling();
|
||||
$sortGroupBy = $userGroupList.data('sort-groups');
|
||||
if ($sortGroupBy === 1) {
|
||||
// Disabled due to performance issues, when we don't need it for sorting
|
||||
GroupList.getEveryoneCount();
|
||||
}
|
||||
|
||||
// Display or hide of Create Group List Element
|
||||
$('#newgroup-init').on('click', function (e) {
|
||||
GroupList.toggleAddGroup(e);
|
||||
});
|
||||
|
||||
$(document).on('click keydown keyup', function(event) {
|
||||
if(!GroupList.isAddGroupButtonVisible() &&
|
||||
!GroupList.elementBelongsToAddGroup(event.target) &&
|
||||
!GroupList.hasAddGroupNameText()) {
|
||||
GroupList.toggleAddGroup();
|
||||
}
|
||||
// Escape
|
||||
if(!GroupList.isAddGroupButtonVisible() && event.keyCode && event.keyCode === 27) {
|
||||
GroupList.toggleAddGroup();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Responsible for Creating Groups.
|
||||
$('#newgroup-form form').submit(function (event) {
|
||||
event.preventDefault();
|
||||
if(GroupList.isGroupNameValid($('#newgroupname').val())) {
|
||||
GroupList.createGroup($('#newgroupname').val());
|
||||
}
|
||||
});
|
||||
|
||||
// click on group name
|
||||
$userGroupList.on('click', '.isgroup', function () {
|
||||
GroupList.showGroup(GroupList.getElementGID(this));
|
||||
});
|
||||
|
||||
// show disabled users
|
||||
$userGroupList.on('click', '.disabledusers', function () {
|
||||
GroupList.showDisabledUsers();
|
||||
});
|
||||
|
||||
$('#newgroupname').on('input', function(){
|
||||
GroupList.handleAddGroupInput(this.value);
|
||||
});
|
||||
|
||||
// highlight `everyone` group at DOMReady by default
|
||||
GroupList.showGroup('_everyone');
|
||||
});
|
File diff suppressed because it is too large
Load diff
24
settings/main.php
Normal file
24
settings/main.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
||||
* @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
||||
*
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
$tmpl = new OC_Template("settings", "settings", "user");
|
||||
$tmpl->printPage();
|
42
settings/package.json
Normal file
42
settings/package.json
Normal file
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"name": "settings",
|
||||
"description": "Nextcloud settings",
|
||||
"version": "1.0.0",
|
||||
"author": "John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>",
|
||||
"license": "AGPL3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development webpack",
|
||||
"watch": "cross-env NODE_ENV=development webpack --progress --watch",
|
||||
"build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"vue": "^2.5.11",
|
||||
"vue-click-outside": "^1.0.7",
|
||||
"vue-infinite-loading": "^2.2.3",
|
||||
"vue-localstorage": "^0.6.2",
|
||||
"vue-multiselect": "^2.1",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"ie >= 11"
|
||||
],
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-preset-env": "^1.6.0",
|
||||
"babel-preset-stage-3": "^6.24.1",
|
||||
"cross-env": "^5.0.5",
|
||||
"css-loader": "^0.28.7",
|
||||
"file-loader": "^1.1.4",
|
||||
"node-sass": "^4.5.3",
|
||||
"sass-loader": "^6.0.6",
|
||||
"vue-loader": "^13.0.5",
|
||||
"vue-template-compiler": "^2.4.4",
|
||||
"webpack": "^3.6.0"
|
||||
}
|
||||
}
|
|
@ -50,7 +50,7 @@ $application->registerRoutes($this, [
|
|||
['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'],
|
||||
['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'],
|
||||
['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'],
|
||||
['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST'],
|
||||
['name' => 'Users#setDisplayName', 'url' => '/settings/users/{id}/displayName', 'verb' => 'PUT'],
|
||||
['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'],
|
||||
['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT'],
|
||||
['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET'],
|
||||
|
|
3
settings/src/.jshintrc
Normal file
3
settings/src/.jshintrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"esversion": 6
|
||||
}
|
16
settings/src/App.vue
Normal file
16
settings/src/App.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'App',
|
||||
beforeMount: function () {
|
||||
// importing server data into the store
|
||||
const serverDataElmt = document.getElementById('serverData');
|
||||
if (serverDataElmt !== null) {
|
||||
this.$store.commit('setServerData', JSON.parse(document.getElementById('serverData').dataset.server));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
32
settings/src/components/appNavigation.vue
Normal file
32
settings/src/components/appNavigation.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div id="app-navigation">
|
||||
<div class="app-navigation-new" v-if="menu.new">
|
||||
<button type="button" :id="menu.new.id" :class="menu.new.icon" @click="menu.new.action">{{menu.new.text}}</button>
|
||||
</div>
|
||||
<ul :id="menu.id">
|
||||
<navigation-item v-for="(item, key) in menu.items" :item="item" :key="key" />
|
||||
</ul>
|
||||
<div id="app-settings">
|
||||
<div id="app-settings-header">
|
||||
<button class="settings-button"
|
||||
data-apps-slide-toggle="#app-settings-content"
|
||||
>{{t('settings', 'Settings')}}</button>
|
||||
</div>
|
||||
<div id="app-settings-content">
|
||||
<slot name="settings-content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import navigationItem from './appNavigation/navigationItem';
|
||||
|
||||
export default {
|
||||
name: 'appNavigation',
|
||||
props: ['menu'],
|
||||
components: {
|
||||
navigationItem
|
||||
}
|
||||
}
|
||||
</script>
|
108
settings/src/components/appNavigation/navigationItem.vue
Normal file
108
settings/src/components/appNavigation/navigationItem.vue
Normal file
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<li :id="item.id" :class="[{'icon-loading-small': item.loading, 'open': item.opened, 'collapsible': item.collapsible&&item.children&&item.children.length>0 }, item.classes]">
|
||||
|
||||
<!-- Bullet -->
|
||||
<div v-if="item.bullet" class="app-navigation-entry-bullet" :style="{ backgroundColor: item.bullet }"></div>
|
||||
|
||||
<!-- Main link -->
|
||||
<a :href="(item.href) ? item.href : '#' " @click="toggleCollapse" :class="item.icon" >{{item.text}}</a>
|
||||
|
||||
<!-- Popover, counter and button(s) -->
|
||||
<div v-if="item.utils" class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<!-- counter -->
|
||||
<li v-if="Number.isInteger(item.utils.counter)"
|
||||
class="app-navigation-entry-utils-counter">{{item.utils.counter}}</li>
|
||||
|
||||
<!-- first action if only one action and counter -->
|
||||
<li v-if="item.utils.actions && item.utils.actions.length === 1 && Number.isInteger(item.utils.counter)"
|
||||
class="app-navigation-entry-utils-menu-button">
|
||||
<button :class="item.utils.actions[0].icon"></button>
|
||||
</li>
|
||||
|
||||
<!-- second action only two actions and no counter -->
|
||||
<li v-else-if="item.utils.actions && item.utils.actions.length === 2 && !Number.isInteger(item.utils.counter)"
|
||||
v-for="action in item.utils.actions" :key="action.action"
|
||||
class="app-navigation-entry-utils-menu-button">
|
||||
<button :class="action.icon"></button>
|
||||
</li>
|
||||
|
||||
<!-- menu if only at least one action and counter OR two actions and no counter-->
|
||||
<li v-else-if="item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)"
|
||||
class="app-navigation-entry-utils-menu-button">
|
||||
<button v-click-outside="hideMenu" @click="showMenu" ></button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- if more than 2 actions or more than 1 actions with counter -->
|
||||
<div v-if="item.utils && item.utils.actions && item.utils.actions.length > 1 && (Number.isInteger(item.utils.counter) || item.utils.actions.length > 2)"
|
||||
class="app-navigation-entry-menu" :class="{ 'open': openedMenu }">
|
||||
<popover-menu :menu="item.utils.actions"/>
|
||||
</div>
|
||||
|
||||
<!-- undo entry -->
|
||||
<div class="app-navigation-entry-deleted" v-if="item.undo">
|
||||
<div class="app-navigation-entry-deleted-description">{{item.undo.text}}</div>
|
||||
<button class="app-navigation-entry-deleted-button icon-history" :title="t('settings', 'Undo')"></button>
|
||||
</div>
|
||||
|
||||
<!-- edit entry -->
|
||||
<div class="app-navigation-entry-edit" v-if="item.edit">
|
||||
<form>
|
||||
<input type="text" v-model="item.text">
|
||||
<input type="submit" value="" class="icon-confirm">
|
||||
<input type="submit" value="" class="icon-close" @click.stop.prevent="cancelEdit">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- if the item has children, inject the component with proper data -->
|
||||
<ul v-if="item.children">
|
||||
<navigation-item v-for="(item, key) in item.children" :item="item" :key="key" />
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popoverMenu from '../popoverMenu';
|
||||
import ClickOutside from 'vue-click-outside';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'navigationItem',
|
||||
props: ['item'],
|
||||
components: {
|
||||
popoverMenu
|
||||
},
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
openedMenu: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showMenu () {
|
||||
this.openedMenu = true;
|
||||
},
|
||||
hideMenu () {
|
||||
this.openedMenu = false;
|
||||
},
|
||||
toggleCollapse () {
|
||||
// if item.opened isn't set, Vue won't trigger view updates https://vuejs.org/v2/api/#Vue-set
|
||||
// ternary is here to detect the undefined state of item.opened
|
||||
Vue.set(this.item, 'opened', this.item.opened ? !this.item.opened : true);
|
||||
},
|
||||
cancelEdit () {
|
||||
// remove the editing class
|
||||
if (Array.isArray(this.item.classes))
|
||||
this.item.classes = this.item.classes.filter(item => item !== 'editing');
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// prevent click outside event with popupItem.
|
||||
this.popupItem = this.$el;
|
||||
},
|
||||
}
|
||||
</script>
|
18
settings/src/components/popoverMenu.vue
Normal file
18
settings/src/components/popoverMenu.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<ul>
|
||||
<popover-item v-for="(item, key) in menu" :item="item" :key="key" />
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import popoverItem from './popoverMenu/popoverItem';
|
||||
|
||||
export default {
|
||||
name: 'popoverMenu',
|
||||
props: ['menu'],
|
||||
components: {
|
||||
popoverItem
|
||||
}
|
||||
}
|
||||
</script>
|
23
settings/src/components/popoverMenu/popoverItem.vue
Normal file
23
settings/src/components/popoverMenu/popoverItem.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<li>
|
||||
<a @click="dispatchToStore" v-if="item.href" :href="(item.href) ? item.href : '#' ">
|
||||
<span :class="item.icon"></span>
|
||||
<span>{{item.text}}</span>
|
||||
</a>
|
||||
<button @click="dispatchToStore(item.action)" v-else>
|
||||
<span :class="item.icon"></span>
|
||||
<span>{{item.text}}</span>
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item'],
|
||||
methods: {
|
||||
dispatchToStore () {
|
||||
this.$store.dispatch(this.item.action, this.item.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
205
settings/src/components/userList.vue
Normal file
205
settings/src/components/userList.vue
Normal file
|
@ -0,0 +1,205 @@
|
|||
<template>
|
||||
<div id="app-content" class="user-list-grid" v-on:scroll.passive="onScroll">
|
||||
<div class="row" id="grid-header" :class="{'sticky': scrolled && !showConfig.showNewUserForm}">
|
||||
<div id="headerAvatar" class="avatar"></div>
|
||||
<div id="headerName" class="name">{{ t('settings', 'Username') }}</div>
|
||||
<div id="headerDisplayName" class="displayName">{{ t('settings', 'Full name') }}</div>
|
||||
<div id="headerPassword" class="password">{{ t('settings', 'Password') }}</div>
|
||||
<div id="headerAddress" class="mailAddress">{{ t('settings', 'Email') }}</div>
|
||||
<div id="headerGroups" class="groups">{{ t('settings', 'Groups') }}</div>
|
||||
<div id="headerSubAdmins" class="subadmins"
|
||||
v-if="subAdminsGroups.length>0">{{ t('settings', 'Group admin for') }}</div>
|
||||
<div id="headerQuota" class="quota">{{ t('settings', 'Quota') }}</div>
|
||||
<div class="headerStorageLocation storageLocation"
|
||||
v-if="showConfig.showStoragePath">{{ t('settings', 'Storage location') }}</div>
|
||||
<div class="headerUserBackend userBackend"
|
||||
v-if="showConfig.showUserBackend">{{ t('settings', 'User backend') }}</div>
|
||||
<div class="headerLastLogin lastLogin"
|
||||
v-if="showConfig.showLastLogin">{{ t('settings', 'Last login') }}</div>
|
||||
<div class="userActions"></div>
|
||||
</div>
|
||||
|
||||
<form class="row" id="new-user" v-show="showConfig.showNewUserForm"
|
||||
v-on:submit.prevent="createUser" :disabled="loading"
|
||||
:class="{'sticky': scrolled && showConfig.showNewUserForm}">
|
||||
<div :class="loading?'icon-loading-small':'icon-add'"></div>
|
||||
<div class="name">
|
||||
<input id="newusername" type="text" required v-model="newUser.id"
|
||||
:placeholder="t('settings', 'User name')" name="username"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off"
|
||||
pattern="[a-zA-Z0-9 _\.@\-']+">
|
||||
</div>
|
||||
<div class="displayName">
|
||||
<input id="newdisplayname" type="text" v-model="newUser.displayName"
|
||||
:placeholder="t('settings', 'Display name')" name="displayname"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off">
|
||||
</div>
|
||||
<div class="password">
|
||||
<input id="newuserpassword" type="password" v-model="newUser.password"
|
||||
:required="newUser.mailAddress===''"
|
||||
:placeholder="t('settings', 'Password')" name="password"
|
||||
autocomplete="new-password" autocapitalize="none" autocorrect="off"
|
||||
:minlength="minPasswordLength">
|
||||
</div>
|
||||
<div class="mailAddress">
|
||||
<input id="newemail" type="email" v-model="newUser.mailAddress"
|
||||
:required="newUser.password===''"
|
||||
:placeholder="t('settings', 'Mail address')" name="email"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off">
|
||||
</div>
|
||||
<div class="groups">
|
||||
<multiselect :options="groups" v-model="newUser.groups"
|
||||
:placeholder="t('settings', 'Add user in group')"
|
||||
label="name" track-by="id" class="multiselect-vue"
|
||||
:multiple="true" :close-on-select="false">
|
||||
<span slot="noResult">{{t('settings','No result')}}</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="subadmins" v-if="subAdminsGroups.length>0">
|
||||
<multiselect :options="subAdminsGroups" v-model="newUser.subAdminsGroups"
|
||||
:placeholder="t('settings', 'Set user as admin for')"
|
||||
label="name" track-by="id" class="multiselect-vue"
|
||||
:multiple="true" :close-on-select="false">
|
||||
<span slot="noResult">{{t('settings','No result')}}</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="quota">
|
||||
<multiselect :options="quotaOptions" v-model="newUser.quota"
|
||||
:placeholder="t('settings', 'Select user quota')"
|
||||
label="label" track-by="id" class="multiselect-vue"
|
||||
:allowEmpty="false" :taggable="true"
|
||||
@tag="validateQuota" >
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="storageLocation" v-if="showConfig.showStoragePath"></div>
|
||||
<div class="userBackend" v-if="showConfig.showUserBackend"></div>
|
||||
<div class="lastLogin" v-if="showConfig.showLastLogin"></div>
|
||||
<div class="userActions">
|
||||
<input type="submit" id="newsubmit" class="button primary icon-checkmark-white has-tooltip"
|
||||
value="" :title="t('settings', 'Add a new user')">
|
||||
<input type="reset" id="newreset" class="button icon-close has-tooltip" @click="resetForm"
|
||||
value="" :title="t('settings', 'Cancel and reset the form')">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<user-row v-for="(user, key) in users" :user="user" :key="key" :settings="settings" :showConfig="showConfig"
|
||||
:groups="groups" :subAdminsGroups="subAdminsGroups" :quotaOptions="quotaOptions" />
|
||||
<infinite-loading @infinite="infiniteHandler">
|
||||
<span slot="spinner"><div class="users-icon-loading"></div></span>
|
||||
<span slot="no-more"><div class="users-list-end">— {{t('settings', 'no more results')}} —</div></span>
|
||||
</infinite-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import userRow from './userList/userRow';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import InfiniteLoading from 'vue-infinite-loading';
|
||||
|
||||
export default {
|
||||
name: 'userList',
|
||||
props: ['users', 'showConfig'],
|
||||
components: {
|
||||
userRow,
|
||||
Multiselect,
|
||||
InfiniteLoading
|
||||
},
|
||||
data() {
|
||||
let unlimitedQuota = {id:'none', label:t('settings', 'Unlimited')},
|
||||
defaultQuota = {id:'default', label:t('settings', 'Default quota')};
|
||||
return {
|
||||
unlimitedQuota: unlimitedQuota,
|
||||
defaultQuota: defaultQuota,
|
||||
loading: false,
|
||||
scrolled: false,
|
||||
newUser: {
|
||||
id:'',
|
||||
displayName:'',
|
||||
password:'',
|
||||
mailAddress:'',
|
||||
groups: [],
|
||||
subAdminsGroups: [],
|
||||
quota: defaultQuota
|
||||
}
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (!this.settings.canChangePassword) {
|
||||
OC.Notification.showTemporary(t('settings','Password change is disabled because the master key is disabled'));
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return this.$store.getters.getServerData;
|
||||
},
|
||||
groups() {
|
||||
// data provided php side + remove the disabled group
|
||||
return this.$store.getters.getGroups.filter(group => group.id !== '_disabled');
|
||||
},
|
||||
subAdminsGroups() {
|
||||
// data provided php side
|
||||
return this.$store.getters.getServerData.subadmingroups;
|
||||
},
|
||||
quotaOptions() {
|
||||
// convert the preset array into objects
|
||||
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);
|
||||
return quotaPreset;
|
||||
},
|
||||
minPasswordLength() {
|
||||
return this.$store.getters.getPasswordPolicyMinLength;
|
||||
},
|
||||
usersOffset() {
|
||||
return this.$store.getters.getUsersOffset;
|
||||
},
|
||||
usersLimit() {
|
||||
return this.$store.getters.getUsersLimit;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onScroll(event) {
|
||||
this.scrolled = event.target.scrollTop>0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate quota string to make sure it's a valid human file size
|
||||
*
|
||||
* @param {string} quota Quota in readable format '5 GB'
|
||||
* @returns {Object}
|
||||
*/
|
||||
validateQuota(quota) {
|
||||
// only used for new presets sent through @Tag
|
||||
let validQuota = OC.Util.computerFileSize(quota);
|
||||
if (validQuota !== null && validQuota > 0) {
|
||||
// unify format output
|
||||
quota = OC.Util.humanFileSize(OC.Util.computerFileSize(quota));
|
||||
return this.newUser.quota = {id: quota, label: quota};
|
||||
}
|
||||
// Default is unlimited
|
||||
return this.newUser.quota = this.quotaOptions[0];
|
||||
},
|
||||
|
||||
infiniteHandler($state) {
|
||||
this.$store.dispatch('getUsers', {offset:this.usersOffset, limit:this.usersLimit})
|
||||
.then((response) => {response?$state.loaded():$state.complete()});
|
||||
},
|
||||
|
||||
resetForm () {
|
||||
// revert form to original state
|
||||
Object.assign(this.newUser, this.$options.data.call(this).newUser);
|
||||
this.loading = false;
|
||||
},
|
||||
createUser() {
|
||||
this.loading = true;
|
||||
this.$store.dispatch('addUser', {
|
||||
userid: this.newUser.id,
|
||||
password: this.newUser.password,
|
||||
email: this.newUser.mailAddress,
|
||||
groups: this.newUser.groups.map(group => group.id)
|
||||
}).then(() =>this.resetForm());
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
370
settings/src/components/userList/userRow.vue
Normal file
370
settings/src/components/userList/userRow.vue
Normal file
|
@ -0,0 +1,370 @@
|
|||
<template>
|
||||
<div class="row">
|
||||
<div class="avatar"><img alt="" width="32" height="32" :src="generateAvatar(user.id, 32)" :srcset="generateAvatar(user.id, 64)+' 2x, '+generateAvatar(user.id, 128)+' 4x'"></div>
|
||||
<div class="name">{{user.id}}</div>
|
||||
<form class="displayName" :class="{'icon-loading-small': loading.displayName}" v-on:submit.prevent="updateDisplayName">
|
||||
<input :id="'displayName'+user.id+rand" type="text"
|
||||
:disabled="loading.displayName||loading.all"
|
||||
:value="user.displayname" ref="displayName"
|
||||
autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
||||
<input type="submit" class="icon-confirm" value="" />
|
||||
</form>
|
||||
<form class="password" v-if="settings.canChangePassword" :class="{'icon-loading-small': loading.password}"
|
||||
v-on:submit.prevent="updatePassword">
|
||||
<input :id="'password'+user.id+rand" type="password" required
|
||||
:disabled="loading.password||loading.all" :minlength="minPasswordLength"
|
||||
value="" :placeholder="t('settings', 'New password')" ref="password"
|
||||
autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
||||
<input type="submit" class="icon-confirm" value="" />
|
||||
</form>
|
||||
<div v-else></div>
|
||||
<form class="mailAddress" :class="{'icon-loading-small': loading.mailAddress}" v-on:submit.prevent="updateEmail">
|
||||
<input :id="'mailAddress'+user.id+rand" type="email"
|
||||
:disabled="loading.mailAddress||loading.all"
|
||||
:value="user.email" ref="mailAddress"
|
||||
autocomplete="new-password" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
||||
<input type="submit" class="icon-confirm" value="" />
|
||||
</form>
|
||||
<div class="groups" :class="{'icon-loading-small': loading.groups}">
|
||||
<multiselect :value="userGroups" :options="groups" :disabled="loading.groups||loading.all"
|
||||
tag-placeholder="create" :placeholder="t('settings', 'Add user in group')"
|
||||
label="name" track-by="id" class="multiselect-vue"
|
||||
:limit="2" :limitText="limitGroups"
|
||||
:multiple="true" :taggable="true" :closeOnSelect="false"
|
||||
@tag="createGroup" @select="addUserGroup" @remove="removeUserGroup">
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="subadmins" v-if="subAdminsGroups.length>0" :class="{'icon-loading-small': loading.subadmins}">
|
||||
<multiselect :value="userSubAdminsGroups" :options="subAdminsGroups" :disabled="loading.subadmins||loading.all"
|
||||
:placeholder="t('settings', 'Set user as admin for')"
|
||||
label="name" track-by="id" class="multiselect-vue"
|
||||
:limit="2" :limitText="limitGroups"
|
||||
:multiple="true" :closeOnSelect="false"
|
||||
@select="addUserSubAdmin" @remove="removeUserSubAdmin">
|
||||
<span slot="noResult">{{t('settings','No result')}}</span>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="quota" :class="{'icon-loading-small': loading.quota}">
|
||||
<multiselect :value="userQuota" :options="quotaOptions" :disabled="loading.quota||loading.all"
|
||||
tag-placeholder="create" :placeholder="t('settings', 'Select user quota')"
|
||||
label="label" track-by="id" class="multiselect-vue"
|
||||
:allowEmpty="false" :taggable="true"
|
||||
@tag="validateQuota" @input="setUserQuota">
|
||||
</multiselect>
|
||||
<progress class="quota-user-progress" :class="{'warn':usedQuota>80}" :value="usedQuota" max="100"></progress>
|
||||
</div>
|
||||
<div class="storageLocation" v-if="showConfig.showStoragePath">{{user.storageLocation}}</div>
|
||||
<div class="userBackend" v-if="showConfig.showUserBackend">{{user.backend}}</div>
|
||||
<div class="lastLogin" v-if="showConfig.showLastLogin" :title="user.lastLogin>0 ? OC.Util.formatDate(user.lastLogin) : ''">
|
||||
{{user.lastLogin>0 ? OC.Util.relativeModifiedDate(user.lastLogin) : t('settings','Never')}}
|
||||
</div>
|
||||
<div class="userActions">
|
||||
<div class="toggleUserActions" v-if="OC.currentUser !== user.id && user.id !== 'admin'">
|
||||
<div class="icon-more" v-click-outside="hideMenu" @click="showMenu"></div>
|
||||
<div class="popovermenu" :class="{ 'open': openedMenu }">
|
||||
<popover-menu :menu="userActions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import popoverMenu from '../popoverMenu';
|
||||
import ClickOutside from 'vue-click-outside';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
//import Multiselect from '../../../node_modules/vue-multiselect/src/index';
|
||||
|
||||
export default {
|
||||
name: 'userRow',
|
||||
props: ['user', 'settings', 'groups', 'subAdminsGroups', 'quotaOptions', 'showConfig'],
|
||||
components: {
|
||||
popoverMenu,
|
||||
Multiselect
|
||||
},
|
||||
directives: {
|
||||
ClickOutside
|
||||
},
|
||||
mounted() {
|
||||
// prevent click outside event with popupItem.
|
||||
this.popupItem = this.$el;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rand: parseInt(Math.random() * 1000),
|
||||
openedMenu: false,
|
||||
loading: {
|
||||
all: false,
|
||||
displayName: false,
|
||||
password: false,
|
||||
mailAddress: false,
|
||||
groups: false,
|
||||
subadmins: false,
|
||||
quota: false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/* USER POPOVERMENU ACTIONS */
|
||||
userActions() {
|
||||
return [{
|
||||
icon: 'icon-delete',
|
||||
text: t('settings','Delete user'),
|
||||
action: 'deleteUser',
|
||||
data: this.user.id
|
||||
},{
|
||||
'icon': this.user.enabled ? 'icon-close' : 'icon-add',
|
||||
'text': this.user.enabled ? t('settings','Disable user') : t('settings','Enable user'),
|
||||
'action': 'enableDisableUser',
|
||||
data: {userid: this.user.id, enabled: !this.user.enabled}
|
||||
}]
|
||||
},
|
||||
|
||||
/* 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;
|
||||
},
|
||||
|
||||
/* QUOTA MANAGEMENT */
|
||||
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 ? userQuota : {id:humanQuota, label:humanQuota};
|
||||
} else if (this.user.quota.quota === 0 || 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;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/* MENU HANDLING */
|
||||
showMenu () {
|
||||
this.openedMenu = true;
|
||||
},
|
||||
hideMenu () {
|
||||
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 the limit text in the selected options
|
||||
*
|
||||
* @param {int} count elements left
|
||||
* @returns {string}
|
||||
*/
|
||||
limitGroups(count) {
|
||||
return '+'+count;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set user displayName
|
||||
*
|
||||
* @param {string} displayName The display name
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateDisplayName() {
|
||||
let displayName = this.$refs.displayName.value;
|
||||
this.loading.displayName = true;
|
||||
this.$store.dispatch('setUserData', {
|
||||
userid: this.user.id,
|
||||
key: 'displayname',
|
||||
value: displayName
|
||||
}).then(() => {
|
||||
this.loading.displayName = false;
|
||||
this.$refs.displayName.value = displayName;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set user password
|
||||
*
|
||||
* @param {string} password The email adress
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updatePassword() {
|
||||
let password = this.$refs.password.value;
|
||||
this.loading.password = true;
|
||||
this.$store.dispatch('setUserData', {
|
||||
userid: this.user.id,
|
||||
key: 'password',
|
||||
value: password
|
||||
}).then(() => {
|
||||
this.loading.password = false;
|
||||
this.$refs.password.value = ''; // empty & show placeholder
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set user mailAddress
|
||||
*
|
||||
* @param {string} mailAddress The email adress
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateEmail() {
|
||||
let mailAddress = this.$refs.mailAddress.value;
|
||||
this.loading.mailAddress = true;
|
||||
this.$store.dispatch('setUserData', {
|
||||
userid: this.user.id,
|
||||
key: 'email',
|
||||
value: mailAddress
|
||||
}).then(() => {
|
||||
this.loading.mailAddress = false;
|
||||
this.$refs.mailAddress.value = mailAddress;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new group
|
||||
*
|
||||
* @param {string} groups Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
createGroup(gid) {
|
||||
this.loading = {groups:true, subadmins:true}
|
||||
this.$store.dispatch('addGroup', gid).then(() => {
|
||||
this.loading = {groups:false, subadmins:false};
|
||||
let userid = this.user.id;
|
||||
this.$store.dispatch('addUserGroup', {userid, gid});
|
||||
});
|
||||
return this.$store.getters.getGroups[this.groups.length];
|
||||
},
|
||||
|
||||
/**
|
||||
* Add user to group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addUserGroup(group) {
|
||||
this.loading.groups = true;
|
||||
let userid = this.user.id;
|
||||
let gid = group.id;
|
||||
return this.$store.dispatch('addUserGroup', {userid, gid})
|
||||
.then(() => this.loading.groups = false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove user from group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeUserGroup(group) {
|
||||
this.loading.groups = true;
|
||||
let userid = this.user.id;
|
||||
let gid = group.id;
|
||||
return this.$store.dispatch('removeUserGroup', {userid, gid})
|
||||
.then(() => this.loading.groups = false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add user to group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addUserSubAdmin(group) {
|
||||
this.loading.subadmins = true;
|
||||
let userid = this.user.id;
|
||||
let gid = group.id;
|
||||
return this.$store.dispatch('addUserSubAdmin', {userid, gid})
|
||||
.then(() => this.loading.subadmins = false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove user from group
|
||||
*
|
||||
* @param {object} group Group object
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeUserSubAdmin(group) {
|
||||
this.loading.subadmins = true;
|
||||
let userid = this.user.id;
|
||||
let gid = group.id;
|
||||
return this.$store.dispatch('removeUserSubAdmin', {userid, gid})
|
||||
.then(() => this.loading.subadmins = false);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Validate quota string to make sure it's a valid human file size
|
||||
*
|
||||
* @param {string|Object} quota Quota in readable format '5 GB' or Object {id: '5 GB', label: '5GB'}
|
||||
* @returns {string}
|
||||
*/
|
||||
setUserQuota(quota = 'none') {
|
||||
this.loading.quota = true;
|
||||
// ensure we only send the preset id
|
||||
quota = quota.id ? quota.id : quota;
|
||||
this.$store.dispatch('setUserData', {
|
||||
userid: this.user.id,
|
||||
key: 'quota',
|
||||
value: quota
|
||||
}).then(() => this.loading.quota = false);
|
||||
return quota;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (validQuota === 0) {
|
||||
return this.setUserQuota('none');
|
||||
} else if (validQuota !== null) {
|
||||
// unify format output
|
||||
return this.setUserQuota(OC.Util.humanFileSize(OC.Util.computerFileSize(quota)));
|
||||
}
|
||||
// if no valid doo not change
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
20
settings/src/main.js
Normal file
20
settings/src/main.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import Vue from 'vue';
|
||||
import { sync } from 'vuex-router-sync';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import store from './store';
|
||||
|
||||
sync(store, router);
|
||||
|
||||
// bind to window
|
||||
Vue.prototype.t = t;
|
||||
Vue.prototype.OC = OC;
|
||||
Vue.prototype.oc_userconfig = oc_userconfig;
|
||||
|
||||
const app = new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#content');
|
||||
|
||||
export { app, router, store };
|
23
settings/src/router.js
Normal file
23
settings/src/router.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import Vue from 'vue';
|
||||
import Router from 'vue-router';
|
||||
import Users from './views/Users';
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
/*
|
||||
* This is the list of routes where the vuejs app will
|
||||
* take over php to provide data
|
||||
* You need to forward the php routing (routes.php) to
|
||||
* /settings/main.php, where the vue-router will ensure
|
||||
* the proper route.
|
||||
* ⚠️ Routes needs to match the php routes.
|
||||
*/
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
base: window.location.pathName,
|
||||
routes: [{
|
||||
path: '/settings/users',
|
||||
component: Users
|
||||
}]
|
||||
});
|
50
settings/src/store/api.js
Normal file
50
settings/src/store/api.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
import axios from 'axios';
|
||||
|
||||
const requestToken = document.getElementsByTagName('head')[0].getAttribute('data-requesttoken');
|
||||
const tokenHeaders = { headers: { requesttoken: requestToken } };
|
||||
|
||||
const sanitize = function(url) {
|
||||
return url.replace(/\/$/, ''); // Remove last slash of url
|
||||
}
|
||||
|
||||
export default {
|
||||
requireAdmin() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
setTimeout(reject, 5000); // automatically reject 5s if not ok
|
||||
function waitForpassword() {
|
||||
if (OC.PasswordConfirmation.requiresPasswordConfirmation()) {
|
||||
setTimeout(waitForpassword, 500);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
waitForpassword();
|
||||
OC.PasswordConfirmation.requirePasswordConfirmation();
|
||||
}).catch((error) => console.log('Required password not entered'));
|
||||
},
|
||||
get(url) {
|
||||
return axios.get(sanitize(url), tokenHeaders)
|
||||
.then((response) => Promise.resolve(response))
|
||||
.catch((error) => Promise.reject(error));
|
||||
},
|
||||
post(url, data) {
|
||||
return axios.post(sanitize(url), data, tokenHeaders)
|
||||
.then((response) => Promise.resolve(response))
|
||||
.catch((error) => Promise.reject(error));
|
||||
},
|
||||
patch(url, data) {
|
||||
return axios.patch(sanitize(url), { data: data, headers: tokenHeaders.headers })
|
||||
.then((response) => Promise.resolve(response))
|
||||
.catch((error) => Promise.reject(error));
|
||||
},
|
||||
put(url, data) {
|
||||
return axios.put(sanitize(url), data, tokenHeaders)
|
||||
.then((response) => Promise.resolve(response))
|
||||
.catch((error) => Promise.reject(error));
|
||||
},
|
||||
delete(url, data) {
|
||||
return axios.delete(sanitize(url), { data: data, headers: tokenHeaders.headers })
|
||||
.then((response) => Promise.resolve(response))
|
||||
.catch((error) => Promise.reject(error));
|
||||
}
|
||||
};
|
24
settings/src/store/index.js
Normal file
24
settings/src/store/index.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import users from './users'
|
||||
import settings from './settings'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const debug = process.env.NODE_ENV !== 'production'
|
||||
|
||||
const mutations = {
|
||||
API_FAILURE(state, error) {
|
||||
console.log(state, error);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
users,
|
||||
settings
|
||||
},
|
||||
strict: debug,
|
||||
|
||||
mutations
|
||||
})
|
18
settings/src/store/settings.js
Normal file
18
settings/src/store/settings.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import api from './api';
|
||||
|
||||
const state = {
|
||||
serverData: {}
|
||||
};
|
||||
const mutations = {
|
||||
setServerData(state, data) {
|
||||
state.serverData = data;
|
||||
}
|
||||
};
|
||||
const getters = {
|
||||
getServerData(state) {
|
||||
return state.serverData;
|
||||
}
|
||||
}
|
||||
const actions = {}
|
||||
|
||||
export default {state, mutations, getters, actions};
|
380
settings/src/store/users.js
Normal file
380
settings/src/store/users.js
Normal file
|
@ -0,0 +1,380 @@
|
|||
import api from './api';
|
||||
|
||||
const orderGroups = function(groups, orderBy) {
|
||||
/* const SORT_USERCOUNT = 1;
|
||||
* const SORT_GROUPNAME = 2;
|
||||
* https://github.com/nextcloud/server/blob/208e38e84e1a07a49699aa90dc5b7272d24489f0/lib/private/Group/MetaData.php#L34
|
||||
*/
|
||||
if (orderBy === 1) {
|
||||
return groups.sort((a, b) => a.usercount < b.usercount);
|
||||
} else {
|
||||
return groups.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
users: [],
|
||||
groups: [],
|
||||
orderBy: 1,
|
||||
minPasswordLength: 0,
|
||||
usersOffset: 0,
|
||||
usersLimit: 25,
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
appendUsers(state, usersObj) {
|
||||
// convert obj to array
|
||||
let users = state.users.concat(Object.keys(usersObj).map(userid => usersObj[userid]));
|
||||
state.usersOffset += state.usersLimit;
|
||||
state.users = users;
|
||||
},
|
||||
setPasswordPolicyMinLength(state, length) {
|
||||
state.minPasswordLength = length!=='' ? length : 0;
|
||||
},
|
||||
initGroups(state, {groups, orderBy}) {
|
||||
state.groups = groups;
|
||||
state.orderBy = orderBy;
|
||||
state.groups = orderGroups(state.groups, state.orderBy);
|
||||
},
|
||||
addGroup(state, groupid) {
|
||||
try {
|
||||
state.groups.push({
|
||||
id: groupid,
|
||||
name: groupid,
|
||||
usercount: 0 // user will be added after the creation
|
||||
});
|
||||
state.groups = orderGroups(state.groups, state.orderBy);
|
||||
} catch (e) {
|
||||
console.log('Can\'t create group', e);
|
||||
}
|
||||
},
|
||||
addUserGroup(state, { userid, gid }) {
|
||||
// this should not be needed as it would means the user contains a group
|
||||
// the server database doesn't have.
|
||||
let group = state.groups.find(groupSearch => groupSearch.id == gid);
|
||||
if (group) {
|
||||
group.usercount++; // increase count
|
||||
}
|
||||
let groups = state.users.find(user => user.id == userid).groups;
|
||||
groups.push(gid);
|
||||
state.groups = orderGroups(state.groups, state.orderBy);
|
||||
},
|
||||
removeUserGroup(state, { userid, gid }) {
|
||||
// this should not be needed as it would means the user contains a group
|
||||
// the server database doesn't have.
|
||||
let group = state.groups.find(groupSearch => groupSearch.id == gid);
|
||||
if (group) {
|
||||
group.usercount--; // lower count
|
||||
}
|
||||
let groups = state.users.find(user => user.id == userid).groups;
|
||||
groups.splice(groups.indexOf(gid),1);
|
||||
state.groups = orderGroups(state.groups, state.orderBy);
|
||||
},
|
||||
addUserSubAdmin(state, { userid, gid }) {
|
||||
let groups = state.users.find(user => user.id == userid).subadmin;
|
||||
groups.push(gid);
|
||||
},
|
||||
removeUserSubAdmin(state, { userid, gid }) {
|
||||
let groups = state.users.find(user => user.id == userid).subadmin;
|
||||
groups.splice(groups.indexOf(gid),1);
|
||||
},
|
||||
deleteUser(state, userid) {
|
||||
let userIndex = state.users.findIndex(user => user.id == userid);
|
||||
state.users.splice(userIndex, 1);
|
||||
},
|
||||
addUserData(state, response) {
|
||||
state.users.push(response.data.ocs.data);
|
||||
},
|
||||
enableDisableUser(state, { userid, enabled }) {
|
||||
state.users.find(user => user.id == userid).enabled = enabled;
|
||||
state.groups.find(group => group.id == '_disabled').usercount += enabled ? -1 : 1;
|
||||
},
|
||||
setUserData(state, { userid, key, value }) {
|
||||
if (key === 'quota') {
|
||||
let humanValue = OC.Util.computerFileSize(value);
|
||||
state.users.find(user => user.id == userid)[key][key] = humanValue?humanValue:value;
|
||||
} else {
|
||||
state.users.find(user => user.id == userid)[key] = value;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const getters = {
|
||||
getUsers(state) {
|
||||
return state.users;
|
||||
},
|
||||
getGroups(state) {
|
||||
return state.groups;
|
||||
},
|
||||
getPasswordPolicyMinLength(state) {
|
||||
return state.minPasswordLength;
|
||||
},
|
||||
getUsersOffset(state) {
|
||||
return state.usersOffset;
|
||||
},
|
||||
getUsersLimit(state) {
|
||||
return state.usersLimit;
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
/**
|
||||
* Get all users with full details
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {int} options.offset List offset to request
|
||||
* @param {int} options.limit List number to return from offset
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getUsers(context, { offset, limit, search }) {
|
||||
search = typeof search === 'string' ? search : '';
|
||||
return api.get(OC.linkToOCS(`cloud/users/details?offset=${offset}&limit=${limit}&search=${search}`, 2))
|
||||
.then((response) => {
|
||||
if (Object.keys(response.data.ocs.data.users).length > 0) {
|
||||
context.commit('appendUsers', response.data.ocs.data.users);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all users with full details
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {int} options.offset List offset to request
|
||||
* @param {int} options.limit List number to return from offset
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getUsersFromList(context, { offset, limit, search }) {
|
||||
search = typeof search === 'string' ? search : '';
|
||||
return api.get(OC.linkToOCS(`cloud/users/details?offset=${offset}&limit=${limit}&search=${search}`, 2))
|
||||
.then((response) => {
|
||||
if (Object.keys(response.data.ocs.data.users).length > 0) {
|
||||
context.commit('appendUsers', response.data.ocs.data.users);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all users with full details from a groupid
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {int} options.offset List offset to request
|
||||
* @param {int} options.limit List number to return from offset
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getUsersFromGroup(context, { groupid, offset, limit }) {
|
||||
return api.get(OC.linkToOCS(`cloud/users/${groupid}/details?offset=${offset}&limit=${limit}`, 2))
|
||||
.then((response) => context.commit('getUsersFromList', response.data.ocs.data.users))
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
},
|
||||
|
||||
|
||||
getPasswordPolicyMinLength(context) {
|
||||
return api.get(OC.linkToOCS('apps/provisioning_api/api/v1/config/apps/password_policy/minLength', 2))
|
||||
.then((response) => context.commit('setPasswordPolicyMinLength', response.data.ocs.data.data))
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
},
|
||||
|
||||
/**
|
||||
* Add group
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {string} gid Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addGroup(context, gid) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(OC.linkToOCS(`cloud/groups`, 2), {groupid: gid})
|
||||
.then((response) => context.commit('addGroup', gid))
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add group
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {string} gid Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeGroup(context, gid) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(OC.linkToOCS(`cloud/groups`, 2), {groupid: gid})
|
||||
.then((response) => context.commit('removeGroup', gid))
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add user to group
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {string} options.userid User id
|
||||
* @param {string} options.gid Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addUserGroup(context, { userid, gid }) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(OC.linkToOCS(`cloud/users/${userid}/groups`, 2), { groupid: gid })
|
||||
.then((response) => context.commit('addUserGroup', { userid, gid }))
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove user from group
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {string} options.userid User id
|
||||
* @param {string} options.gid Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeUserGroup(context, { userid, gid }) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.delete(OC.linkToOCS(`cloud/users/${userid}/groups`, 2), { groupid: gid })
|
||||
.then((response) => context.commit('removeUserGroup', { userid, gid }))
|
||||
.catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add user to group admin
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {string} options.userid User id
|
||||
* @param {string} options.gid Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addUserSubAdmin(context, { userid, gid }) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(OC.linkToOCS(`cloud/users/${userid}/subadmins`, 2), { groupid: gid })
|
||||
.then((response) => context.commit('addUserSubAdmin', { userid, gid }))
|
||||
.catch((error) => context.commit('API_FAILURE', error));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove user from group admin
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {string} options.userid User id
|
||||
* @param {string} options.gid Group id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
removeUserSubAdmin(context, { userid, gid }) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.delete(OC.linkToOCS(`cloud/users/${userid}/subadmins`, 2), { groupid: gid })
|
||||
.then((response) => context.commit('removeUserSubAdmin', { userid, gid }))
|
||||
.catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a user
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {string} userid User id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
deleteUser(context, userid) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.delete(OC.linkToOCS(`cloud/users/${userid}`, 2))
|
||||
.then((response) => context.commit('deleteUser', userid))
|
||||
.catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a user
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {string} options.userid User id
|
||||
* @param {string} options.password User password
|
||||
* @param {string} options.email User email
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addUser({context, dispatch}, {userid, password, email, groups}) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.post(OC.linkToOCS(`cloud/users`, 2), {userid, password, email, groups})
|
||||
.then((response) => dispatch('addUserData', userid))
|
||||
.catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get user data and commit addition
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {string} userid User id
|
||||
* @returns {Promise}
|
||||
*/
|
||||
addUserData(context, userid) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.get(OC.linkToOCS(`cloud/users/${userid}`, 2))
|
||||
.then((response) => context.commit('addUserData', response))
|
||||
.catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
});
|
||||
},
|
||||
|
||||
/** Enable or disable user
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {string} options.userid User id
|
||||
* @param {boolean} options.enabled User enablement status
|
||||
* @returns {Promise}
|
||||
*/
|
||||
enableDisableUser(context, { userid, enabled = true }) {
|
||||
let userStatus = enabled ? 'enable' : 'disable';
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.put(OC.linkToOCS(`cloud/users/${userid}/${userStatus}`, 2))
|
||||
.then((response) => context.commit('enableDisableUser', { userid, enabled }))
|
||||
.catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit user data
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {Object} options
|
||||
* @param {string} options.userid User id
|
||||
* @param {string} options.key User field to edit
|
||||
* @param {string} options.value Value of the change
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setUserData(context, { userid, key, value }) {
|
||||
if (['email', 'quota', 'displayname', 'password'].indexOf(key) !== -1) {
|
||||
// We allow empty email or displayname
|
||||
if (typeof value === 'string' &&
|
||||
(
|
||||
(['quota', 'password'].indexOf(key) !== -1 && value.length > 0) ||
|
||||
['email', 'displayname'].indexOf(key) !== -1
|
||||
)
|
||||
) {
|
||||
return api.requireAdmin().then((response) => {
|
||||
return api.put(OC.linkToOCS(`cloud/users/${userid}`, 2), { key: key, value: value })
|
||||
.then((response) => context.commit('setUserData', { userid, key, value }))
|
||||
.catch((error) => context.commit('API_FAILURE', { userid, error }));
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.reject(new Error('Invalid request data'));
|
||||
}
|
||||
};
|
||||
|
||||
export default { state, mutations, getters, actions };
|
152
settings/src/views/Users.vue
Normal file
152
settings/src/views/Users.vue
Normal file
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<app-navigation :menu="menu">
|
||||
<template slot="settings-content">
|
||||
<div>
|
||||
<input type="checkbox" id="showLastLogin" class="checkbox"
|
||||
:checked="showLastLogin" v-model="showLastLogin">
|
||||
<label for="showLastLogin">{{t('settings', 'Show last login')}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="showUserBackend" class="checkbox"
|
||||
:checked="showUserBackend" v-model="showUserBackend">
|
||||
<label for="showUserBackend">{{t('settings', 'Show user backend')}}</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" id="showStoragePath" class="checkbox"
|
||||
:checked="showStoragePath" v-model="showStoragePath">
|
||||
<label for="showStoragePath">{{t('settings', 'Show storage path')}}</label>
|
||||
</div>
|
||||
</template>
|
||||
</app-navigation>
|
||||
<user-list :users="users" :showConfig="showConfig" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import appNavigation from '../components/appNavigation';
|
||||
import userList from '../components/userList';
|
||||
import Vue from 'vue';
|
||||
import VueLocalStorage from 'vue-localstorage'
|
||||
Vue.use(VueLocalStorage)
|
||||
|
||||
export default {
|
||||
name: 'Users',
|
||||
components: {
|
||||
appNavigation,
|
||||
userList
|
||||
},
|
||||
beforeMount() {
|
||||
this.$store.commit('initGroups', {
|
||||
groups: this.$store.getters.getServerData.groups,
|
||||
orderBy: this.$store.getters.getServerData.sortGroups
|
||||
});
|
||||
this.$store.dispatch('getPasswordPolicyMinLength');
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showConfig: {
|
||||
showStoragePath: false,
|
||||
showUserBackend: false,
|
||||
showLastLogin: false,
|
||||
showNewUserForm: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getLocalstorage(key) {
|
||||
// force initialization
|
||||
this.showConfig[key] = this.$localStorage.get(key) === 'true';
|
||||
return this.showConfig[key];
|
||||
},
|
||||
setLocalStorage(key, status) {
|
||||
this.showConfig[key] = status;
|
||||
this.$localStorage.set(key, status);
|
||||
return status;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
users() {
|
||||
return this.$store.getters.getUsers;
|
||||
},
|
||||
loading() {
|
||||
return Object.keys(this.users).length === 0;
|
||||
},
|
||||
usersOffset() {
|
||||
return this.$store.getters.getUsersOffset;
|
||||
},
|
||||
usersLimit() {
|
||||
return this.$store.getters.getUsersLimit;
|
||||
},
|
||||
showLastLogin: {
|
||||
get: function() {return this.getLocalstorage('showLastLogin')},
|
||||
set: function(status) {
|
||||
this.setLocalStorage('showLastLogin', status);
|
||||
}
|
||||
},
|
||||
showUserBackend: {
|
||||
get: function() {return this.getLocalstorage('showUserBackend')},
|
||||
set: function(status) {
|
||||
this.setLocalStorage('showUserBackend', status);
|
||||
}
|
||||
},
|
||||
showStoragePath: {
|
||||
get: function() {return this.getLocalstorage('showStoragePath')},
|
||||
set: function(status) {
|
||||
this.setLocalStorage('showStoragePath', status);
|
||||
}
|
||||
},
|
||||
menu() {
|
||||
let self = this;
|
||||
// Data provided php side
|
||||
let groups = this.$store.getters.getGroups;
|
||||
groups = Array.isArray(groups) ? groups : [];
|
||||
|
||||
// Map groups
|
||||
groups = groups.map(group => {
|
||||
let item = {};
|
||||
item.id = group.id.replace(' ', '_');
|
||||
item.classes = [];
|
||||
item.href = '#group'+group.id.replace(' ', '_');
|
||||
item.text = group.name;
|
||||
item.utils = {counter: group.usercount};
|
||||
return item;
|
||||
});
|
||||
|
||||
// Adjust data
|
||||
if (groups[0].id === 'admin') {
|
||||
groups[0].text = t('settings', 'Admins');} // rename admin group
|
||||
if (groups[1].id === '_disabled') {
|
||||
groups[1].text = t('settings', 'Disabled users'); // rename disabled group
|
||||
if (groups[1].utils.counter === 0) {
|
||||
groups.splice(1, 1); // remove disabled if empty
|
||||
}
|
||||
}
|
||||
|
||||
// Add everyone group
|
||||
groups.unshift({
|
||||
id: '_everyone',
|
||||
classes: ['active'],
|
||||
href:'#group_everyone',
|
||||
text: t('settings', 'Everyone'),
|
||||
utils: {counter: this.users.length}
|
||||
});
|
||||
|
||||
// Return
|
||||
return {
|
||||
id: 'usergrouplist',
|
||||
new: {
|
||||
id:'new-user-button',
|
||||
text: t('settings','New user'),
|
||||
icon: 'icon-add',
|
||||
action: function(){self.showConfig.showNewUserForm=!self.showConfig.showNewUserForm}
|
||||
},
|
||||
items: groups
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
|
@ -1,9 +1,27 @@
|
|||
<?php /**
|
||||
* Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/?>
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018, John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
||||
* @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This is the default empty template to load Vue!
|
||||
* Do your cbackend computations into a php files
|
||||
* then serve this file as template and include your data into
|
||||
* the $serverData template variable
|
||||
*
|
||||
* $tmpl = new OC_Template('settings', 'settings', 'user');
|
||||
* $tmpl->assign('serverData', $serverData);
|
||||
* $tmpl->printPage();
|
||||
|
||||
<?php foreach($_['forms'] as $form) {
|
||||
print_unescaped($form);
|
||||
}
|
||||
*/
|
||||
|
||||
script('settings', 'main');
|
||||
style('settings', 'settings');
|
||||
|
||||
// Did we have some data to inject ?
|
||||
if(is_array($_['serverData'])) {
|
||||
$serverData = json_encode($_['serverData']);
|
||||
?>
|
||||
<span id="serverData" data-server="<?php p($serverData);?>"></span>
|
||||
<?php } ?>
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2011, Robin Appelman <icewind1991@gmail.com>
|
||||
* Copyright (c) 2017, John Molakvoæ <skjnldsv@protonmail.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
script('settings', [
|
||||
'users/deleteHandler',
|
||||
'users/filter',
|
||||
'users/users',
|
||||
'users/groups'
|
||||
]);
|
||||
script('core', [
|
||||
'multiselect',
|
||||
'singleselect'
|
||||
]);
|
||||
style('settings', 'settings');
|
||||
|
||||
$userlistParams = array();
|
||||
$allGroups=array();
|
||||
foreach($_["adminGroup"] as $group) {
|
||||
$allGroups[$group['id']] = array('displayName' => $group['name']);
|
||||
}
|
||||
foreach($_["groups"] as $group) {
|
||||
$allGroups[$group['id']] = array('displayName' => $group['name']);
|
||||
}
|
||||
$userlistParams['subadmingroups'] = $allGroups;
|
||||
$userlistParams['allGroups'] = json_encode($allGroups);
|
||||
$items = array_flip($userlistParams['subadmingroups']);
|
||||
unset($items['admin']);
|
||||
$userlistParams['subadmingroups'] = array_flip($items);
|
||||
|
||||
translation('settings');
|
||||
?>
|
||||
|
||||
<div id="app-navigation">
|
||||
<?php print_unescaped($this->inc('users/part.createuser')); ?>
|
||||
<?php print_unescaped($this->inc('users/part.grouplist')); ?>
|
||||
<div id="app-settings">
|
||||
<div id="app-settings-header">
|
||||
<button class="settings-button" tabindex="0" data-apps-slide-toggle="#app-settings-content"><?php p($l->t('Settings'));?></button>
|
||||
</div>
|
||||
<div id="app-settings-content">
|
||||
<?php print_unescaped($this->inc('users/part.setquota')); ?>
|
||||
|
||||
<div id="userlistoptions">
|
||||
<p>
|
||||
<input type="checkbox" name="StorageLocation" value="StorageLocation" id="CheckboxStorageLocation"
|
||||
class="checkbox" <?php if ($_['show_storage_location'] === 'true') print_unescaped('checked="checked"'); ?> />
|
||||
<label for="CheckboxStorageLocation">
|
||||
<?php p($l->t('Show storage location')) ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" name="UserBackend" value="UserBackend" id="CheckboxUserBackend"
|
||||
class="checkbox" <?php if ($_['show_backend'] === 'true') print_unescaped('checked="checked"'); ?> />
|
||||
<label for="CheckboxUserBackend">
|
||||
<?php p($l->t('Show user backend')) ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" name="LastLogin" value="LastLogin" id="CheckboxLastLogin"
|
||||
class="checkbox" <?php if ($_['show_last_login'] === 'true') print_unescaped('checked="checked"'); ?> />
|
||||
<label for="CheckboxLastLogin">
|
||||
<?php p($l->t('Show last login')) ?>
|
||||
</label>
|
||||
</p>
|
||||
<p class="info-text">
|
||||
<?php p($l->t('When the password of a new user is left empty, an activation email with a link to set the password is sent.')) ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="app-content">
|
||||
<?php print_unescaped($this->inc('users/part.userlist', $userlistParams)); ?>
|
||||
</div>
|
|
@ -1,3 +0,0 @@
|
|||
<div class="app-navigation-new">
|
||||
<button type="button" id="new-user-button" class="icon-add"><?php p($l->t('Add user'))?></button>
|
||||
</div>
|
|
@ -1,69 +0,0 @@
|
|||
<ul id="usergrouplist" data-sort-groups="<?php p($_['sortGroups']); ?>">
|
||||
<!-- Add new group -->
|
||||
<?php if ($_['isAdmin']) { ?>
|
||||
<li id="newgroup-entry">
|
||||
<a href="#" class="icon-add" id="newgroup-init"><?php p($l->t('Add group'))?></a>
|
||||
<div class="app-navigation-entry-edit" id="newgroup-form">
|
||||
<form>
|
||||
<input type="text" id="newgroupname" placeholder="<?php p($l->t('Add group'))?>">
|
||||
<input type="submit" value="" class="icon-checkmark">
|
||||
</form>
|
||||
</div>
|
||||
</li>
|
||||
<?php } ?>
|
||||
<!-- Everyone -->
|
||||
<li id="everyonegroup" data-gid="_everyone" data-usercount="" class="isgroup">
|
||||
<a href="#">
|
||||
<span class="groupname">
|
||||
<?php p($l->t('Everyone')); ?>
|
||||
</span>
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="usercount app-navigation-entry-utils-counter" id="everyonecount"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- The Admin Group -->
|
||||
<?php foreach($_["adminGroup"] as $adminGroup): ?>
|
||||
<li data-gid="admin" data-usercount="<?php if($adminGroup['usercount'] > 0) { p($adminGroup['usercount']); } ?>" class="isgroup">
|
||||
<a href="#"><span class="groupname"><?php p($l->t('Admins')); ?></span></a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter"><?php if($adminGroup['usercount'] > 0) { p($adminGroup['usercount']); } ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<!-- Disabled Users -->
|
||||
<?php $disabledUsersGroup = $_["disabledUsersGroup"] ?>
|
||||
<li data-gid="_disabledUsers" data-usercount="<?php if($disabledUsersGroup['usercount'] > 0) { p($disabledUsersGroup['usercount']); } ?>" class="isgroup">
|
||||
<a href="#"><span class="groupname"><?php p($l->t('Disabled')); ?></span></a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter"><?php if($disabledUsersGroup['usercount'] > 0) { p($disabledUsersGroup['usercount']); } ?></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!--List of Groups-->
|
||||
<?php foreach($_["groups"] as $group): ?>
|
||||
<li data-gid="<?php p($group['id']) ?>" data-usercount="<?php p($group['usercount']) ?>" class="isgroup">
|
||||
<a href="#" class="dorename">
|
||||
<span class="groupname"><?php p($group['name']); ?></span>
|
||||
</a>
|
||||
<div class="app-navigation-entry-utils">
|
||||
<ul>
|
||||
<li class="app-navigation-entry-utils-counter"><?php if($group['usercount'] > 0) { p($group['usercount']); } ?></li>
|
||||
<?php if($_['isAdmin']): ?>
|
||||
<li class="app-navigation-entry-utils-menu-button delete">
|
||||
<button class="icon-delete"></button>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
|
@ -1,35 +0,0 @@
|
|||
<div class="quota">
|
||||
<!-- Default storage -->
|
||||
<span><?php p($l->t('Default quota'));?></span>
|
||||
<?php if((bool) $_['isAdmin']): ?>
|
||||
<select id='default_quota' data-inputtitle="<?php p($l->t('Please enter storage quota (ex: "512 MB" or "12 GB")')) ?>" data-tipsy-gravity="s">
|
||||
<option <?php if($_['default_quota'] === 'none') print_unescaped('selected="selected"');?> value='none'>
|
||||
<?php p($l->t('Unlimited'));?>
|
||||
</option>
|
||||
<?php foreach($_['quota_preset'] as $preset):?>
|
||||
<?php if($preset !== 'default'):?>
|
||||
<option <?php if($_['default_quota']==$preset) print_unescaped('selected="selected"');?> value='<?php p($preset);?>'>
|
||||
<?php p($preset);?>
|
||||
</option>
|
||||
<?php endif;?>
|
||||
<?php endforeach;?>
|
||||
<?php if($_['defaultQuotaIsUserDefined']):?>
|
||||
<option selected="selected" value='<?php p($_['default_quota']);?>'>
|
||||
<?php p($_['default_quota']);?>
|
||||
</option>
|
||||
<?php endif;?>
|
||||
<option data-new value='other'>
|
||||
<?php p($l->t('Other'));?>
|
||||
...
|
||||
</option>
|
||||
</select>
|
||||
<?php endif; ?>
|
||||
<?php if((bool) !$_['isAdmin']): ?>
|
||||
:
|
||||
<?php if( $_['default_quota'] === 'none'): ?>
|
||||
<?php p($l->t('Unlimited'));?>
|
||||
<?php else: ?>
|
||||
<?php p($_['default_quota']);?>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
|
@ -1,149 +0,0 @@
|
|||
<form class="newUserMenu" id="newuser" autocomplete="off">
|
||||
<table id="userlist" class="grid" data-groups="<?php p($_['allGroups']);?>">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="headerAvatar" scope="col"></th>
|
||||
<th id="headerName" scope="col"><?php p($l->t('Username'))?></th>
|
||||
<th id="headerDisplayName" scope="col"><?php p($l->t( 'Full name' )); ?></th>
|
||||
<th id="headerPassword" scope="col"><?php p($l->t( 'Password' )); ?></th>
|
||||
<th class="mailAddress" scope="col"><?php p($l->t( 'Email' )); ?></th>
|
||||
<th id="headerGroups" scope="col"><?php p($l->t( 'Groups' )); ?></th>
|
||||
<?php if(is_array($_['subadmins']) || $_['subadmins']): ?>
|
||||
<th id="headerSubAdmins" scope="col"><?php p($l->t('Group admin for')); ?></th>
|
||||
<?php endif;?>
|
||||
<?php if((bool)$_['recoveryAdminEnabled']): ?>
|
||||
<th id="recoveryPassword" scope="col"><?php p($l->t('Recovery password')); ?></th>
|
||||
<?php endif; ?>
|
||||
<th id="headerQuota" scope="col"><?php p($l->t('Quota')); ?></th>
|
||||
<th class="storageLocation" scope="col"><?php p($l->t('Storage location')); ?></th>
|
||||
<th class="userBackend" scope="col"><?php p($l->t('User backend')); ?></th>
|
||||
<th class="lastLogin" scope="col"><?php p($l->t('Last login')); ?></th>
|
||||
<th class="userActions"></th>
|
||||
</tr>
|
||||
<tr id="newuserHeader" style="display:none">
|
||||
<td class="icon-add"></td>
|
||||
<td class="name">
|
||||
<input id="newusername" type="text" required
|
||||
placeholder="<?php p($l->t('Username'))?>" name="username"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off" />
|
||||
</td>
|
||||
<td class="displayName">
|
||||
<input id="newdisplayname" type="text"
|
||||
placeholder="<?php p($l->t('Full name'))?>" name="displayname"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off" />
|
||||
</td>
|
||||
<td class="password">
|
||||
<input id="newuserpassword" type="password"
|
||||
placeholder="<?php p($l->t('Password'))?>" name="password"
|
||||
autocomplete="new-password" autocapitalize="none" autocorrect="off" />
|
||||
</td>
|
||||
<td class="mailAddress">
|
||||
<input id="newemail" type="email"
|
||||
placeholder="<?php p($l->t('E-Mail'))?>" name="email"
|
||||
autocomplete="off" autocapitalize="none" autocorrect="off" />
|
||||
</td>
|
||||
<td class="groups">
|
||||
<div class="groupsListContainer multiselect button" data-placeholder="<?php p($l->t('Groups'))?>">
|
||||
<span class="title groupsList"></span>
|
||||
<span class="icon-triangle-s"></span>
|
||||
</div>
|
||||
</td>
|
||||
<?php if(is_array($_['subadmins']) || $_['subadmins']): ?>
|
||||
<td></td>
|
||||
<?php endif;?>
|
||||
<?php if((bool)$_['recoveryAdminEnabled']): ?>
|
||||
<td class="recoveryPassword">
|
||||
<input id="recoveryPassword"
|
||||
type="password"
|
||||
placeholder="<?php p($l->t('Admin Recovery Password'))?>"
|
||||
title="<?php p($l->t('Enter the recovery password in order to recover the users files during password change'))?>"
|
||||
alt="<?php p($l->t('Enter the recovery password in order to recover the users files during password change'))?>"/>
|
||||
</td>
|
||||
<?php endif; ?>
|
||||
<td class="quota"></td>
|
||||
<td class="storageLocation" scope="col"></td>
|
||||
<td class="userBackend" scope="col"></td>
|
||||
<td class="lastLogin" scope="col"></td>
|
||||
<td class="userActions">
|
||||
<input type="submit" id="newsubmit" class="button primary icon-checkmark-white has-tooltip" value="" title="<?php p($l->t('Add user'))?>" />
|
||||
<input type="reset" id="newreset" class="button icon-close has-tooltip" value="" title="<?php p($l->t('Cancel'))?>" />
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- the following <tr> is used as a template for the JS part -->
|
||||
<tr style="display:none">
|
||||
<td class="avatar"><div class="avatardiv"></div></td>
|
||||
<td class="name" scope="row"></td>
|
||||
<td class="displayName"><span></span> <img class="action"
|
||||
src="<?php p(image_path('core', 'actions/rename.svg'))?>"
|
||||
alt="<?php p($l->t('change full name'))?>" title="<?php p($l->t('change full name'))?>"/>
|
||||
</td>
|
||||
<td class="password"><span>●●●●●●●</span> <img class="action"
|
||||
src="<?php print_unescaped(image_path('core', 'actions/rename.svg'))?>"
|
||||
alt="<?php p($l->t('set new password'))?>" title="<?php p($l->t('set new password'))?>"/>
|
||||
</td>
|
||||
<td class="mailAddress"><span></span><div class="loading-small hidden"></div> <img class="action"
|
||||
src="<?php p(image_path('core', 'actions/rename.svg'))?>"
|
||||
alt="<?php p($l->t('change email address'))?>" title="<?php p($l->t('change email address'))?>"/>
|
||||
</td>
|
||||
<td class="groups"><div class="groupsListContainer multiselect button"
|
||||
><span class="title groupsList"></span><span class="icon-triangle-s"></span></div>
|
||||
</td>
|
||||
<?php if(is_array($_['subadmins']) || $_['subadmins']): ?>
|
||||
<td class="subadmins"><div class="groupsListContainer multiselect button"
|
||||
><span class="title groupsList"></span><span class="icon-triangle-s"></span></div>
|
||||
</td>
|
||||
<?php endif;?>
|
||||
<?php if((bool)$_['recoveryAdminEnabled']): ?>
|
||||
<td></td>
|
||||
<?php endif; ?>
|
||||
<td class="quota">
|
||||
<select class="quota-user" data-inputtitle="<?php p($l->t('Please enter storage quota (ex: "512 MB" or "12 GB")')) ?>">
|
||||
<option value='default'>
|
||||
<?php p($l->t('Default'));?>
|
||||
</option>
|
||||
<option value='none'>
|
||||
<?php p($l->t('Unlimited'));?>
|
||||
</option>
|
||||
<?php foreach($_['quota_preset'] as $preset):?>
|
||||
<option value='<?php p($preset);?>'>
|
||||
<?php p($preset);?>
|
||||
</option>
|
||||
<?php endforeach;?>
|
||||
<option value='other' data-new>
|
||||
<?php p($l->t('Other'));?> ...
|
||||
</option>
|
||||
</select>
|
||||
<progress class="quota-user-progress" value="" max="100"></progress>
|
||||
</td>
|
||||
<td class="storageLocation"></td>
|
||||
<td class="userBackend"></td>
|
||||
<td class="lastLogin"></td>
|
||||
<td class="userActions">
|
||||
<div class="toggleUserActions">
|
||||
<a class="action"><span class="icon-more"></span></a>
|
||||
<div class="popovermenu">
|
||||
<ul class="userActionsMenu">
|
||||
<li>
|
||||
<a href="#" class="menuitem action-togglestate permanent" data-action="togglestate"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="menuitem action-remove permanent" data-action="remove">
|
||||
<span class="icon icon-delete"></span>
|
||||
<span><?php p($l->t('Delete')); ?></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
|
||||
<div class="emptycontent" style="display:none">
|
||||
<div class="icon-search"></div>
|
||||
<h2></h2>
|
||||
</div>
|
|
@ -1,220 +0,0 @@
|
|||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Vincent Petry
|
||||
* @copyright 2014 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('DeleteHandler tests', function() {
|
||||
var showNotificationSpy;
|
||||
var hideNotificationSpy;
|
||||
var clock;
|
||||
var removeCallback;
|
||||
var markCallback;
|
||||
var undoCallback;
|
||||
|
||||
function init(markCallback, removeCallback, undoCallback) {
|
||||
var handler = new DeleteHandler('dummyendpoint.php', 'paramid', markCallback, removeCallback);
|
||||
handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry', undoCallback);
|
||||
return handler;
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
showNotificationSpy = sinon.spy(OC.Notification, 'showHtml');
|
||||
hideNotificationSpy = sinon.spy(OC.Notification, 'hide');
|
||||
clock = sinon.useFakeTimers();
|
||||
removeCallback = sinon.stub();
|
||||
markCallback = sinon.stub();
|
||||
undoCallback = sinon.stub();
|
||||
|
||||
$('#testArea').append('<div id="notification"></div>');
|
||||
});
|
||||
afterEach(function() {
|
||||
showNotificationSpy.restore();
|
||||
hideNotificationSpy.restore();
|
||||
clock.restore();
|
||||
});
|
||||
it('shows a notification when marking for delete', function() {
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.mark('some_uid');
|
||||
|
||||
expect(showNotificationSpy.calledOnce).toEqual(true);
|
||||
expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_uid entry');
|
||||
|
||||
expect(markCallback.calledOnce).toEqual(true);
|
||||
expect(markCallback.getCall(0).args[0]).toEqual('some_uid');
|
||||
expect(removeCallback.notCalled).toEqual(true);
|
||||
expect(undoCallback.notCalled).toEqual(true);
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(0);
|
||||
});
|
||||
it('deletes first entry and reshows notification on second delete', function() {
|
||||
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
|
||||
204,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({status: 'success'})
|
||||
]);
|
||||
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_other_uid/, [
|
||||
204,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({status: 'success'})
|
||||
]);
|
||||
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.mark('some_uid');
|
||||
|
||||
expect(showNotificationSpy.calledOnce).toEqual(true);
|
||||
expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_uid entry');
|
||||
showNotificationSpy.resetHistory();
|
||||
|
||||
handler.mark('some_other_uid');
|
||||
|
||||
expect(hideNotificationSpy.calledOnce).toEqual(true);
|
||||
expect(showNotificationSpy.calledOnce).toEqual(true);
|
||||
expect(showNotificationSpy.getCall(0).args[0]).toEqual('removed some_other_uid entry');
|
||||
|
||||
expect(markCallback.calledTwice).toEqual(true);
|
||||
expect(markCallback.getCall(0).args[0]).toEqual('some_uid');
|
||||
expect(markCallback.getCall(1).args[0]).toEqual('some_other_uid');
|
||||
// called only once, because it is called once the second user is deleted
|
||||
expect(removeCallback.calledOnce).toEqual(true);
|
||||
expect(undoCallback.notCalled).toEqual(true);
|
||||
|
||||
// previous one was delete
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
var request = fakeServer.requests[0];
|
||||
expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
|
||||
});
|
||||
it('automatically deletes after timeout', function() {
|
||||
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
|
||||
204,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({status: 'success'})
|
||||
]);
|
||||
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.mark('some_uid');
|
||||
|
||||
clock.tick(5000);
|
||||
// nothing happens yet
|
||||
expect(fakeServer.requests.length).toEqual(0);
|
||||
|
||||
clock.tick(3000);
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
var request = fakeServer.requests[0];
|
||||
expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
|
||||
});
|
||||
it('deletes when deleteEntry is called', function() {
|
||||
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({status: 'success'})
|
||||
]);
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.mark('some_uid');
|
||||
|
||||
handler.deleteEntry();
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
var request = fakeServer.requests[0];
|
||||
expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid');
|
||||
});
|
||||
it('deletes when deleteEntry is called and escapes', function() {
|
||||
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({status: 'success'})
|
||||
]);
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.mark('some_uid<>/"..\\');
|
||||
|
||||
handler.deleteEntry();
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
var request = fakeServer.requests[0];
|
||||
expect(request.url).toEqual(OC.webroot + '/index.php/dummyendpoint.php/some_uid%3C%3E%2F%22..%5C');
|
||||
});
|
||||
it('cancels deletion when undo is clicked', function() {
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry <span class="undo">Undo</span>', undoCallback);
|
||||
handler.mark('some_uid');
|
||||
$('#notification .undo').click();
|
||||
|
||||
expect(undoCallback.calledOnce).toEqual(true);
|
||||
|
||||
// timer was cancelled
|
||||
clock.tick(10000);
|
||||
expect(fakeServer.requests.length).toEqual(0);
|
||||
});
|
||||
it('cancels deletion when cancel method is called', function() {
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.setNotification(OC.Notification, 'dataid', 'removed %oid entry <span class="undo">Undo</span>', undoCallback);
|
||||
handler.mark('some_uid');
|
||||
handler.cancel();
|
||||
|
||||
// not sure why, seems to be by design
|
||||
expect(undoCallback.notCalled).toEqual(true);
|
||||
|
||||
// timer was cancelled
|
||||
clock.tick(10000);
|
||||
expect(fakeServer.requests.length).toEqual(0);
|
||||
});
|
||||
it('calls removeCallback after successful server side deletion', function() {
|
||||
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
|
||||
200,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({status: 'success'})
|
||||
]);
|
||||
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.mark('some_uid');
|
||||
handler.deleteEntry();
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
var request = fakeServer.requests[0];
|
||||
var query = OC.parseQueryString(request.requestBody);
|
||||
|
||||
expect(removeCallback.calledOnce).toEqual(true);
|
||||
expect(undoCallback.notCalled).toEqual(true);
|
||||
expect(removeCallback.getCall(0).args[0]).toEqual('some_uid');
|
||||
});
|
||||
it('calls undoCallback and shows alert after failed server side deletion', function() {
|
||||
// stub t to avoid extra calls
|
||||
var tStub = sinon.stub(window, 't').returns('text');
|
||||
fakeServer.respondWith(/\/index\.php\/dummyendpoint.php\/some_uid/, [
|
||||
403,
|
||||
{ 'Content-Type': 'application/json' },
|
||||
JSON.stringify({status: 'error', data: {message: 'test error'}})
|
||||
]);
|
||||
|
||||
var alertDialogStub = sinon.stub(OC.dialogs, 'alert');
|
||||
var handler = init(markCallback, removeCallback, undoCallback);
|
||||
handler.mark('some_uid');
|
||||
handler.deleteEntry();
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
var request = fakeServer.requests[0];
|
||||
var query = OC.parseQueryString(request.requestBody);
|
||||
|
||||
expect(removeCallback.notCalled).toEqual(true);
|
||||
expect(undoCallback.calledOnce).toEqual(true);
|
||||
expect(undoCallback.getCall(0).args[0]).toEqual('some_uid');
|
||||
|
||||
expect(alertDialogStub.calledOnce);
|
||||
|
||||
alertDialogStub.restore();
|
||||
tStub.restore();
|
||||
});
|
||||
});
|
|
@ -17,6 +17,7 @@
|
|||
* @author Stephan Peijnik <speijnik@anexia-it.com>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @author Thomas Pulzer <t.pulzer@kniel.de>
|
||||
* @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
|
@ -41,12 +42,10 @@ OC_Util::checkSubAdminUser();
|
|||
$userManager = \OC::$server->getUserManager();
|
||||
$groupManager = \OC::$server->getGroupManager();
|
||||
$appManager = \OC::$server->getAppManager();
|
||||
|
||||
// Set the sort option: SORT_USERCOUNT or SORT_GROUPNAME
|
||||
$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
|
||||
|
||||
$config = \OC::$server->getConfig();
|
||||
|
||||
/* SORT OPTION: SORT_USERCOUNT or SORT_GROUPNAME */
|
||||
$sortGroupsBy = \OC\Group\MetaData::SORT_USERCOUNT;
|
||||
if ($config->getSystemValue('sort_groups_by_name', false)) {
|
||||
$sortGroupsBy = \OC\Group\MetaData::SORT_GROUPNAME;
|
||||
} else {
|
||||
|
@ -62,14 +61,16 @@ if ($config->getSystemValue('sort_groups_by_name', false)) {
|
|||
}
|
||||
}
|
||||
|
||||
$uid = \OC_User::getUser();
|
||||
$isAdmin = OC_User::isAdminUser($uid);
|
||||
/* ENCRYPTION CONFIG */
|
||||
$isEncryptionEnabled = \OC::$server->getEncryptionManager()->isEnabled();
|
||||
$useMasterKey = $config->getAppValue('encryption', 'useMasterKey', true);
|
||||
// If masterKey enabled, then you can change password. This is to avoid data loss!
|
||||
$canChangePassword = ($isEncryptionEnabled && $useMasterKey) || $useMasterKey;
|
||||
|
||||
$isDisabled = true;
|
||||
$user = $userManager->get($uid);
|
||||
if ($user) {
|
||||
$isDisabled = !$user->isEnabled();
|
||||
}
|
||||
|
||||
/* GROUPS */
|
||||
$uid = \OC_User::getUser();
|
||||
$isAdmin = \OC_User::isAdminUser($uid);
|
||||
|
||||
$groupsInfo = new \OC\Group\MetaData(
|
||||
$uid,
|
||||
|
@ -81,9 +82,6 @@ $groupsInfo = new \OC\Group\MetaData(
|
|||
$groupsInfo->setSorting($sortGroupsBy);
|
||||
list($adminGroup, $groups) = $groupsInfo->get();
|
||||
|
||||
$recoveryAdminEnabled = $appManager->isEnabledForUser('encryption') &&
|
||||
$config->getAppValue( 'encryption', 'recoveryAdminEnabled', '0');
|
||||
|
||||
if ($isAdmin) {
|
||||
$subAdmins = \OC::$server->getGroupManager()->getSubAdmin()->getAllSubAdmins();
|
||||
// New class returns IUser[] so convert back
|
||||
|
@ -108,42 +106,38 @@ if($isAdmin) {
|
|||
|
||||
$disabledUsers = $isLDAPUsed ? 0 : $userManager->countDisabledUsers();
|
||||
$disabledUsersGroup = [
|
||||
'id' => '_disabledUsers',
|
||||
'name' => '_disabledUsers',
|
||||
'id' => '_disabled',
|
||||
'name' => 'Disabled users',
|
||||
'usercount' => $disabledUsers
|
||||
];
|
||||
$allGroups = array_merge_recursive($adminGroup, $groups);
|
||||
|
||||
// load preset quotas
|
||||
/* QUOTAS PRESETS */
|
||||
$quotaPreset=$config->getAppValue('files', 'quota_preset', '1 GB, 5 GB, 10 GB');
|
||||
$quotaPreset=explode(',', $quotaPreset);
|
||||
foreach($quotaPreset as &$preset) {
|
||||
$preset=trim($preset);
|
||||
}
|
||||
$quotaPreset=array_diff($quotaPreset, array('default', 'none'));
|
||||
|
||||
$defaultQuota=$config->getAppValue('files', 'default_quota', 'none');
|
||||
$defaultQuotaIsUserDefined=array_search($defaultQuota, $quotaPreset)===false
|
||||
&& array_search($defaultQuota, array('none', 'default'))===false;
|
||||
|
||||
\OC::$server->getEventDispatcher()->dispatch('OC\Settings\Users::loadAdditionalScripts');
|
||||
|
||||
$tmpl = new OC_Template("settings", "users/main", "user");
|
||||
$tmpl->assign('groups', $groups);
|
||||
$tmpl->assign('sortGroups', $sortGroupsBy);
|
||||
$tmpl->assign('adminGroup', $adminGroup);
|
||||
$tmpl->assign('disabledUsersGroup', $disabledUsersGroup);
|
||||
$tmpl->assign('isAdmin', (int)$isAdmin);
|
||||
$tmpl->assign('subadmins', $subAdmins);
|
||||
$tmpl->assign('numofgroups', count($groups) + count($adminGroup));
|
||||
$tmpl->assign('quota_preset', $quotaPreset);
|
||||
$tmpl->assign('default_quota', $defaultQuota);
|
||||
$tmpl->assign('defaultQuotaIsUserDefined', $defaultQuotaIsUserDefined);
|
||||
$tmpl->assign('recoveryAdminEnabled', $recoveryAdminEnabled);
|
||||
|
||||
$tmpl->assign('show_storage_location', $config->getAppValue('core', 'umgmt_show_storage_location', 'false'));
|
||||
$tmpl->assign('show_last_login', $config->getAppValue('core', 'umgmt_show_last_login', 'false'));
|
||||
$tmpl->assign('show_email', $config->getAppValue('core', 'umgmt_show_email', 'false'));
|
||||
$tmpl->assign('show_backend', $config->getAppValue('core', 'umgmt_show_backend', 'false'));
|
||||
$tmpl->assign('send_email', $config->getAppValue('core', 'umgmt_send_email', 'false'));
|
||||
/* FINAL DATA */
|
||||
$serverData = array();
|
||||
// groups
|
||||
$serverData['groups'] = array_merge_recursive($adminGroup, [$disabledUsersGroup], $groups);
|
||||
$serverData['subadmingroups'] = $groups;
|
||||
// Various data
|
||||
$serverData['subadmins'] = $subAdmins;
|
||||
$serverData['sortGroups'] = $sortGroupsBy;
|
||||
$serverData['quotaPreset'] = $quotaPreset;
|
||||
$serverData['userCount'] = $userManager->countUsers();
|
||||
// Settings
|
||||
$serverData['defaultQuota'] = $defaultQuota;
|
||||
$serverData['canChangePassword'] = $canChangePassword;
|
||||
|
||||
// print template + vue + serve data
|
||||
$tmpl = new OC_Template('settings', 'settings', 'user');
|
||||
$tmpl->assign('serverData', $serverData);
|
||||
$tmpl->printPage();
|
||||
|
|
108
settings/webpack.config.js
Normal file
108
settings/webpack.config.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
var path = require('path')
|
||||
var webpack = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
entry: './src/main.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, './js'),
|
||||
publicPath: '/dist/',
|
||||
filename: 'main.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader'
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.sass$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader',
|
||||
'sass-loader?indentedSyntax'
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {
|
||||
loaders: {
|
||||
// Since sass-loader (weirdly) has SCSS as its default parse mode, we map
|
||||
// the "scss" and "sass" values for the lang attribute to the right configs here.
|
||||
// other preprocessors should work out of the box, no loader config like this necessary.
|
||||
'scss': [
|
||||
'vue-style-loader',
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
],
|
||||
'sass': [
|
||||
'vue-style-loader',
|
||||
'css-loader',
|
||||
'sass-loader?indentedSyntax'
|
||||
]
|
||||
}
|
||||
// other vue-loader options go here
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: '[name].[ext]?[hash]'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js'
|
||||
},
|
||||
extensions: ['*', '.js', '.vue', '.json']
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
noInfo: true,
|
||||
overlay: true
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
devtool: '#eval-source-map'
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports.devtool = '#source-map'
|
||||
// http://vue-loader.vuejs.org/en/workflow/production.html
|
||||
module.exports.plugins = (module.exports.plugins || []).concat([
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: true,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true
|
||||
})
|
||||
])
|
||||
}
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue