mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-06 20:29:54 -06:00
Merge pull request #115 from owncloud/feature/create-delete-accounts
Add create/delete capabilities and UI
This commit is contained in:
6
changelog/unreleased/add-create-form.md
Normal file
6
changelog/unreleased/add-create-form.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Enhancement: Add create account form
|
||||
|
||||
We've added a form to create new users above the accounts list.
|
||||
|
||||
https://github.com/owncloud/product/issues/148
|
||||
https://github.com/owncloud/ocis-accounts/pull/115
|
||||
6
changelog/unreleased/add-delete-action.md
Normal file
6
changelog/unreleased/add-delete-action.md
Normal file
@@ -0,0 +1,6 @@
|
||||
Enhancement: Add delete accounts action
|
||||
|
||||
We've added an action into the actions dropdown to enable admins to delete users.
|
||||
|
||||
https://github.com/owncloud/product/issues/148
|
||||
https://github.com/owncloud/ocis-accounts/pull/115
|
||||
6
go.sum
6
go.sum
@@ -912,6 +912,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -1042,7 +1043,9 @@ github.com/micro/micro/v2 v2.0.1-0.20200210100719-f38a1d8d5348/go.mod h1:MQBt/cB
|
||||
github.com/micro/micro/v2 v2.5.1-0.20200418121137-24e9b206767c/go.mod h1:fqqaYbJGYzSBi7Ms2Adly7Xzw9+WIRBAucUjwGmYeFY=
|
||||
github.com/micro/micro/v2 v2.8.0 h1:AMqpnKsOBnuGHjU0jVmTL17BRdsOx0FbvI/Gkl2uLrA=
|
||||
github.com/micro/micro/v2 v2.8.0/go.mod h1:VTIGqEBLAMh22q72DnGd95iJSQY/3yvXd9GIIooQ69c=
|
||||
github.com/micro/protoc-gen-micro v1.0.0 h1:qKh5S3I1RfenhIs5mqDFJLwRlRDlgin7XWiUKZbpwLM=
|
||||
github.com/micro/protoc-gen-micro v1.0.0/go.mod h1:C8ij4DJhapBmypcT00AXdb0cZ675/3PqUO02buWWqbE=
|
||||
github.com/micro/protoc-gen-micro/v2 v2.3.0 h1:PBbGeNh4BOy1w4eRdeo4yWJJNWGLnaJX6/h55I74EXE=
|
||||
github.com/micro/protoc-gen-micro/v2 v2.3.0/go.mod h1:gcsUvKSTTTalq+pqdUbFS40OTsURpYgL5+yUguR1djk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
@@ -1157,6 +1160,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
@@ -1164,6 +1168,7 @@ github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
@@ -1979,6 +1984,7 @@ google.golang.org/genproto v0.0.0-20200624020401-64a14ca9d1ad h1:uAwc13+y0Y8QZLT
|
||||
google.golang.org/genproto v0.0.0-20200624020401-64a14ca9d1ad/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc/examples v0.0.0-20200824180931-410880dd7d91 h1:eUaF7ghTaPu2Ivm9aqGW31Zr9aVB8k1KO1m3lo7lbj8=
|
||||
google.golang.org/grpc/examples v0.0.0-20200824180931-410880dd7d91/go.mod h1:wQWkdCkP0Pl3MzFPvfqTNUnXA2eIVY4eakDiKJvniKc=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
||||
@@ -76,5 +76,8 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"owncloud-design-system": "^1.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"validator": "^13.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -294,14 +294,15 @@ func NewGroupsServiceEndpoints() []*api.Endpoint {
|
||||
&api.Endpoint{
|
||||
Name: "GroupsService.RemoveMember",
|
||||
Path: []string{"/api/v0/groups/{group_id=*}/members/{account_id}/$ref"},
|
||||
Method: []string{"DELETE"},
|
||||
Method: []string{"POST"},
|
||||
Body: "*",
|
||||
Handler: "rpc",
|
||||
},
|
||||
&api.Endpoint{
|
||||
Name: "GroupsService.ListMembers",
|
||||
Path: []string{"/api/v0/groups/{id=*}/members/$ref"},
|
||||
Method: []string{"GET"},
|
||||
Method: []string{"POST"},
|
||||
Body: "*",
|
||||
Handler: "rpc",
|
||||
},
|
||||
}
|
||||
@@ -501,14 +502,15 @@ func RegisterGroupsServiceHandler(s server.Server, hdlr GroupsServiceHandler, op
|
||||
opts = append(opts, api.WithEndpoint(&api.Endpoint{
|
||||
Name: "GroupsService.RemoveMember",
|
||||
Path: []string{"/api/v0/groups/{group_id=*}/members/{account_id}/$ref"},
|
||||
Method: []string{"DELETE"},
|
||||
Method: []string{"POST"},
|
||||
Body: "*",
|
||||
Handler: "rpc",
|
||||
}))
|
||||
opts = append(opts, api.WithEndpoint(&api.Endpoint{
|
||||
Name: "GroupsService.ListMembers",
|
||||
Path: []string{"/api/v0/groups/{id=*}/members/$ref"},
|
||||
Method: []string{"GET"},
|
||||
Method: []string{"POST"},
|
||||
Body: "*",
|
||||
Handler: "rpc",
|
||||
}))
|
||||
return s.Handle(s.NewHandler(&GroupsService{h}, opts...))
|
||||
|
||||
@@ -328,7 +328,7 @@ func (h *webGroupsServiceHandler) RemoveMember(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.Status(r, http.StatusCreated)
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ func (h *webGroupsServiceHandler) ListMembers(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.Status(r, http.StatusCreated)
|
||||
render.JSON(w, r, resp)
|
||||
}
|
||||
|
||||
@@ -368,8 +368,8 @@ func RegisterGroupsServiceWeb(r chi.Router, i GroupsServiceHandler, middlewares
|
||||
r.MethodFunc("POST", "/api/v0/accounts/groups-update", handler.UpdateGroup)
|
||||
r.MethodFunc("POST", "/api/v0/accounts/groups-delete", handler.DeleteGroup)
|
||||
r.MethodFunc("POST", "/api/v0/groups/{group_id=*}/members/$ref", handler.AddMember)
|
||||
r.MethodFunc("DELETE", "/api/v0/groups/{group_id=*}/members/{account_id}/$ref", handler.RemoveMember)
|
||||
r.MethodFunc("GET", "/api/v0/groups/{id=*}/members/$ref", handler.ListMembers)
|
||||
r.MethodFunc("POST", "/api/v0/groups/{group_id=*}/members/{account_id}/$ref", handler.RemoveMember)
|
||||
r.MethodFunc("POST", "/api/v0/groups/{id=*}/members/$ref", handler.ListMembers)
|
||||
}
|
||||
|
||||
// ListAccountsRequestJSONMarshaler describes the default jsonpb.Marshaler used by all
|
||||
|
||||
@@ -119,7 +119,8 @@ service GroupsService {
|
||||
rpc RemoveMember(RemoveMemberRequest) returns (Group) {
|
||||
// All request parameters go into body.
|
||||
option (google.api.http) = {
|
||||
delete: "/api/v0/groups/{group_id=*}/members/{account_id}/$ref"
|
||||
// URLs are broken
|
||||
post: "/api/v0/groups/{group_id=*}/members/{account_id}/$ref"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
@@ -127,7 +128,8 @@ service GroupsService {
|
||||
rpc ListMembers(ListMembersRequest) returns (ListMembersResponse) {
|
||||
// All request parameters go into body.
|
||||
option (google.api.http) = {
|
||||
get: "/api/v0/groups/{id=*}/members/$ref"
|
||||
// URLs are broken
|
||||
post: "/api/v0/groups/{id=*}/members/$ref"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v0/groups/groups-create": {
|
||||
"/api/v0/accounts/groups-create": {
|
||||
"post": {
|
||||
"summary": "Creates an account",
|
||||
"operationId": "CreateGroup",
|
||||
@@ -173,7 +173,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v0/groups/groups-delete": {
|
||||
"/api/v0/accounts/groups-delete": {
|
||||
"post": {
|
||||
"summary": "Deletes an account",
|
||||
"operationId": "DeleteGroup",
|
||||
@@ -200,7 +200,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v0/groups/groups-get": {
|
||||
"/api/v0/accounts/groups-get": {
|
||||
"post": {
|
||||
"summary": "Gets an account",
|
||||
"operationId": "GetGroup",
|
||||
@@ -227,7 +227,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v0/groups/groups-list": {
|
||||
"/api/v0/accounts/groups-list": {
|
||||
"post": {
|
||||
"summary": "Lists accounts",
|
||||
"operationId": "ListGroups",
|
||||
@@ -254,7 +254,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v0/groups/groups-update": {
|
||||
"/api/v0/accounts/groups-update": {
|
||||
"post": {
|
||||
"summary": "Updates an account",
|
||||
"operationId": "UpdateGroup",
|
||||
@@ -281,7 +281,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v0/groups/{group_id}/members/$ref": {
|
||||
"/api/v0/groups/{group_id}/members/$ref": {
|
||||
"post": {
|
||||
"summary": "group:addmember https://docs.microsoft.com/en-us/graph/api/group-post-members?view=graph-rest-1.0\u0026tabs=http",
|
||||
"operationId": "AddMember",
|
||||
@@ -315,8 +315,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v0/groups/{group_id}/members/{account_id}/$ref": {
|
||||
"delete": {
|
||||
"/api/v0/groups/{group_id}/members/{account_id}/$ref": {
|
||||
"post": {
|
||||
"summary": "group:removemember https://docs.microsoft.com/en-us/graph/api/group-delete-members?view=graph-rest-1.0",
|
||||
"operationId": "RemoveMember",
|
||||
"responses": {
|
||||
@@ -341,6 +341,14 @@
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/settingsRemoveMemberRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
@@ -348,8 +356,8 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v0/groups/{id}/members/$ref": {
|
||||
"get": {
|
||||
"/api/v0/groups/{id}/members/$ref": {
|
||||
"post": {
|
||||
"summary": "group:listmembers https://docs.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0",
|
||||
"operationId": "ListMembers",
|
||||
"responses": {
|
||||
@@ -369,36 +377,12 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "page_size",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
{
|
||||
"name": "page_token",
|
||||
"description": "Optional. A pagination token returned from a previous call to `Get`\nthat indicates from where search should continue.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "field_mask.paths",
|
||||
"description": "The set of field mask paths.",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "multi"
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
"description": "Optional. Search criteria used to select the groups to return.\nIf no search criteria is specified then all groups will be\nreturned. TODO update query language\nQuery expressions can be used to restrict results based upon\nthe account properties where the operators `=`, `NOT`, `AND` and `OR`\ncan be used along with the suffix wildcard symbol `*`.\n\nThe string properties in a query expression should use escaped quotes\nfor values that include whitespace to prevent unexpected behavior.\n\nSome example queries are:\n\n* Query `display_name=Th*` returns accounts whose display_name\nstarts with \"Th\"\n* Query `display_name=\\\\\"Test String\\\\\"` returns groups with\ndisplay names that include both \"Test\" and \"String\"",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"type": "string"
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/settingsListMembersRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
@@ -819,6 +803,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsListMembersRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page_size": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"page_token": {
|
||||
"type": "string",
|
||||
"title": "Optional. A pagination token returned from a previous call to `Get`\nthat indicates from where search should continue"
|
||||
},
|
||||
"field_mask": {
|
||||
"$ref": "#/definitions/protobufFieldMask",
|
||||
"description": "Optional. Used to specify a subset of fields that should be\nreturned by a get operation or modified by an update operation."
|
||||
},
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "TODO update query language\nQuery expressions can be used to restrict results based upon\nthe account properties where the operators `=`, `NOT`, `AND` and `OR`\ncan be used along with the suffix wildcard symbol `*`.\n\nThe string properties in a query expression should use escaped quotes\nfor values that include whitespace to prevent unexpected behavior.\n\nSome example queries are:\n\n* Query `display_name=Th*` returns accounts whose display_name\nstarts with \"Th\"\n* Query `display_name=\\\\\"Test String\\\\\"` returns groups with\ndisplay names that include both \"Test\" and \"String\"",
|
||||
"title": "Optional. Search criteria used to select the groups to return.\nIf no search criteria is specified then all groups will be\nreturned"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"title": "The id of the group to list members from"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsListMembersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -888,6 +898,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsRemoveMemberRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"group_id": {
|
||||
"type": "string",
|
||||
"title": "The id of the group to remove a member from"
|
||||
},
|
||||
"account_id": {
|
||||
"type": "string",
|
||||
"title": "The account id to remove"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsUpdateAccountRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
<div>
|
||||
<div class="uk-container uk-padding">
|
||||
<h1 v-text="$gettext('Accounts')" />
|
||||
<oc-button
|
||||
v-if="numberOfSelectedAccounts < 1 && !isAccountCreationInProgress"
|
||||
id="accounts-new-account-trigger"
|
||||
key="create-accounts-button"
|
||||
v-text="$gettext('Create new user')"
|
||||
variation="primary"
|
||||
:disabled="isAccountCreationInProgress || !isInitialized"
|
||||
:uk-tooltip="disabledCreateAccountBtnTooltip"
|
||||
@click="setAccountCreationProgress(true)"
|
||||
/>
|
||||
<accounts-list-new-account-row v-if="isAccountCreationInProgress" @close="setAccountCreationProgress(false)" />
|
||||
<oc-grid v-if="numberOfSelectedAccounts > 0" key="selected-accounts-info" gutter="small" class="uk-flex-middle">
|
||||
<span v-text="selectionInfoText" />
|
||||
<span>|</span>
|
||||
@@ -42,9 +53,14 @@
|
||||
<script>
|
||||
import { mapGetters, mapActions, mapState, mapMutations } from 'vuex'
|
||||
import AccountsList from './accounts/AccountsList.vue'
|
||||
import AccountsListNewAccountRow from './accounts/AccountsListNewAccountRow.vue'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: { AccountsList },
|
||||
components: { AccountsList, AccountsListNewAccountRow },
|
||||
data: () => ({
|
||||
isAccountCreationInProgress: false
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters('Accounts', ['isInitialized', 'getAccountsSorted']),
|
||||
...mapState('Accounts', ['selectedAccounts']),
|
||||
@@ -85,18 +101,41 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
actions.push({
|
||||
id: 'accounts-actions-dropdown-action-delete',
|
||||
label: this.$gettext('Delete'),
|
||||
handler: this.deleteAccounts
|
||||
})
|
||||
|
||||
return actions
|
||||
},
|
||||
|
||||
disabledCreateAccountBtnTooltip () {
|
||||
if (!this.isInitialized) {
|
||||
return this.$gettext('Loading users')
|
||||
}
|
||||
|
||||
if (this.isAccountCreationInProgress) {
|
||||
return this.$gettext('User creation is already in progress')
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('Accounts', ['initialize', 'toggleAccountStatus']),
|
||||
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION'])
|
||||
...mapActions('Accounts', ['initialize', 'toggleAccountStatus', 'deleteAccounts']),
|
||||
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION']),
|
||||
|
||||
setAccountCreationProgress (isInProgress) {
|
||||
this.isAccountCreationInProgress = isInProgress
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initialize()
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.RESET_ACCOUNTS_SELECTION()
|
||||
this.setAccountCreationProgress(false)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
145
ui/components/accounts/AccountsListNewAccountRow.vue
Normal file
145
ui/components/accounts/AccountsListNewAccountRow.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="uk-flex uk-flex-top">
|
||||
<oc-grid gutter="small">
|
||||
<label>
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-username"
|
||||
type="text"
|
||||
v-model="username"
|
||||
:error-message="usernameError"
|
||||
:placeholder="$gettext('Username')"
|
||||
:disabled="isInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-email"
|
||||
type="email"
|
||||
v-model="email"
|
||||
:error-message="emailError"
|
||||
:placeholder="$gettext('Email')"
|
||||
:disabled="isInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<label class="uk-margin-xsmall-right">
|
||||
<oc-text-input
|
||||
id="accounts-new-account-input-password"
|
||||
type="password"
|
||||
v-model="password"
|
||||
:error-message="passwordError"
|
||||
:placeholder="$gettext('Password')"
|
||||
:disabled="isInProgress"
|
||||
@keydown.enter="createAccount"
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
<oc-button
|
||||
v-text="$gettext('Cancel')"
|
||||
@click="emitClose"
|
||||
class="uk-margin-xsmall-right"
|
||||
:disabled="isInProgress"
|
||||
/>
|
||||
<oc-button
|
||||
id="accounts-new-account-button-confirm"
|
||||
variation="primary"
|
||||
:disabled="isInProgress"
|
||||
@click="createAccount"
|
||||
>
|
||||
<oc-spinner
|
||||
v-if="isInProgress"
|
||||
key="account-creation-in-progress"
|
||||
size="xsmall"
|
||||
class="uk-margin-xsmall-right"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span v-text="isInProgress ? $gettext('Creating') : $gettext('Create')" />
|
||||
</oc-button>
|
||||
</div>
|
||||
</oc-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import isEmail from 'validator/es/lib/isEmail'
|
||||
import isEmpty from 'validator/es/lib/isEmpty'
|
||||
import debounce from 'debounce'
|
||||
import { mapActions } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AccountsListNewAccountRow',
|
||||
|
||||
data: () => ({
|
||||
username: '',
|
||||
usernameError: '',
|
||||
email: '',
|
||||
emailError: '',
|
||||
password: '',
|
||||
passwordError: '',
|
||||
isInProgress: false
|
||||
}),
|
||||
|
||||
methods: {
|
||||
...mapActions('Accounts', ['createNewAccount']),
|
||||
|
||||
emitClose () {
|
||||
this.$emit('close')
|
||||
},
|
||||
createAccount () {
|
||||
if (!(this.checkUsername() & this.checkEmail() & this.checkPassword())) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isInProgress = true
|
||||
this.createNewAccount({ username: this.username, email: this.email, password: this.password }).finally(() => {
|
||||
this.isInProgress = false
|
||||
})
|
||||
this.emitClose()
|
||||
},
|
||||
|
||||
checkUsername () {
|
||||
if (isEmpty(this.username)) {
|
||||
debounce(this.usernameError = this.$gettext('Username cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.usernameError = ''
|
||||
return true
|
||||
},
|
||||
|
||||
checkEmail () {
|
||||
if (isEmpty(this.email)) {
|
||||
debounce(this.emailError = this.$gettext('Email cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isEmail(this.email)) {
|
||||
debounce(this.emailError = this.$gettext('Invalid email address'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.emailError = ''
|
||||
return true
|
||||
},
|
||||
|
||||
checkPassword () {
|
||||
// Later on some restrictions might be applied here
|
||||
if (isEmpty(this.password)) {
|
||||
debounce(this.passwordError = this.$gettext('Password cannot be empty'), 500)
|
||||
return false
|
||||
}
|
||||
|
||||
this.passwordError = ''
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#accounts-new-account-button-confirm > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,10 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { AccountsService_ListAccounts, AccountsService_UpdateAccount } from '../client/accounts'
|
||||
import {
|
||||
AccountsService_ListAccounts,
|
||||
AccountsService_UpdateAccount,
|
||||
AccountsService_CreateAccount,
|
||||
AccountsService_DeleteAccount
|
||||
} from '../client/accounts'
|
||||
import { RoleService_ListRoles } from '../client/settings'
|
||||
/* eslint-enable camelcase */
|
||||
import { injectAuthToken } from '../helpers/auth'
|
||||
@@ -56,6 +61,16 @@ const mutations = {
|
||||
|
||||
RESET_ACCOUNTS_SELECTION (state) {
|
||||
state.selectedAccounts = []
|
||||
},
|
||||
|
||||
PUSH_NEW_ACCOUNT (state, account) {
|
||||
state.accounts.push(account)
|
||||
},
|
||||
|
||||
DELETE_ACCOUNT (state, accountId) {
|
||||
const accountIndex = state.accounts.findIndex(account => account.id === accountId)
|
||||
|
||||
state.accounts.splice(accountIndex, 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +178,74 @@ const actions = {
|
||||
}, { root: true })
|
||||
}
|
||||
|
||||
commit('RESET_ACCOUNTS_SELECTION')
|
||||
},
|
||||
async createNewAccount ({ rootGetters, commit, dispatch }, account) {
|
||||
injectAuthToken(rootGetters.user.token)
|
||||
|
||||
const response = await AccountsService_CreateAccount({
|
||||
$domain: rootGetters.configuration.server,
|
||||
body: {
|
||||
account: {
|
||||
on_premises_sam_account_name: account.username,
|
||||
preferred_name: account.username,
|
||||
mail: account.email,
|
||||
password_profile: {
|
||||
password: account.password
|
||||
},
|
||||
account_enabled: true,
|
||||
display_name: account.username
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (response.status === 201) {
|
||||
commit('PUSH_NEW_ACCOUNT', response.data)
|
||||
} else {
|
||||
dispatch('showMessage', {
|
||||
title: 'Failed to create account',
|
||||
desc: response.statusText,
|
||||
status: 'danger'
|
||||
}, { root: true })
|
||||
}
|
||||
},
|
||||
|
||||
async deleteAccounts ({ rootGetters, state, commit, dispatch }) {
|
||||
const failedAccounts = []
|
||||
|
||||
injectAuthToken(rootGetters.user.token)
|
||||
|
||||
for (const account of state.selectedAccounts) {
|
||||
const response = await AccountsService_DeleteAccount({
|
||||
$domain: rootGetters.configuration.server,
|
||||
body: {
|
||||
id: account.id
|
||||
}
|
||||
})
|
||||
|
||||
if (response.status === 201 || response.status === 204) {
|
||||
commit('DELETE_ACCOUNT', account.id)
|
||||
} else {
|
||||
failedAccounts.push({ account: account.diisplayName, statusText: response.statusText })
|
||||
}
|
||||
}
|
||||
|
||||
if (failedAccounts.length === 1) {
|
||||
dispatch('showMessage', {
|
||||
title: 'Failed to delete account',
|
||||
desc: failedAccounts[0].statusText,
|
||||
status: 'danger'
|
||||
}, { root: true })
|
||||
}
|
||||
|
||||
if (failedAccounts.length > 1) {
|
||||
dispatch('showMessage', {
|
||||
title: 'Failed to delete accounts',
|
||||
desc: 'Could not delete multiple accounts',
|
||||
status: 'danger'
|
||||
}, { root: true })
|
||||
}
|
||||
|
||||
commit('RESET_ACCOUNTS_SELECTION')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,3 +59,15 @@ Feature: Accounts
|
||||
Then the status indicator of user "einstein,marie" should be "enabled" on the WebUI
|
||||
# And user "einstein" should be able to log in
|
||||
# And user "marie" should be able to log in
|
||||
|
||||
Scenario: create a user
|
||||
Given user "Moss" has logged in using the webUI
|
||||
And the user browses to the accounts page
|
||||
When the user creates a new user with username "bob", email "bob@example.org" and password "bob" using the WebUI
|
||||
Then user "bob" should be displayed in the accounts list on the WebUI
|
||||
|
||||
Scenario: delete a user
|
||||
Given user "Moss" has logged in using the webUI
|
||||
And the user browses to the accounts page
|
||||
When the user deletes user "bob" using the WebUI
|
||||
Then user "bob" should not be displayed in the accounts list on the WebUI
|
||||
@@ -14,13 +14,14 @@ module.exports = {
|
||||
return this.waitForElementVisible('@accountsListTable')
|
||||
},
|
||||
isUserListed: async function (username) {
|
||||
let user
|
||||
const usernameInTable = util.format(this.elements.userInAccountsList.selector, username)
|
||||
await this.useXpath().waitForElementVisible(usernameInTable)
|
||||
.getText(usernameInTable, (result) => {
|
||||
user = result
|
||||
})
|
||||
return user.value
|
||||
return true
|
||||
},
|
||||
isUserDeleted: async function (username) {
|
||||
const usernameInTable = util.format(this.elements.userInAccountsList.selector, username)
|
||||
await this.useXpath().waitForElementNotPresent(usernameInTable)
|
||||
return true
|
||||
},
|
||||
|
||||
selectRole: function (username, role) {
|
||||
@@ -43,17 +44,9 @@ module.exports = {
|
||||
},
|
||||
|
||||
toggleUserStatus: function (usernames, status) {
|
||||
usernames = usernames.split(',')
|
||||
const actionSelector = status === 'enabled' ? this.elements.enableAction : this.elements.disableAction
|
||||
|
||||
// Select users
|
||||
for (const username of usernames) {
|
||||
const checkboxSelector =
|
||||
util.format(this.elements.rowByUsername.selector, username) +
|
||||
this.elements.rowCheckbox.selector
|
||||
|
||||
this.useXpath().click(checkboxSelector)
|
||||
}
|
||||
this.selectUsers(usernames)
|
||||
|
||||
return this
|
||||
.waitForElementVisible('@actionsDropdownTrigger')
|
||||
@@ -76,6 +69,38 @@ module.exports = {
|
||||
}
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
deleteUsers: function (usernames) {
|
||||
this.selectUsers(usernames)
|
||||
|
||||
return this
|
||||
.waitForElementVisible('@actionsDropdownTrigger')
|
||||
.click('@actionsDropdownTrigger')
|
||||
.click('@deleteAction')
|
||||
},
|
||||
|
||||
selectUsers: function (usernames) {
|
||||
usernames = usernames.split(',')
|
||||
|
||||
for (const username of usernames) {
|
||||
const checkboxSelector =
|
||||
util.format(this.elements.rowByUsername.selector, username) +
|
||||
this.elements.rowCheckbox.selector
|
||||
|
||||
this.useXpath().click(checkboxSelector)
|
||||
}
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
createUser: function (username, email, password) {
|
||||
return this
|
||||
.click('@accountsNewAccountTrigger')
|
||||
.setValue('@newAccountInputUsername', username)
|
||||
.setValue('@newAccountInputEmail', email)
|
||||
.setValue('@newAccountInputPassword', password)
|
||||
.click('@newAccountButtonConfirm')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -129,6 +154,24 @@ module.exports = {
|
||||
statusIndicator: {
|
||||
selector: '//span[contains(@class, "accounts-status-indicator-%s")]',
|
||||
locateStrategy: 'xpath'
|
||||
},
|
||||
newAccountInputUsername: {
|
||||
selector: '#accounts-new-account-input-username'
|
||||
},
|
||||
newAccountInputEmail: {
|
||||
selector: '#accounts-new-account-input-email'
|
||||
},
|
||||
newAccountInputPassword: {
|
||||
selector: '#accounts-new-account-input-password'
|
||||
},
|
||||
newAccountButtonConfirm: {
|
||||
selector: '#accounts-new-account-button-confirm'
|
||||
},
|
||||
accountsNewAccountTrigger: {
|
||||
selector: '#accounts-new-account-trigger'
|
||||
},
|
||||
deleteAction: {
|
||||
selector: '#accounts-actions-dropdown-action-delete'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,15 @@ When('the user browses to the accounts page', function () {
|
||||
})
|
||||
|
||||
Then('user {string} should be displayed in the accounts list on the WebUI', async function (username) {
|
||||
await client.page.accountsPage().accountsList(username)
|
||||
await client.page.accountsPage().accountsList()
|
||||
const userListed = await client.page.accountsPage().isUserListed(username)
|
||||
return assert.strictEqual(userListed, username)
|
||||
return assert.strictEqual(userListed, true)
|
||||
})
|
||||
|
||||
Then('user {string} should not be displayed in the accounts list on the WebUI', async function (username) {
|
||||
await client.page.accountsPage().accountsList()
|
||||
const userDeleted = await client.page.accountsPage().isUserDeleted(username)
|
||||
return assert.strictEqual(userDeleted, true)
|
||||
})
|
||||
|
||||
Given('the user has changed the role of user {string} to {string}', function (username, role) {
|
||||
@@ -42,3 +48,14 @@ When('the user enables user/users {string} using the WebUI', function (usernames
|
||||
Then('the status indicator of user/users {string} should be {string} on the WebUI', function (usernames, status) {
|
||||
return client.page.accountsPage().checkUsersStatus(usernames, status)
|
||||
})
|
||||
|
||||
When(
|
||||
'the user creates a new user with username {string}, email {string} and password {string} using the WebUI',
|
||||
function (username, email, password) {
|
||||
return client.page.accountsPage().createUser(username, email, password)
|
||||
}
|
||||
)
|
||||
|
||||
When('the user deletes user/users {string} using the WebUI', function (usernames) {
|
||||
return client.page.accountsPage().deleteUsers(usernames)
|
||||
})
|
||||
|
||||
@@ -6348,6 +6348,11 @@ validate-npm-package-license@^3.0.1:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
validator@^13.1.1:
|
||||
version "13.1.1"
|
||||
resolved "https://registry.yarnpkg.com/validator/-/validator-13.1.1.tgz#f8811368473d2173a9d8611572b58c5783f223bf"
|
||||
integrity sha512-8GfPiwzzRoWTg7OV1zva1KvrSemuMkv07MA9TTl91hfhe+wKrsrgVN4H2QSFd/U/FhiU3iWPYVgvbsOGwhyFWw==
|
||||
|
||||
vasync@1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/vasync/-/vasync-1.6.2.tgz#568edcf40b2b5c35b1cc048cad085de4739703fb"
|
||||
|
||||
Reference in New Issue
Block a user