mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-01-23 06:38:34 +01:00
Set up the Options view bound to the prefs & settings stores
This commit is contained in:
parent
39281ec428
commit
c448628e1b
@ -1,5 +1,14 @@
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
|
||||
onMounted(async () => {
|
||||
const { useUserStore } = await import('./stores/user.js');
|
||||
const { language } = useNavigatorLanguage()
|
||||
|
||||
watch(language, () => {
|
||||
useUserStore().applyLanguage()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
20
resources/js_vue3/app.js
vendored
20
resources/js_vue3/app.js
vendored
@ -38,9 +38,11 @@ app.use(i18nVue, {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Notifications
|
||||
app.use(Notifications)
|
||||
|
||||
// Components registration
|
||||
// Global components registration
|
||||
import ResponsiveWidthWrapper from '@/layouts/ResponsiveWidthWrapper.vue'
|
||||
import FormWrapper from '@/layouts/FormWrapper.vue'
|
||||
import Footer from '@/layouts/Footer.vue'
|
||||
@ -49,10 +51,10 @@ import VueButton from '@/components/formElements/Button.vue'
|
||||
import FieldError from '@/components/formElements/FieldError.vue'
|
||||
import FormField from '@/components/formElements/FormField.vue'
|
||||
import FormPasswordField from '@/components/formElements/FormPasswordField.vue'
|
||||
// import FormSelect from './FormSelect'
|
||||
import FormSelect from '@/components/formElements/FormSelect.vue'
|
||||
// import FormSwitch from './FormSwitch'
|
||||
// import FormToggle from './FormToggle'
|
||||
// import FormCheckbox from './FormCheckbox'
|
||||
import FormToggle from '@/components/formElements/FormToggle.vue'
|
||||
import FormCheckbox from '@/components/formElements/FormCheckbox.vue'
|
||||
import FormButtons from '@/components/formElements/FormButtons.vue'
|
||||
// import Kicker from './Kicker'
|
||||
// import SettingTabs from './SettingTabs'
|
||||
@ -67,6 +69,9 @@ app
|
||||
.component('FieldError', FieldError)
|
||||
.component('FormField', FormField)
|
||||
.component('FormPasswordField', FormPasswordField)
|
||||
.component('FormSelect', FormSelect)
|
||||
.component('FormToggle', FormToggle)
|
||||
.component('FormCheckbox', FormCheckbox)
|
||||
.component('FormButtons', FormButtons)
|
||||
|
||||
// Global error handling
|
||||
@ -81,8 +86,5 @@ if (process.env.NODE_ENV != 'development') {
|
||||
app.mount('#app')
|
||||
|
||||
// Theme
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
|
||||
const mode = useColorMode({
|
||||
attribute: 'data-theme',
|
||||
})
|
||||
import { useUserStore } from '@/stores/user'
|
||||
useUserStore().applyUserPrefs()
|
||||
|
37
resources/js_vue3/components/VersionChecker.vue
Normal file
37
resources/js_vue3/components/VersionChecker.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<script setup>
|
||||
import systemService from '@/services/systemService'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
|
||||
const appSettings = useAppSettingsStore()
|
||||
const isScanning = ref(false)
|
||||
const isUpToDate = ref(null)
|
||||
|
||||
async function getLatestRelease() {
|
||||
isScanning.value = true;
|
||||
|
||||
await systemService.getLastRelease()
|
||||
.then(response => {
|
||||
appSettings.latestRelease = response.data.newRelease
|
||||
isUpToDate.value = response.data.newRelease === false
|
||||
})
|
||||
|
||||
isScanning.value = false;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="columns is-mobile is-vcentered">
|
||||
<div class="column is-narrow">
|
||||
<button type="button" :class="isScanning ? 'is-loading' : ''" class="button is-link is-rounded is-small" @click="getLatestRelease">Check now</button>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span v-if="appSettings.latestRelease" class="mt-2 has-text-warning">
|
||||
<span class="release-flag"></span>{{ appSettings.latestRelease }} is available <a class="is-size-7" href="https://github.com/Bubka/2FAuth/releases">View on Github</a>
|
||||
</span>
|
||||
<span v-if="isUpToDate" class="has-text-grey">
|
||||
{{ $t('commons.you_are_up_to_date') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
44
resources/js_vue3/components/formElements/FormCheckbox.vue
Normal file
44
resources/js_vue3/components/formElements/FormCheckbox.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
fieldName: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
labelClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const attrs = useAttrs()
|
||||
const checked = ref(props.modelValue)
|
||||
|
||||
function setCheckbox() {
|
||||
if (attrs['disabled'] == undefined) {
|
||||
emit('update:modelValue', checked)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="field">
|
||||
<input :id="fieldName" type="checkbox" :name="fieldName" class="is-checkradio is-info" v-model="checked" v-on:change="setCheckbox" v-bind="$attrs"/>
|
||||
<label tabindex="0" :for="fieldName" class="label" :class="labelClass" v-html="$t(label)" v-on:keypress.space.prevent="setCheckbox" />
|
||||
<p class="help" v-html="$t(help)" v-if="help" />
|
||||
</div>
|
||||
</template>
|
39
resources/js_vue3/components/formElements/FormSelect.vue
Normal file
39
resources/js_vue3/components/formElements/FormSelect.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: [String, Number, Boolean],
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fieldName: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
|
||||
const selected = ref(props.modelValue)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="field">
|
||||
<label class="label" v-html="$t(label)"></label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select v-model="selected" v-on:change="$emit('update:modelValue', $event.target.value)">
|
||||
<option v-for="option in options" :value="option.value">{{ $t(option.text) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <FieldError :form="form" :field="fieldName" /> -->
|
||||
<p class="help" v-html="$t(help)" v-if="help"></p>
|
||||
</div>
|
||||
</template>
|
73
resources/js_vue3/components/formElements/FormToggle.vue
Normal file
73
resources/js_vue3/components/formElements/FormToggle.vue
Normal file
@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
import { useIdGenerator } from '@/composables/helpers'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: [String, Number, Boolean],
|
||||
choices: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
fieldName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
hasOffset: Boolean,
|
||||
isDisabled: Boolean,
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
help: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
|
||||
// defines what events our component emits
|
||||
const emit = defineEmits('update:modelValue')
|
||||
|
||||
function setRadio(event) {
|
||||
emit('update:modelValue', event)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="field" :class="{ 'pt-3': hasOffset }" role="radiogroup"
|
||||
:aria-labelledby="useIdGenerator('label',fieldName).inputId">
|
||||
<label v-if="label" :id="useIdGenerator('label',fieldName).inputId" class="label" v-html="$t(label)" />
|
||||
<div class="is-toggle buttons">
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<button
|
||||
v-for="choice in choices"
|
||||
:key="choice.value"
|
||||
:id="useIdGenerator('button',fieldName+choice.value).inputId"
|
||||
role="radio"
|
||||
type="button"
|
||||
class="button"
|
||||
:aria-checked="modelValue===choice.value"
|
||||
:disabled="isDisabled"
|
||||
:class="{
|
||||
'is-link': modelValue===choice.value,
|
||||
'is-dark': mode==='dark',
|
||||
'is-multiline': choice.legend,
|
||||
}"
|
||||
v-on:click.stop="setRadio(choice.value)"
|
||||
:title="choice.title? choice.title:''">
|
||||
<input
|
||||
:id="useIdGenerator('radio',choice.value).inputId"
|
||||
type="radio"
|
||||
class="is-hidden"
|
||||
:checked="modelValue===choice.value"
|
||||
:value="choice.value"
|
||||
:disabled="isDisabled" />
|
||||
<span v-if="choice.legend" v-html="$t(choice.legend)" class="is-block is-size-7" />
|
||||
<FontAwesomeIcon :icon="['fas',choice.icon]" v-if="choice.icon" class="mr-2" /> {{ $t(choice.text) }}
|
||||
</button>
|
||||
</UseColorMode>
|
||||
</div>
|
||||
<!-- <FieldError :form="form" :field="fieldName" /> -->
|
||||
<p class="help" v-html="$t(help)" v-if="help" />
|
||||
</div>
|
||||
</template>
|
45
resources/js_vue3/layouts/SettingTabs.vue
Normal file
45
resources/js_vue3/layouts/SettingTabs.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<script setup>
|
||||
const tabs = ref([
|
||||
{
|
||||
'name' : wTrans('settings.options'),
|
||||
'view' : 'settings.options',
|
||||
'id' : 'lnkTabOptions'
|
||||
},
|
||||
// {
|
||||
// 'name' : wTrans('settings.account'),
|
||||
// 'view' : 'settings.account',
|
||||
// 'id' : 'lnkTabAccount'
|
||||
// },
|
||||
// {
|
||||
// 'name' : wTrans('settings.oauth'),
|
||||
// 'view' : 'settings.oauth.tokens',
|
||||
// 'id' : 'lnkTabOAuth'
|
||||
// },
|
||||
// {
|
||||
// 'name' : wTrans('settings.webauthn'),
|
||||
// 'view' : 'settings.webauthn.devices',
|
||||
// 'id' : 'lnkTabWebauthn'
|
||||
// },
|
||||
])
|
||||
|
||||
const props = defineProps({
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="options-header">
|
||||
<ResponsiveWidthWrapper>
|
||||
<div class="tabs is-centered is-fullwidth">
|
||||
<ul>
|
||||
<li v-for="tab in tabs" :key="tab.view" :class="{ 'is-active': tab.view === props.activeTab }">
|
||||
<RouterLink :id="tab.id" :to="{ name: tab.view }">{{ tab.name }}</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</ResponsiveWidthWrapper>
|
||||
</div>
|
||||
</template>
|
32
resources/js_vue3/router/index.js
vendored
32
resources/js_vue3/router/index.js
vendored
@ -34,25 +34,25 @@ import noEmptyError from './middlewares/noEmptyError'
|
||||
const router = createRouter({
|
||||
history: createWebHistory('/'),
|
||||
routes: [
|
||||
// { path: '/start', name: 'start', component: Start, meta: { requiresAuth: true }, props: true },
|
||||
// { path: '/capture', name: 'capture', component: Capture, meta: { requiresAuth: true }, props: true },
|
||||
// { path: '/start', name: 'start', component: Start, meta: { middlewares: [authGuard] }, props: true },
|
||||
// { path: '/capture', name: 'capture', component: Capture, meta: { middlewares: [authGuard] }, props: true },
|
||||
|
||||
{ path: '/accounts', name: 'accounts', component: Accounts, meta: { middlewares: [authGuard], requiresAuth: true }, alias: '/', props: true },
|
||||
// { path: '/account/create', name: 'createAccount', component: CreateAccount, meta: { requiresAuth: true } },
|
||||
// { path: '/account/import', name: 'importAccounts', component: ImportAccount, meta: { requiresAuth: true } },
|
||||
// { path: '/account/:twofaccountId/edit', name: 'editAccount', component: EditAccount, meta: { requiresAuth: true } },
|
||||
// { path: '/account/:twofaccountId/qrcode', name: 'showQRcode', component: QRcodeAccount, meta: { requiresAuth: true } },
|
||||
{ path: '/accounts', name: 'accounts', component: Accounts, meta: { middlewares: [authGuard] }, alias: '/', props: true },
|
||||
// { path: '/account/create', name: 'createAccount', component: CreateAccount, meta: { middlewares: [authGuard] } },
|
||||
// { path: '/account/import', name: 'importAccounts', component: ImportAccount, meta: { middlewares: [authGuard] } },
|
||||
// { path: '/account/:twofaccountId/edit', name: 'editAccount', component: EditAccount, meta: { middlewares: [authGuard] } },
|
||||
// { path: '/account/:twofaccountId/qrcode', name: 'showQRcode', component: QRcodeAccount, meta: { middlewares: [authGuard] } },
|
||||
|
||||
// { path: '/groups', name: 'groups', component: Groups, meta: { requiresAuth: true }, props: true },
|
||||
// { path: '/group/create', name: 'createGroup', component: CreateGroup, meta: { requiresAuth: true } },
|
||||
// { path: '/group/:groupId/edit', name: 'editGroup', component: EditGroup, meta: { requiresAuth: true }, props: true },
|
||||
// { path: '/groups', name: 'groups', component: Groups, meta: { middlewares: [authGuard] }, props: true },
|
||||
// { path: '/group/create', name: 'createGroup', component: CreateGroup, meta: { middlewares: [authGuard] } },
|
||||
// { path: '/group/:groupId/edit', name: 'editGroup', component: EditGroup, meta: { middlewares: [authGuard] }, props: true },
|
||||
|
||||
{ path: '/settings/options', name: 'settings.options', component: SettingsOptions, meta: { requiresAuth: true, showAbout: true } },
|
||||
// { path: '/settings/account', name: 'settings.account', component: SettingsAccount, meta: { requiresAuth: true, showAbout: true } },
|
||||
// { path: '/settings/oauth', name: 'settings.oauth.tokens', component: SettingsOAuth, meta: { requiresAuth: true, showAbout: true } },
|
||||
// { path: '/settings/oauth/pat/create', name: 'settings.oauth.generatePAT', component: GeneratePAT, meta: { requiresAuth: true, showAbout: true } },
|
||||
// { path: '/settings/webauthn/:credentialId/edit', name: 'settings.webauthn.editCredential', component: EditCredential, meta: { requiresAuth: true, showAbout: true }, props: true },
|
||||
// { path: '/settings/webauthn', name: 'settings.webauthn.devices', component: SettingsWebAuthn, meta: { requiresAuth: true, showAbout: true } },
|
||||
{ path: '/settings/options', name: 'settings.options', component: SettingsOptions, meta: { middlewares: [authGuard], showAbout: true } },
|
||||
// { path: '/settings/account', name: 'settings.account', component: SettingsAccount, meta: { middlewares: [authGuard], showAbout: true } },
|
||||
// { path: '/settings/oauth', name: 'settings.oauth.tokens', component: SettingsOAuth, meta: { middlewares: [authGuard], showAbout: true } },
|
||||
// { path: '/settings/oauth/pat/create', name: 'settings.oauth.generatePAT', component: GeneratePAT, meta: { middlewares: [authGuard], showAbout: true } },
|
||||
// { path: '/settings/webauthn/:credentialId/edit', name: 'settings.webauthn.editCredential', component: EditCredential, meta: { middlewares: [authGuard], showAbout: true }, props: true },
|
||||
// { path: '/settings/webauthn', name: 'settings.webauthn.devices', component: SettingsWebAuthn, meta: { middlewares: [authGuard], showAbout: true } },
|
||||
|
||||
{ path: '/login', name: 'login', component: Login, meta: { disabledWithAuthProxy: true, showAbout: true } },
|
||||
{ path: '/register', name: 'register', component: Register, meta: { disabledWithAuthProxy: true, showAbout: true } },
|
||||
|
@ -13,6 +13,7 @@ export default async function auth({ to, next, stores }) {
|
||||
preferences: currentUser.preferences,
|
||||
isAdmin: currentUser.is_admin,
|
||||
})
|
||||
user.applyUserPrefs()
|
||||
}
|
||||
}
|
||||
|
||||
|
14
resources/js_vue3/services/appSettingService.js
vendored
Normal file
14
resources/js_vue3/services/appSettingService.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { httpClientFactory } from '@/services/httpClientFactory'
|
||||
|
||||
const apiClient = httpClientFactory('api')
|
||||
|
||||
export default {
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
update(name, value) {
|
||||
return apiClient.put('/settings/' + name, { value: value })
|
||||
},
|
||||
|
||||
}
|
14
resources/js_vue3/services/groupService.js
vendored
Normal file
14
resources/js_vue3/services/groupService.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { httpClientFactory } from '@/services/httpClientFactory'
|
||||
|
||||
const apiClient = httpClientFactory('api')
|
||||
|
||||
export default {
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
getAll() {
|
||||
return apiClient.get('groups')
|
||||
},
|
||||
|
||||
}
|
10
resources/js_vue3/services/systemService.js
vendored
10
resources/js_vue3/services/systemService.js
vendored
@ -5,10 +5,18 @@ const webClient = httpClientFactory('web')
|
||||
export default {
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
* @returns Promise
|
||||
*/
|
||||
getSystemInfos() {
|
||||
return webClient.get('infos')
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns Promise
|
||||
*/
|
||||
getLastRelease() {
|
||||
return webClient.get('latestRelease')
|
||||
}
|
||||
|
||||
}
|
14
resources/js_vue3/services/userPreferenceService.js
vendored
Normal file
14
resources/js_vue3/services/userPreferenceService.js
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { httpClientFactory } from '@/services/httpClientFactory'
|
||||
|
||||
const apiClient = httpClientFactory('api')
|
||||
|
||||
export default {
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
update(name, value) {
|
||||
return apiClient.put('/user/preferences/' + name, { value: value })
|
||||
},
|
||||
|
||||
}
|
16
resources/js_vue3/stores/appSettings.js
vendored
16
resources/js_vue3/stores/appSettings.js
vendored
@ -1,18 +1,14 @@
|
||||
import { defineStore } from 'pinia'
|
||||
// import { useApi } from '@/api/useAPI.js'
|
||||
|
||||
// const api = useApi()
|
||||
import appSettingService from '@/services/appSettingService'
|
||||
|
||||
export const useAppSettingsStore = defineStore({
|
||||
id: 'settings',
|
||||
id: 'appSettings',
|
||||
|
||||
state: () => {
|
||||
state: () => {
|
||||
return { ...window.appSettings }
|
||||
},
|
||||
|
||||
actions: {
|
||||
updateSetting(setting) {
|
||||
this.settings = { ...this.state.settings, ...setting }
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
|
||||
},
|
||||
})
|
||||
|
26
resources/js_vue3/stores/user.js
vendored
26
resources/js_vue3/stores/user.js
vendored
@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import authService from '@/services/authService'
|
||||
import router from '@/router'
|
||||
import { useColorMode } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'user',
|
||||
@ -20,9 +21,6 @@ export const useUserStore = defineStore({
|
||||
},
|
||||
|
||||
actions: {
|
||||
updatePreference(preference) {
|
||||
this.preferences = { ...this.state.preferences, ...preference }
|
||||
},
|
||||
|
||||
logout() {
|
||||
// async appLogout(evt) {
|
||||
@ -45,7 +43,29 @@ export const useUserStore = defineStore({
|
||||
reset() {
|
||||
localStorage.clear()
|
||||
this.$reset()
|
||||
this.applyUserPrefs()
|
||||
router.push({ name: 'login' })
|
||||
},
|
||||
|
||||
applyTheme() {
|
||||
const mode = useColorMode({
|
||||
attribute: 'data-theme',
|
||||
})
|
||||
mode.value = this.preferences.theme == 'system' ? 'auto' : this.preferences.theme
|
||||
},
|
||||
|
||||
applyLanguage() {
|
||||
const { isSupported, language } = useNavigatorLanguage()
|
||||
|
||||
if (isSupported) {
|
||||
loadLanguageAsync(this.preferences.lang == 'browser' ? language.value.slice(0, 2) : this.preferences.lang)
|
||||
}
|
||||
else loadLanguageAsync('en')
|
||||
},
|
||||
|
||||
applyUserPrefs() {
|
||||
this.applyTheme()
|
||||
this.applyLanguage()
|
||||
}
|
||||
|
||||
},
|
||||
|
@ -34,6 +34,7 @@
|
||||
preferences: response.data.preferences,
|
||||
isAdmin: response.data.is_admin,
|
||||
})
|
||||
user.applyTheme()
|
||||
|
||||
router.push({ name: 'accounts', params: { toRefresh: true } })
|
||||
})
|
||||
|
@ -1,7 +1,200 @@
|
||||
<script>
|
||||
<script setup>
|
||||
import SettingTabs from '@/layouts/SettingTabs.vue'
|
||||
import groupService from '@/services/groupService'
|
||||
import userPreferenceService from '@/services/userPreferenceService'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useAppSettingsStore } from '@/stores/appSettings'
|
||||
import { useNotifyStore } from '@/stores/notify'
|
||||
import { UseColorMode } from '@vueuse/components'
|
||||
import VersionChecker from '@/components/VersionChecker.vue'
|
||||
|
||||
const $2fauth = inject('2fauth')
|
||||
const user = useUserStore()
|
||||
const notify = useNotifyStore()
|
||||
const appSettings = useAppSettingsStore()
|
||||
const returnTo = useStorage($2fauth.prefix + 'returnTo', 'accounts')
|
||||
|
||||
const layouts = [
|
||||
{ text: 'settings.forms.grid', value: 'grid', icon: 'th' },
|
||||
{ text: 'settings.forms.list', value: 'list', icon: 'list' },
|
||||
]
|
||||
const themes = [
|
||||
{ text: 'settings.forms.light', value: 'light', icon: 'sun' },
|
||||
{ text: 'settings.forms.dark', value: 'dark', icon: 'moon' },
|
||||
{ text: 'settings.forms.automatic', value: 'system', icon: 'desktop' },
|
||||
]
|
||||
const passwordFormats = [
|
||||
{ text: '12 34 56', value: 2, legend: 'settings.forms.pair', title: 'settings.forms.pair_legend' },
|
||||
{ text: '123 456', value: 3, legend: 'settings.forms.trio', title: 'settings.forms.trio_legend' },
|
||||
{ text: '1234 5678', value: 0.5, legend: 'settings.forms.half', title: 'settings.forms.half_legend' },
|
||||
]
|
||||
const kickUserAfters = [
|
||||
{ text: 'settings.forms.never', value: 0 },
|
||||
{ text: 'settings.forms.on_otp_copy', value: -1 },
|
||||
{ text: 'settings.forms.1_minutes', value: 1 },
|
||||
{ text: 'settings.forms.5_minutes', value: 5 },
|
||||
{ text: 'settings.forms.10_minutes', value: 10 },
|
||||
{ text: 'settings.forms.15_minutes', value: 15 },
|
||||
{ text: 'settings.forms.30_minutes', value: 30 },
|
||||
{ text: 'settings.forms.1_hour', value: 60 },
|
||||
{ text: 'settings.forms.1_day', value: 1440 },
|
||||
]
|
||||
const groups = [
|
||||
{ text: 'groups.no_group', value: 0 },
|
||||
{ text: 'groups.active_group', value: -1 },
|
||||
]
|
||||
const captureModes = [
|
||||
{ text: 'settings.forms.livescan', value: 'livescan' },
|
||||
{ text: 'settings.forms.upload', value: 'upload' },
|
||||
{ text: 'settings.forms.advanced_form', value: 'advancedForm' },
|
||||
]
|
||||
const getOtpTriggers = [
|
||||
{ text: 'settings.forms.otp_generation_on_request', value: true, legend: 'settings.forms.otp_generation_on_request_legend', title: 'settings.forms.otp_generation_on_request_title' },
|
||||
{ text: 'settings.forms.otp_generation_on_home', value: false, legend: 'settings.forms.otp_generation_on_home_legend', title: 'settings.forms.otp_generation_on_home_title' },
|
||||
]
|
||||
|
||||
const langs = computed(() => {
|
||||
let locales = [{
|
||||
text: 'languages.browser_preference',
|
||||
value: 'browser'
|
||||
}];
|
||||
|
||||
for (const locale of $2fauth.langs) {
|
||||
locales.push({
|
||||
text: 'languages.' + locale,
|
||||
value: locale
|
||||
})
|
||||
}
|
||||
return locales
|
||||
})
|
||||
|
||||
user.$subscribe((mutation) => {
|
||||
userPreferenceService.update(mutation.events.key, mutation.events.newValue).then(response => {
|
||||
useNotifyStore().info({ type: 'is-success', text: trans('settings.forms.setting_saved') })
|
||||
|
||||
if(mutation.events.key === 'lang' && getActiveLanguage() !== mutation.events.newValue) {
|
||||
user.applyLanguage()
|
||||
}
|
||||
else if(mutation.events.key === 'theme') {
|
||||
user.applyTheme()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
appSettings.$subscribe((mutation) => {
|
||||
appSettingService.update(mutation.events.key, mutation.events.newValue).then(response => {
|
||||
useNotifyStore().info({ type: 'is-success', text: trans('settings.forms.setting_saved') })
|
||||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
groupService.getAll().then(response => {
|
||||
response.data.forEach((data) => {
|
||||
if( data.id >0 ) {
|
||||
groups.push({
|
||||
text: data.name,
|
||||
value: data.id
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeRouteLeave((to) => {
|
||||
if (! to.name.startsWith('settings.')) {
|
||||
notify.clear()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p>toto</p>
|
||||
<div>
|
||||
<SettingTabs activeTab="settings.options"></SettingTabs>
|
||||
<div class="options-tabs">
|
||||
<FormWrapper>
|
||||
<form>
|
||||
<!-- <input type="hidden" name="isReady" id="isReady" :value="isReady" /> -->
|
||||
<!-- user preferences -->
|
||||
<div class="block">
|
||||
<h4 class="title is-4 has-text-grey-light">{{ $t('settings.general') }}</h4>
|
||||
<!-- Language -->
|
||||
<FormSelect v-model="user.preferences.lang" :options="langs" fieldName="lang" label="settings.forms.language.label" help="settings.forms.language.help" />
|
||||
<div class="field help">
|
||||
{{ $t('settings.forms.some_translation_are_missing') }}
|
||||
<a class="ml-2" href="https://crowdin.com/project/2fauth">
|
||||
{{ $t('settings.forms.help_translate_2fauth') }}
|
||||
<FontAwesomeIcon :icon="['fas', 'external-link-alt']" />
|
||||
</a>
|
||||
</div>
|
||||
<!-- display mode -->
|
||||
<FormToggle v-model="user.preferences.displayMode" :choices="layouts" fieldName="displayMode" label="settings.forms.display_mode.label" help="settings.forms.display_mode.help"/>
|
||||
<!-- theme -->
|
||||
<FormToggle v-model="user.preferences.theme" :choices="themes" fieldName="theme" label="settings.forms.theme.label" help="settings.forms.theme.help"/>
|
||||
<!-- show icon -->
|
||||
<FormCheckbox v-model="user.preferences.showAccountsIcons" fieldName="showAccountsIcons" label="settings.forms.show_accounts_icons.label" help="settings.forms.show_accounts_icons.help" />
|
||||
<!-- Official icons -->
|
||||
<FormCheckbox v-model="user.preferences.getOfficialIcons" fieldName="getOfficialIcons" label="settings.forms.get_official_icons.label" help="settings.forms.get_official_icons.help" />
|
||||
<!-- password format -->
|
||||
<FormCheckbox v-model="user.preferences.formatPassword" fieldName="formatPassword" label="settings.forms.password_format.label" help="settings.forms.password_format.help" />
|
||||
<FormToggle v-model="user.preferences.formatPasswordBy" :choices="passwordFormats" fieldName="formatPasswordBy" />
|
||||
|
||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('groups.groups') }}</h4>
|
||||
<!-- default group -->
|
||||
<FormSelect v-model="user.preferences.defaultGroup" :options="groups" fieldName="defaultGroup" label="settings.forms.default_group.label" help="settings.forms.default_group.help" />
|
||||
<!-- retain active group -->
|
||||
<FormCheckbox v-model="user.preferences.rememberActiveGroup" fieldName="rememberActiveGroup" label="settings.forms.remember_active_group.label" help="settings.forms.remember_active_group.help" />
|
||||
|
||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.security') }}</h4>
|
||||
<!-- auto lock -->
|
||||
<FormSelect v-model="user.preferences.kickUserAfter" :options="kickUserAfters" fieldName="kickUserAfter" label="settings.forms.auto_lock.label" help="settings.forms.auto_lock.help" />
|
||||
<!-- get OTP on request -->
|
||||
<FormToggle v-model="user.preferences.getOtpOnRequest" :choices="getOtpTriggers" fieldName="getOtpOnRequest" label="settings.forms.otp_generation.label" help="settings.forms.otp_generation.help"/>
|
||||
<!-- otp as dot -->
|
||||
<FormCheckbox v-model="user.preferences.showOtpAsDot" fieldName="showOtpAsDot" label="settings.forms.show_otp_as_dot.label" help="settings.forms.show_otp_as_dot.help" />
|
||||
<!-- close otp on copy -->
|
||||
<FormCheckbox v-model="user.preferences.closeOtpOnCopy" fieldName="closeOtpOnCopy" label="settings.forms.close_otp_on_copy.label" help="settings.forms.close_otp_on_copy.help" :disabled="!user.preferences.getOtpOnRequest" />
|
||||
<!-- copy otp on get -->
|
||||
<FormCheckbox v-model="user.preferences.copyOtpOnDisplay" fieldName="copyOtpOnDisplay" label="settings.forms.copy_otp_on_display.label" help="settings.forms.copy_otp_on_display.help" :disabled="!user.preferences.getOtpOnRequest" />
|
||||
|
||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.data_input') }}</h4>
|
||||
<!-- basic qrcode -->
|
||||
<FormCheckbox v-model="user.preferences.useBasicQrcodeReader" fieldName="useBasicQrcodeReader" label="settings.forms.use_basic_qrcode_reader.label" help="settings.forms.use_basic_qrcode_reader.help" />
|
||||
<!-- direct capture -->
|
||||
<FormCheckbox v-model="user.preferences.useDirectCapture" fieldName="useDirectCapture" label="settings.forms.useDirectCapture.label" help="settings.forms.useDirectCapture.help" />
|
||||
<!-- default capture mode -->
|
||||
<FormSelect v-model="user.preferences.defaultCaptureMode" :options="captureModes" fieldName="defaultCaptureMode" label="settings.forms.defaultCaptureMode.label" help="settings.forms.defaultCaptureMode.help" />
|
||||
</div>
|
||||
<!-- Admin settings -->
|
||||
<div v-if="user.isAdmin">
|
||||
<h4 class="title is-4 pt-4 has-text-grey-light">{{ $t('settings.administration') }}</h4>
|
||||
<div class="is-size-7-mobile block" v-html="$t('settings.administration_legend')"></div>
|
||||
<!-- Check for update -->
|
||||
<FormCheckbox v-model="appSettings.checkForUpdate" fieldName="checkForUpdate" label="commons.check_for_update" help="commons.check_for_update_help" />
|
||||
<VersionChecker />
|
||||
<!-- protect db -->
|
||||
<FormCheckbox v-model="appSettings.useEncryption" fieldName="useEncryption" label="settings.forms.use_encryption.label" help="settings.forms.use_encryption.help" />
|
||||
<!-- disable registration -->
|
||||
<FormCheckbox v-model="appSettings.disableRegistration" fieldName="disableRegistration" label="settings.forms.disable_registration.label" help="settings.forms.disable_registration.help" />
|
||||
</div>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
</div>
|
||||
<VueFooter :showButtons="true">
|
||||
<!-- Close button -->
|
||||
<p class="control">
|
||||
<UseColorMode v-slot="{ mode }">
|
||||
<RouterLink
|
||||
id="btnClose"
|
||||
:to="{ name: returnTo }"
|
||||
class="button is-rounded"
|
||||
:class="{'is-dark' : mode === 'dark'}"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('commons.close_the_x_page', {pagetitle: $route.meta.title})">
|
||||
{{ $t('commons.close') }}
|
||||
</RouterLink>
|
||||
</UseColorMode>
|
||||
</p>
|
||||
</VueFooter>
|
||||
</div>
|
||||
</template>
|
7
vite.config.js
vendored
7
vite.config.js
vendored
@ -38,10 +38,15 @@ export default defineConfig({
|
||||
'@vueuse/core': [
|
||||
'useStorage',
|
||||
'useClipboard',
|
||||
'useNavigatorLanguage'
|
||||
],
|
||||
'laravel-vue-i18n': [
|
||||
'i18nVue',
|
||||
'trans'
|
||||
'trans',
|
||||
'wTrans',
|
||||
'getActiveLanguage',
|
||||
'loadLanguageAsync',
|
||||
'getActiveLanguage'
|
||||
],
|
||||
'@kyvg/vue3-notification': [
|
||||
'useNotification'
|
||||
|
Loading…
Reference in New Issue
Block a user