mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-06-25 14:32:09 +02:00
Add Groups to vue front-end
This commit is contained in:
parent
9b29c4d294
commit
b1c2a56c2a
14
resources/js/packages/fontawesome.js
vendored
14
resources/js/packages/fontawesome.js
vendored
@ -5,6 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
import {
|
||||
faPlus,
|
||||
faPlusCircle,
|
||||
faQrcode,
|
||||
faImage,
|
||||
faTrash,
|
||||
@ -16,7 +17,11 @@ import {
|
||||
faEllipsisH,
|
||||
faBars,
|
||||
faSpinner,
|
||||
faChevronLeft
|
||||
faChevronLeft,
|
||||
faCaretUp,
|
||||
faCaretDown,
|
||||
faLayerGroup,
|
||||
faMinusCircle,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import {
|
||||
@ -25,6 +30,7 @@ import {
|
||||
|
||||
library.add(
|
||||
faPlus,
|
||||
faPlusCircle,
|
||||
faQrcode,
|
||||
faImage,
|
||||
faTrash,
|
||||
@ -37,7 +43,11 @@ library.add(
|
||||
faBars,
|
||||
faSpinner,
|
||||
faGithubAlt,
|
||||
faChevronLeft
|
||||
faChevronLeft,
|
||||
faCaretUp,
|
||||
faCaretDown,
|
||||
faLayerGroup,
|
||||
faMinusCircle,
|
||||
);
|
||||
|
||||
Vue.component('font-awesome-icon', FontAwesomeIcon)
|
@ -1,5 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Group selector -->
|
||||
<div class="container groups" v-if="showGroupSelector">
|
||||
<div class="columns is-centered">
|
||||
<div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-full" v-for="group in groups" v-if="group.count > 0" :key="group.id">
|
||||
<button :disabled="group.id == $root.appSettings.activeGroup" class="button is-fullwidth is-dark has-text-light is-outlined" @click="setActiveGroup(group.id)">{{ group.name }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-centered">
|
||||
<div class="column has-text-centered">
|
||||
<router-link :to="{ name: 'groups' }" >{{ $t('groups.manage_groups') }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- show accounts list -->
|
||||
<div class="container" v-if="this.showAccounts">
|
||||
<!-- accounts -->
|
||||
@ -47,13 +64,16 @@
|
||||
<!-- </vue-pull-refresh> -->
|
||||
</div>
|
||||
<!-- header -->
|
||||
<div class="header has-background-black-ter" v-if="this.showAccounts">
|
||||
<div class="header has-background-black-ter" v-if="this.showAccounts || this.showGroupSelector">
|
||||
<div class="columns is-gapless is-mobile is-centered">
|
||||
<div class="column is-three-quarters-mobile is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
|
||||
<!-- toolbar -->
|
||||
<div class="toolbar has-text-centered" v-if="editMode">
|
||||
<div class="manage-buttons tags has-addons are-medium">
|
||||
<span class="tag is-dark">{{ selectedAccounts.length }} {{ $t('commons.selected') }}</span>
|
||||
<a class="tag is-link" v-if="selectedAccounts.length > 0" @click="moveAccounts">
|
||||
{{ $t('commons.move') }} <font-awesome-icon :icon="['fas', 'layer-group']" />
|
||||
</a>
|
||||
<a class="tag is-danger" v-if="selectedAccounts.length > 0" @click="destroyAccounts">
|
||||
{{ $t('commons.delete') }} <font-awesome-icon :icon="['fas', 'trash']" />
|
||||
</a>
|
||||
@ -69,6 +89,18 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- group selector -->
|
||||
<div class="group-selector has-text-centered" v-if="!editMode">
|
||||
<div class="columns" @click="toggleGroupSelector">
|
||||
<div class="column" v-if="!showGroupSelector">
|
||||
{{ this.activeGroupName }} ({{ this.accounts.length }})
|
||||
<font-awesome-icon :icon="['fas', 'caret-down']" />
|
||||
</div>
|
||||
<div class="column" v-else>
|
||||
{{ $t('groups.select_accounts_to_show') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -79,11 +111,11 @@
|
||||
<twofaccount-show ref="TwofaccountShow" ></twofaccount-show>
|
||||
</modal>
|
||||
<!-- footer -->
|
||||
<vue-footer v-if="showFooter" :showButtons="accounts.length > 0">
|
||||
<vue-footer v-if="showFooter && !showGroupSelector" :showButtons="accounts.length > 0">
|
||||
<!-- New item buttons -->
|
||||
<p class="control" v-if="!showUploader && !editMode">
|
||||
<a class="button is-link is-rounded is-focus" @click="showUploader = true">
|
||||
<span>{{ $t('twofaccounts.new') }}</span>
|
||||
<span>{{ $t('commons.new') }}</span>
|
||||
<span class="icon is-small">
|
||||
<font-awesome-icon :icon="['fas', 'qrcode']" />
|
||||
</span>
|
||||
@ -91,12 +123,12 @@
|
||||
</p>
|
||||
<!-- Manage button -->
|
||||
<p class="control" v-if="!showUploader && !editMode">
|
||||
<a class="button is-dark is-rounded" @click="setEditModeTo(true)">{{ $t('twofaccounts.manage') }}</a>
|
||||
<a class="button is-dark is-rounded" @click="setEditModeTo(true)">{{ $t('commons.manage') }}</a>
|
||||
</p>
|
||||
<!-- Done button -->
|
||||
<p class="control" v-if="!showUploader && editMode">
|
||||
<a class="button is-success is-rounded" @click="setEditModeTo(false)">
|
||||
<span>{{ $t('twofaccounts.done') }}</span>
|
||||
<span>{{ $t('commons.done') }}</span>
|
||||
<span class="icon is-small">
|
||||
<font-awesome-icon :icon="['fas', 'check']" />
|
||||
</span>
|
||||
@ -109,6 +141,12 @@
|
||||
</a>
|
||||
</p>
|
||||
</vue-footer>
|
||||
<vue-footer v-if="showFooter && showGroupSelector" :showButtons="true">
|
||||
<!-- Close Group selector button -->
|
||||
<p class="control">
|
||||
<a class="button is-dark is-rounded" @click="closeGroupSelector()">{{ $t('commons.close') }}</a>
|
||||
</p>
|
||||
</vue-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -120,18 +158,24 @@
|
||||
import QuickUploader from './../components/QuickUploader'
|
||||
// import vuePullRefresh from 'vue-pull-refresh';
|
||||
import draggable from 'vuedraggable'
|
||||
import Form from './../components/Form'
|
||||
|
||||
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
accounts : [],
|
||||
groups : [],
|
||||
selectedAccounts: [],
|
||||
form: new Form({
|
||||
activeGroup: this.$root.appSettings.activeGroup,
|
||||
}),
|
||||
showTwofaccountInModal : false,
|
||||
search: '',
|
||||
editMode: this.InitialEditMode,
|
||||
showUploader: false,
|
||||
showFooter: true,
|
||||
showGroupSelector: false,
|
||||
drag: false,
|
||||
}
|
||||
},
|
||||
@ -151,9 +195,20 @@
|
||||
},
|
||||
|
||||
showAccounts() {
|
||||
return this.accounts.length > 0 && !this.showUploader ? true : false
|
||||
return this.accounts.length > 0 && !this.showUploader && !this.showGroupSelector ? true : false
|
||||
},
|
||||
|
||||
activeGroupName() {
|
||||
let g = this.groups.find(el => el.id === parseInt(this.$root.appSettings.activeGroup))
|
||||
|
||||
if(g) {
|
||||
return g.name
|
||||
}
|
||||
else {
|
||||
return this.$t('commons.all')
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
props: ['InitialEditMode'],
|
||||
@ -161,6 +216,7 @@
|
||||
mounted() {
|
||||
|
||||
this.fetchAccounts()
|
||||
this.fetchGroups()
|
||||
|
||||
// stop OTP generation on modal close
|
||||
this.$on('modalClose', function() {
|
||||
@ -261,6 +317,72 @@
|
||||
}
|
||||
},
|
||||
|
||||
async moveAccounts() {
|
||||
|
||||
let accountsIds = []
|
||||
this.selectedAccounts.forEach(id => accountsIds.push(id))
|
||||
|
||||
// Backend will associate all accounts with the selected group in the same move
|
||||
await this.axios.patch('/api/group/accounts', {accountsIds: accountsIds, groupId: '3'} )
|
||||
|
||||
// we fetch the accounts again to prevent the js collection being
|
||||
// desynchronize from the backend php collection
|
||||
this.fetchAccounts()
|
||||
|
||||
},
|
||||
|
||||
fetchGroups() {
|
||||
this.groups = []
|
||||
|
||||
this.axios.get('api/groups').then(response => {
|
||||
response.data.forEach((data) => {
|
||||
this.groups.push({
|
||||
id : data.id,
|
||||
name : data.name,
|
||||
isActive: data.isActive,
|
||||
count: data.twofaccounts_count
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
async setActiveGroup(id) {
|
||||
|
||||
this.form.activeGroup = id
|
||||
|
||||
await this.form.post('/api/settings/options', {returnError: true})
|
||||
.then(response => {
|
||||
|
||||
this.$root.appSettings.activeGroup = response.data.settings.activeGroup
|
||||
|
||||
this.closeGroupSelector()
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
this.$router.push({ name: 'genericError', params: { err: error.response } })
|
||||
});
|
||||
|
||||
this.fetchAccounts()
|
||||
},
|
||||
|
||||
toggleGroupSelector: function(event) {
|
||||
|
||||
if (event) {
|
||||
this.showGroupSelector ? this.closeGroupSelector() : this.openGroupSelector()
|
||||
}
|
||||
},
|
||||
|
||||
openGroupSelector: function(event) {
|
||||
|
||||
this.showGroupSelector = true
|
||||
},
|
||||
|
||||
closeGroupSelector: function(event) {
|
||||
|
||||
this.showGroupSelector = false
|
||||
},
|
||||
|
||||
setEditModeTo(state) {
|
||||
if( state === false ) {
|
||||
this.selectedAccounts = []
|
||||
|
81
resources/js/views/Groups.vue
Normal file
81
resources/js/views/Groups.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div class="columns is-centered">
|
||||
<div class="form-column column is-two-thirds-tablet is-half-desktop is-one-third-widescreen is-one-quarter-fullhd">
|
||||
<h1 class="title">
|
||||
{{ $t('groups.groups') }}
|
||||
</h1>
|
||||
<p class="is-size-7-mobile">
|
||||
{{ $t('groups.manage_groups_legend')}}
|
||||
</p>
|
||||
<router-link class="is-link" :to="{ name: 'createGroup' }">
|
||||
<font-awesome-icon :icon="['fas', 'plus-circle']" /> Create new group
|
||||
</router-link>
|
||||
<div v-for="group in groups" :key="group.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
|
||||
{{ group.name }}
|
||||
<a class="has-text-grey is-pulled-right" @click="deleteGroup(group.id)">
|
||||
<font-awesome-icon :icon="['fas', 'trash']" />
|
||||
</a>
|
||||
<router-link :to="{ name: 'editGroup', params: { groupId: group.id }}" class="tag is-dark">
|
||||
{{ $t('commons.rename') }}
|
||||
</router-link>
|
||||
<span class="is-family-primary is-size-6 is-size-7-mobile has-text-grey">{{ group.count }} {{ $t('twofaccounts.accounts') }}</span>
|
||||
</div>
|
||||
<p class="is-size-7 is-pulled-right">
|
||||
{{ $t('groups.deleting_group_does_not_delete_accounts')}}
|
||||
</p>
|
||||
<!-- footer -->
|
||||
<vue-footer :showButtons="true">
|
||||
<!-- close button -->
|
||||
<p class="control">
|
||||
<router-link :to="{ name: 'accounts' }" class="button is-dark is-rounded" @click="">{{ $t('commons.close') }}</router-link>
|
||||
</p>
|
||||
</vue-footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
groups : [],
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
||||
this.fetchGroups()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async fetchGroups() {
|
||||
|
||||
await this.axios.get('api/groups').then(response => {
|
||||
response.data.forEach((data) => {
|
||||
this.groups.push({
|
||||
id : data.id,
|
||||
name : data.name,
|
||||
count: data.twofaccounts_count
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Remove the pseudo 'All' group
|
||||
this.groups.shift()
|
||||
},
|
||||
|
||||
deleteGroup(id) {
|
||||
if(confirm(this.$t('groups.confirm.delete'))) {
|
||||
this.axios.delete('/api/groups/' + id)
|
||||
|
||||
// Remove the deleted group from the collection
|
||||
this.groups = this.groups.filter(a => a.id !== id)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
50
resources/js/views/groups/Create.vue
Normal file
50
resources/js/views/groups/Create.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<form-wrapper :title="$t('groups.forms.new_group')">
|
||||
<form @submit.prevent="createGroup" @keydown="form.onKeydown($event)">
|
||||
<form-field :form="form" fieldName="name" inputType="text" :label="$t('commons.name')" autofocus />
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<v-button>{{ $t('commons.create') }}</v-button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-text" @click="cancelCreation">{{ $t('commons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Form from './../../components/Form'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: new Form({
|
||||
name: '',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async createGroup() {
|
||||
|
||||
await this.form.post('/api/groups')
|
||||
|
||||
if( this.form.errors.any() === false ) {
|
||||
this.$router.push({name: 'groups', params: { InitialEditMode: false }});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
cancelCreation: function() {
|
||||
|
||||
this.$router.push({name: 'groups', params: { InitialEditMode: false }});
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
63
resources/js/views/groups/Edit.vue
Normal file
63
resources/js/views/groups/Edit.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<form-wrapper :title="$t('groups.forms.rename_group')">
|
||||
<form @submit.prevent="updateGroup" @keydown="form.onKeydown($event)">
|
||||
<form-field :form="form" fieldName="name" inputType="text" :label="$t('groups.forms.new_name')" autofocus />
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<v-button :isLoading="form.isBusy">{{ $t('commons.save') }}</v-button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button type="button" class="button is-text" @click="cancelCreation">{{ $t('commons.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form-wrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Form from './../../components/Form'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
groupExists: false,
|
||||
form: new Form({
|
||||
name: '',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
created: function() {
|
||||
this.getGroup();
|
||||
},
|
||||
|
||||
methods: {
|
||||
async getGroup () {
|
||||
|
||||
const { data } = await this.axios.get('/api/groups/' + this.$route.params.groupId)
|
||||
|
||||
this.form.fill(data)
|
||||
this.groupExists = true
|
||||
|
||||
},
|
||||
|
||||
async updateGroup() {
|
||||
|
||||
await this.form.put('/api/groups/' + this.$route.params.groupId)
|
||||
|
||||
if( this.form.errors.any() === false ) {
|
||||
this.$router.push({name: 'groups', params: { InitialEditMode: true }})
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
cancelCreation: function() {
|
||||
|
||||
this.$router.push({name: 'groups', params: { InitialEditMode: true }});
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
30
resources/lang/en/groups.php
Normal file
30
resources/lang/en/groups.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Groups Language Lines
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The following language lines are used during authentication for various
|
||||
| messages that we need to display to the user. You are free to modify
|
||||
| these language lines according to your application's requirements.
|
||||
|
|
||||
*/
|
||||
|
||||
'groups' => 'Groups',
|
||||
'select_accounts_to_show' => 'Select accounts to show',
|
||||
'manage_groups' => 'Manage groups',
|
||||
'manage_groups_legend' => 'You can create groups to manage your accounts the way you want. All your accounts remain visible in the pseudo group named \'All\', regardless of the group they belong to.',
|
||||
'deleting_group_does_not_delete_accounts' => 'Deleting a group does not delete accounts',
|
||||
'forms' => [
|
||||
'new_group' => 'New group',
|
||||
'new_name' => 'New name',
|
||||
'rename_group' => 'Rename group',
|
||||
],
|
||||
'confirm' => [
|
||||
'delete' => 'Are you sure you want to delete this group?',
|
||||
],
|
||||
|
||||
];
|
25
resources/sass/app.scss
vendored
25
resources/sass/app.scss
vendored
@ -51,13 +51,34 @@ a:hover {
|
||||
}
|
||||
}
|
||||
|
||||
.group-selector {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.group-item {
|
||||
border-bottom: 1px solid hsl(0, 0%, 21%);
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.group-item:first-of-type {
|
||||
margin-top: 2.5rem;
|
||||
}
|
||||
|
||||
.group-item span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.accounts {
|
||||
margin-top: 40px;
|
||||
margin-top: 64px;
|
||||
}
|
||||
|
||||
.groups {
|
||||
margin-top: 110px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 769px) {
|
||||
.accounts {
|
||||
margin-top: 60px;
|
||||
margin-top: 84px;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user