Reorganize code into components and align layout with phoenix

This commit is contained in:
Benedikt Kulmann
2020-09-10 16:28:21 +02:00
parent cea2720c39
commit 06880c1488
8 changed files with 304 additions and 290 deletions
+5
View File
@@ -226,6 +226,7 @@ github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/reflex v0.2.0 h1:6d9WpWJseKjJvZEevKP7Pk42nPx2+BUTqmhNk8wZPwM=
github.com/cespare/reflex v0.2.0/go.mod h1:ooqOLJ4algvHP/oYvKWfWJ9tFUzCLDk5qkIJduMYrgI=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -294,6 +295,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da h1:WXnT88cFG2davqSFqvaFfzkSMC0lqh/8/rKZ+z7tYvI=
github.com/crewjam/httperr v0.0.0-20190612203328-a946449404da/go.mod h1:+rmNIXRvYMqLQeR4DHyTvs6y0MEMymTz4vyFpFkKTPs=
@@ -893,6 +895,7 @@ github.com/karrick/godirwalk v1.7.8 h1:VfG72pyIxgtC7+3X9CMHI0AOl4LwyRAg98WAgsvff
github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
@@ -916,6 +919,7 @@ 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=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -1145,6 +1149,7 @@ github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag=
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito=
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
+3 -32
View File
File diff suppressed because one or more lines are too long
+20 -110
View File
@@ -1,49 +1,20 @@
<template>
<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>
<div>
<oc-button v-text="$gettext('Clear selection')" variation="raw" @click="RESET_ACCOUNTS_SELECTION" />
</div>
<div>
<oc-action-drop class="accounts-actions-dropdown">
<template v-slot:button>
<span class="uk-margin-xsmall-right" v-text="$gettext('Actions')" />
<oc-icon name="expand_more" />
</template>
<template v-slot:actions>
<oc-button
v-for="(action, index) in actions"
:key="action.label"
:id="action.id"
variation="raw"
role="menuitem"
:class="{ 'uk-margin-small-bottom': index + 1 !== actions.length }"
class="uk-width-1-1 uk-flex-left"
@click="action.handler"
>
{{ action.label }}
</oc-button>
</template>
</oc-action-drop>
</div>
</oc-grid>
<div class="uk-flex uk-flex-column">
<template v-if="isInitialized">
<accounts-list :accounts="accounts" />
<div class="oc-app-bar">
<accounts-batch-actions
v-if="isAnyAccountSelected"
:number-of-selected-accounts="numberOfSelectedAccounts"
:selected-accounts="selectedAccounts"
/>
<accounts-create v-else />
</div>
<oc-grid class="uk-height-1-1 uk-flex-1 uk-overflow-auto">
<div class="uk-width-expand">
<accounts-list :accounts="accounts" />
</div>
</oc-grid>
</template>
<oc-loader v-else />
</div>
@@ -51,91 +22,30 @@
</template>
<script>
import { mapGetters, mapActions, mapState, mapMutations } from 'vuex'
import { mapGetters, mapActions, mapState } from 'vuex'
import AccountsList from './accounts/AccountsList.vue'
import AccountsListNewAccountRow from './accounts/AccountsListNewAccountRow.vue'
import AccountsCreate from './accounts/AccountsCreate.vue'
import AccountsBatchActions from './accounts/AccountsBatchActions.vue'
export default {
name: 'App',
components: { AccountsList, AccountsListNewAccountRow },
data: () => ({
isAccountCreationInProgress: false
}),
components: { AccountsBatchActions, AccountsList, AccountsCreate },
computed: {
...mapGetters('Accounts', ['isInitialized', 'getAccountsSorted']),
...mapGetters('Accounts', ['isInitialized', 'getAccountsSorted', 'isAnyAccountSelected']),
...mapState('Accounts', ['selectedAccounts']),
accounts () {
return this.getAccountsSorted
},
numberOfSelectedAccounts () {
return this.selectedAccounts.length
},
selectionInfoText () {
const translated = this.$ngettext('%{ amount } selected user', '%{ amount } selected users', this.numberOfSelectedAccounts)
return this.$gettextInterpolate(translated, { amount: this.numberOfSelectedAccounts })
},
actions () {
const actions = []
const numberOfDisabledAccounts = this.selectedAccounts.filter(account => !account.accountEnabled).length
const isAnyAccountDisabled = numberOfDisabledAccounts > 0
const isAnyAccountEnabled = numberOfDisabledAccounts < this.numberOfSelectedAccounts
if (isAnyAccountDisabled) {
actions.push({
id: 'accounts-actions-dropdown-action-enable',
label: this.$gettext('Enable'),
handler: () => this.toggleAccountStatus(true)
})
}
if (isAnyAccountEnabled) {
actions.push({
id: 'accounts-actions-dropdown-action-disable',
label: this.$gettext('Disable'),
handler: () => this.toggleAccountStatus(false)
})
}
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', 'deleteAccounts']),
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION']),
setAccountCreationProgress (isInProgress) {
this.isAccountCreationInProgress = isInProgress
}
...mapActions('Accounts', ['initialize'])
},
created () {
this.initialize()
},
beforeDestroy () {
this.RESET_ACCOUNTS_SELECTION()
this.setAccountCreationProgress(false)
}
}
</script>
@@ -0,0 +1,89 @@
<template>
<oc-grid key="selected-accounts-info" gutter="small" class="uk-flex-middle">
<span v-text="selectionInfoText" />
<span>|</span>
<div>
<oc-button v-text="$gettext('Clear selection')" variation="raw" @click="RESET_ACCOUNTS_SELECTION" />
</div>
<div>
<oc-action-drop class="accounts-actions-dropdown">
<template v-slot:button>
<span class="uk-margin-xsmall-right" v-text="$gettext('Actions')" />
<oc-icon name="expand_more" />
</template>
<template v-slot:actions>
<oc-button
v-for="(action, index) in actions"
:key="action.label"
:id="action.id"
variation="raw"
role="menuitem"
:class="{ 'uk-margin-small-bottom': index + 1 !== actions.length }"
class="uk-width-1-1 uk-flex-left"
@click="action.handler"
>
{{ action.label }}
</oc-button>
</template>
</oc-action-drop>
</div>
</oc-grid>
</template>
<script>
import { mapActions, mapMutations } from 'vuex'
export default {
name: 'AccountsBatchActions',
props: {
numberOfSelectedAccounts: {
type: Number,
required: true
},
selectedAccounts: {
type: Array,
required: true
}
},
computed: {
selectionInfoText () {
const translated = this.$ngettext('%{ amount } selected user', '%{ amount } selected users', this.numberOfSelectedAccounts)
return this.$gettextInterpolate(translated, { amount: this.numberOfSelectedAccounts })
},
actions () {
const actions = []
const numberOfDisabledAccounts = this.selectedAccounts.filter(account => !account.accountEnabled).length
const isAnyAccountDisabled = numberOfDisabledAccounts > 0
const isAnyAccountEnabled = numberOfDisabledAccounts < this.numberOfSelectedAccounts
if (isAnyAccountDisabled) {
actions.push({
id: 'accounts-actions-dropdown-action-enable',
label: this.$gettext('Enable'),
handler: () => this.toggleAccountStatus(true)
})
}
if (isAnyAccountEnabled) {
actions.push({
id: 'accounts-actions-dropdown-action-disable',
label: this.$gettext('Disable'),
handler: () => this.toggleAccountStatus(false)
})
}
actions.push({
id: 'accounts-actions-dropdown-action-delete',
label: this.$gettext('Delete'),
handler: this.deleteAccounts
})
return actions
}
},
methods: {
...mapActions('Accounts', ['toggleAccountStatus', 'deleteAccounts']),
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION'])
}
}
</script>
+179
View File
@@ -0,0 +1,179 @@
<template>
<div>
<oc-grid v-if="isFormInProgress" gutter="small">
<label>
<oc-text-input
id="accounts-new-account-input-username"
type="text"
v-model="formData.username"
:error-message="formValidation.usernameError"
:placeholder="$gettext('Username')"
:disabled="isRequestInProgress"
@keydown.enter="createAccount"
/>
</label>
<label>
<oc-text-input
id="accounts-new-account-input-email"
type="email"
v-model="formData.email"
:error-message="formValidation.emailError"
:placeholder="$gettext('Email')"
:disabled="isRequestInProgress"
@keydown.enter="createAccount"
/>
</label>
<label class="uk-margin-xsmall-right">
<oc-text-input
id="accounts-new-account-input-password"
type="password"
v-model="formData.password"
:error-message="formValidation.passwordError"
:placeholder="$gettext('Password')"
:disabled="isRequestInProgress"
@keydown.enter="createAccount"
/>
</label>
<div>
<oc-button
v-text="$gettext('Cancel')"
@click="cancelForm"
class="uk-margin-xsmall-right"
:disabled="isRequestInProgress"
/>
<oc-button
id="accounts-new-account-button-confirm"
variation="primary"
:disabled="isRequestInProgress"
@click="createAccount"
>
<oc-spinner
v-if="isRequestInProgress"
key="account-creation-in-progress"
size="xsmall"
class="uk-margin-xsmall-right"
aria-hidden="true"
/>
<span v-text="isRequestInProgress ? $gettext('Creating') : $gettext('Create')" />
</oc-button>
</div>
</oc-grid>
<oc-grid v-else gutter="small">
<div>
<oc-button
id="accounts-new-account-trigger"
key="create-accounts-button"
v-text="$gettext('Create new account')"
variation="primary"
@click="setFormInProgress(true)"
/>
</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: 'AccountsCreate',
data: () => ({
isFormInProgress: false,
isRequestInProgress: false,
formData: {
username: '',
email: '',
password: ''
},
formValidation: {
usernameError: '',
emailError: '',
passwordError: ''
}
}),
methods: {
...mapActions('Accounts', ['createNewAccount']),
setFormInProgress (inProgress) {
this.isFormInProgress = inProgress
},
cancelForm () {
this.isRequestInProgress = false
this.setFormInProgress(false)
this.formData = {
username: '',
email: '',
password: ''
}
this.formValidation = {
usernameError: '',
emailError: '',
passwordError: ''
}
},
createAccount () {
if (!(this.checkUsername() & this.checkEmail() & this.checkPassword())) {
return
}
this.isRequestInProgress = true
this.createNewAccount(this.formData).finally(() => {
this.cancelForm()
})
},
checkUsername () {
if (isEmpty(this.formData.username)) {
debounce(this.formValidation.usernameError = this.$gettext('Username cannot be empty'), 500)
return false
}
this.formValidation.usernameError = ''
return true
},
checkEmail () {
if (isEmpty(this.formData.email)) {
debounce(this.formValidation.emailError = this.$gettext('Email cannot be empty'), 500)
return false
}
if (!isEmail(this.formData.email)) {
debounce(this.formValidation.emailError = this.$gettext('Invalid email address'), 500)
return false
}
this.formValidation.emailError = ''
return true
},
checkPassword () {
// Later on some restrictions might be applied here
if (isEmpty(this.formData.password)) {
debounce(this.formValidation.passwordError = this.$gettext('Password cannot be empty'), 500)
return false
}
this.formValidation.passwordError = ''
return true
}
},
onDestroy () {
this.cancelForm()
}
}
</script>
<style>
#accounts-new-account-button-confirm > span {
display: flex;
align-items: center;
}
</style>
+6 -2
View File
@@ -33,7 +33,7 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import {mapActions, mapGetters, mapMutations } from 'vuex'
import AccountsListRow from './AccountsListRow.vue'
export default {
@@ -51,7 +51,11 @@ export default {
...mapGetters('Accounts', ['areAllAccountsSelected'])
},
methods: {
...mapActions('Accounts', ['toggleSelectionAll'])
...mapActions('Accounts', ['toggleSelectionAll']),
...mapMutations('Accounts', ['RESET_ACCOUNTS_SELECTION'])
},
beforeDestroy () {
this.RESET_ACCOUNTS_SELECTION()
}
}
</script>
@@ -1,145 +0,0 @@
<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>
+2 -1
View File
@@ -28,7 +28,8 @@ const getters = {
return a1.onPremisesSamAccountName.localeCompare(a2.onPremisesSamAccountName)
})
},
areAllAccountsSelected: state => state.accounts.length === state.selectedAccounts.length
areAllAccountsSelected: state => state.accounts.length === state.selectedAccounts.length,
isAnyAccountSelected: state => state.selectedAccounts.length > 0
}
const mutations = {