Cleanup and fixes

This commit is contained in:
Antoine Nguyen
2024-07-18 13:15:30 +02:00
parent fa099382d1
commit f4036af90e
13 changed files with 15 additions and 747 deletions
@@ -151,9 +151,9 @@ function fetchAlarms({ page, itemsPerPage, sortBy }) {
page: page || 1,
page_size: itemsPerPage || 10,
}
if (sortBy) {
if (sortBy && sortBy.length) {
params.ordering = sortBy
.map((item) => (this.options.sortDesc[0] ? `-${item}` : item))
.map((item) => (item.order !== 'asc' ? `-${item.key}` : item.key))
.join(',')
}
if (search.value !== '') {
@@ -177,7 +177,7 @@ async function deleteAlarm(alarm, single = true) {
}
async function deleteAlarms() {
await alarmsApi.bulkDelete(selected.value.map((alarm) => alarm.id))
busStore.displayNotification({ msg: this.$gettext('Alarms deleted') })
busStore.displayNotification({ msg: $gettext('Alarms deleted') })
selected.value = []
fetchAlarms({})
}
@@ -1,141 +0,0 @@
<template>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col
cols="12"
sm="6"
class="d-flex flex-column justify-center pa-10"
@keyup.enter="changePassword"
>
<h4 class="text-primary mb-6 text-h4">
{{ $gettext('Forgot password?') }}
</h4>
<v-form ref="observer">
<p id="password_validation">
{{ password_validation_error }}
</p>
<label class="m-label">{{ $gettext('Change password') }}</label>
<v-text-field
v-model="password"
type="password"
variant="outlined"
:rules="[rules.required]"
/>
<v-text-field
v-model="password_confirmed"
type="password"
variant="outlined"
:rules="[
rules.required,
(value) => rules.samePassword(value, password),
]"
/>
</v-form>
<div class="d-flex justify-center">
<v-btn
class="flex-grow-1"
color="primary"
size="large"
:loading="loading"
@click="changePassword"
>
{{ $gettext('Submit') }}
</v-btn>
</div>
<div>
<router-link :to="{ name: 'Login' }" class="float-right text-primary">
{{ $gettext('Return to login') }}
</router-link>
</div>
</v-col>
</v-row>
</v-container>
</template>
<script setup lang="js">
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext'
import auth from '@/api/auth'
import rules from '@/plugins/rules.js'
const { $gettext } = useGettext()
const loading = ref(false)
const password = ref('')
const password_confirmed = ref('')
const password_validation_error = ref('')
const route = useRoute()
const router = useRouter()
const observer = ref(null)
let token
let id
function checkUrl() {
if (route.params.token === undefined) {
if (token === '' || id === '') {
router.push({ name: 'PasswordRecovery' })
}
} else {
const decodedId = atob(route.params.id)
if (!/^\d+$/.test(decodedId)) {
router.push({ name: 'PasswordRecovery' })
} else {
id = route.params.id
token = route.params.token
router.push({ name: 'PasswordRecoveryChangeForm' })
}
}
}
async function changePassword() {
const { valid } = await observer.value.validate()
if (!valid) {
return
}
const payload = {
new_password1: password.value,
new_password2: password_confirmed.value,
token: token,
id: id,
}
loading.value = true
password_validation_error.value = ''
auth
.changePassword(payload)
.then((resp) => {
if (resp.status === 200) {
loading.value = false
router.push({ name: 'Login' })
}
})
.catch((err) => {
let message = ''
loading.value = false
if (err.response.status === 403) {
message = $gettext('Invalid reset token.')
} else if (err.response.status === 404) {
message = $gettext('User unknown.')
} else if (
err.response.status === 400 &&
err.response.data.type === 'password_requirement'
) {
err.response.data.errors.forEach((element) => {
message += $gettext(element) + '<br>'
})
} else if (err.response.status === 429) {
message = $gettext('Too many unsuccessful attempts, please try later.')
}
password_validation_error.value = message
})
}
checkUrl()
</script>
<style>
#password_validation {
color: crimson;
}
</style>
@@ -1,130 +0,0 @@
<template>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col
cols="12"
sm="10"
md="8"
class="text-center"
@keyup.enter="recoverPassword"
>
<h4 class="text-primary mb-10 text-h4">
{{ $gettext('Forgot password?') }}
</h4>
<v-form ref="observer" class="my-4">
<label class="m-label">{{
$gettext('Please fill-in your primary email address')
}}</label>
<v-text-field
v-model="email"
variant="outlined"
:rules="[rules.required, rules.email]"
:error-messages="errors"
/>
</v-form>
<div class="d-flex justify-center">
<v-btn
class="flex-grow-1"
color="primary"
size="large"
:loading="loading"
@click="recoverPassword"
>
{{ $gettext('Submit') }}
</v-btn>
</div>
<div>
<a class="float-right text-primary" @click="returnToLogin">{{
$gettext('Return to login')
}}</a>
</div>
</v-col>
<ConfirmDialog ref="dialog" />
</v-row>
</v-container>
</template>
<script setup lang="js">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext'
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
import auth from '@/api/auth'
import rules from '@/plugins/rules.js'
const router = useRouter()
const { $gettext } = useGettext()
const loading = ref(false)
const email = ref('')
const dialog = ref(null)
const observer = ref(null)
const errors = ref([])
function returnToLogin() {
router.push({ name: 'Login' })
}
async function showDialog(title, body, error = false) {
let color = 'info'
if (error) {
color = 'error'
}
const confirm = await dialog.value.open(title, body, {
color: color,
cancelLabel: $gettext('Ok'),
agreeLabel: $gettext('Return to login'),
})
if (!confirm) {
return
}
returnToLogin()
}
async function recoverPassword() {
const { valid } = await observer.value.validate()
if (!valid) {
return
}
const payload = {
email: email.value,
}
loading.value = true
auth
.recoverPassword(payload)
.then(async (resp) => {
loading.value = false
if (resp.status === 200) {
if (resp.data.type === 'sms') {
router.push({ name: 'PasswordRecoverySmsTotpForm' })
} else if (resp.data.type === 'email') {
await showDialog(
'Info',
$gettext('Email sent, please check your inbox')
)
}
}
})
.catch((err) => {
loading.value = false
if (err.response.status === 404) {
if (
err.response.data.type === 'sms' ||
err.response.data.type === 'email'
) {
errors.value = [$gettext(err.response.data.reason)]
}
} else if (
err.response.status === 503 &&
err.response.data.type === 'email'
) {
showDialog('Error', err.response.data.reason, true)
} else if (err.response.status === 429) {
errors.value = [
$gettext('Too many unsuccessful attempts, please try later.'),
]
}
})
}
</script>
@@ -1,140 +0,0 @@
<template>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col
cols="12"
sm="6"
class="d-flex flex-column justify-center pa-10"
@keyup.enter="changePassword"
>
<ConfirmDialog ref="confirm" />
<h4 class="text-primary mb-6 text-h4">
{{ $gettext('Password recovery') }}
</h4>
<v-form ref="observer">
<label class="m-label"
>{{ $gettext("Enter the code you've just received by SMS") }}
</label>
<v-text-field
v-model="sms_totp"
type="totp"
variant="outlined"
:rules="[rules.required, (value) => rules.length(value, 6)]"
:error-messages="errors"
/>
</v-form>
<div class="d-flex justify-center">
<v-btn
class="flex-grow-1"
color="primary"
size="large"
:loading="loading"
@click="checkSmsTotp"
>
{{ $gettext('Submit') }}
</v-btn>
</div>
<div>
<a class="float-left text-primary" @click="resendSms">{{
$gettext('Resend SMS.')
}}</a>
<router-link
:to="{ name: 'Login' }"
class="float-right text-primary"
>{{ $gettext('Return to login') }}</router-link
>
</div>
</v-col>
</v-row>
</v-container>
</template>
<script setup lang="js">
import { ref } from 'vue'
import auth from '@/api/auth'
import rules from '@/plugins/rules.js'
import { useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext'
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
const { $gettext } = useGettext()
const router = useRouter()
const confirm = ref(null)
const loading = ref(false)
const observer = ref(null)
const sms_totp = ref('')
const errors = ref([])
async function showErrorDialog(body) {
const confirm = await confirm.value.open($gettext('Error'), body, {
color: 'error',
cancelLabel: $gettext('Return to login'),
agreeLabel: $gettext('Retry reset'),
})
if (!confirm) {
router.push({ name: 'Login' })
} else {
router.push({ name: 'PasswordRecoveryForm' })
}
}
async function resendSms() {
auth
.checkSmsTotp({ type: 'resend' })
.then((resp) => {
loading.value = false
if (resp.status === 200) {
observer.value.setErrors({
sms_totp: this.$gettext('TOTP resent.'),
})
}
})
.catch(async (err) => {
if (err.response.status === 400) {
this.loading = false
await showErrorDialog(
$gettext(
'User seems wrong, return to login or restart reset the process?'
)
)
} else if (err.response.status === 429) {
errors.value = [
$gettext('Too many unsuccessful attempts, please try later.'),
]
}
})
}
async function checkSmsTotp() {
const { valid } = await observer.value.validate()
if (!valid) {
return
}
const payload = {
type: 'confirm',
sms_totp: this.sms_totp,
}
loading.value = true
auth
.checkSmsTotp(payload)
.then((resp) => {
loading.value = false
if (resp.status === 200) {
// Code is good, redirecting to change password form.
router.push({
name: 'PasswordRecoveryChangeForm',
data: resp.response.data,
})
}
})
.catch(async (err) => {
loading.value = false
if (err.response.status === 403 && 'reason' in err.response.data) {
await showErrorDialog(err.response.data.reason)
} else if (err.response.status === 400 && 'reason' in err.response.data) {
errors.value = [$gettext(err.response.data.reason)]
}
})
}
</script>
@@ -1,78 +0,0 @@
<template>
<v-container class="fill-height" fuild>
<v-row align="center" justify="center" dense>
<v-col cols="12" sm="10" md="8" lg="8" class="text-center">
<h4 class="text-primary mb-10 text-h4">
{{ $gettext('Two-factor authentication') }}
</h4>
<v-form ref="loginForm" @submit.prevent="verifyCode">
<div class="text-body-2">
{{
$gettext(
'Enter the code from the two-factor app on your mobile device. If you have lost your device, you may enter one of your recovery codes.'
)
}}
</div>
<v-otp-input
ref="otpInput"
v-model="code"
length="6"
:rules="[rules.required]"
@finish="verifyCode"
/>
<v-btn
block
color="primary"
size="large"
type="submit"
:loading="loading"
class="mb-5"
>
{{ $gettext('Verify code') }}
</v-btn>
</v-form>
</v-col>
</v-row>
</v-container>
</template>
<script setup lang="js">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores'
import { useGettext } from 'vue3-gettext'
import account from '@/api/account'
import Cookies from 'js-cookie'
import rules from '@/plugins/rules.js'
const authStore = useAuthStore()
const router = useRouter()
const { $gettext } = useGettext()
const loading = ref(false)
const code = ref('')
const loginForm = ref()
async function verifyCode() {
const { valid } = await loginForm.value.validate()
if (!valid) {
return
}
loading.value = true
account
.verifyTFACode(code.value)
.then((resp) => {
Cookies.set('token', resp.data.access, { sameSite: 'strict' })
Cookies.set('refreshToken', resp.data.refresh, {
sameSite: 'strict',
})
authStore.initialize().then(() => {
// bus.$emit('loggedIn')
router.push({ name: 'DomainList' })
})
})
.catch(() => {
loading.value = false
})
}
</script>
-22
View File
@@ -12,33 +12,11 @@ const routes = [
name: 'Login',
component: () => import('@/views/login/LoginView.vue'),
},
{
path: 'twofa',
name: 'TwoFA',
component: () => import('@/views/login/TwoFA.vue'),
},
{
path: 'logged',
name: 'LoginCallback',
component: () => import('@/views/login/LoginCallbackView.vue'),
},
{
path: 'password_recovery',
name: 'PasswordRecovery',
component: () => import('../views/login/PasswordRecoveryView.vue'),
},
{
path: 'password_recovery/confirm/:id?/:token?/',
name: 'PasswordRecoveryChangeForm',
component: () =>
import('../views/login/PasswordRecoveryChangeView.vue'),
},
{
path: 'password_recovery/sms_confirm',
name: 'PasswordRecoverySms',
component: () =>
import('../views/login/PasswordRecoverySmsTotpView.vue'),
},
],
},
{
@@ -65,3 +65,9 @@ const tab = ref()
background-color: #f7f8fa !important;
}
</style>
<style>
.v-window__container {
width: 100% !important;
}
</style>
@@ -195,3 +195,9 @@ loadParams(route.params.app)
color: #ff5252 !important;
}
</style>
<style>
.v-window__container {
width: 100% !important;
}
</style>
@@ -1,26 +0,0 @@
<template>
<template v-if="smAndDown">
<PasswordRecoveryChangeForm />
</template>
<template v-else>
<v-row class="fill-height ma-0">
<v-col class="bg-primary">
<v-img
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
max-width="300"
class="mt-10 ml-8"
/>
</v-col>
<v-col class="text-center">
<PasswordRecoveryChangeForm />
</v-col>
</v-row>
</template>
</template>
<script setup lang="js">
import PasswordRecoveryChangeForm from '@/components/login/PasswordRecoveryChangeForm.vue'
import { useDisplay } from 'vuetify'
const { smAndDown } = useDisplay()
</script>
@@ -1,129 +0,0 @@
<template>
<v-row style="height: 100%; margin: 0 auto">
<v-col cols="0" sm="6" class="primary" style="height: 100%">
<v-img
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
max-width="300"
class="mt-10 ml-8"
/>
</v-col>
<v-col
cols="12"
sm="6"
class="d-flex flex-column justify-center pa-10"
@keyup.enter="recoverPassword"
>
<span class="text-primary mb-6 text-h4">{{
$gettext('Forgot password?')
}}</span>
<v-form ref="observer">
<label class="m-label">{{
$gettext('Please fill-in your primary email address')
}}</label>
<v-text-field
v-model="email"
variant="outlined"
:rules="[rules.required, rules.email]"
:error-messages="errors"
/>
</v-form>
<div class="d-flex justify-center">
<v-btn
class="flex-grow-1"
color="primary"
size="large"
:loading="loading"
@click="recoverPassword"
>
{{ $gettext('Submit') }}
</v-btn>
</div>
<div>
<a :to="{ name: 'Login' }" class="float-right text-primary">{{
$gettext('Return to login')
}}</a>
</div>
</v-col>
<ConfirmDialog ref="confirm" />
</v-row>
</template>
<script setup lang="js">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext'
import ConfirmDialog from '@/components/tools/ConfirmDialog.vue'
import auth from '@/api/auth'
import rules from '@/plugins/rules.js'
const router = useRouter()
const { $gettext } = useGettext()
const loading = ref(false)
const email = ref('')
const confirm = ref(null)
const errors = ref([])
async function showDialog(title, body, error = false) {
let color = 'info'
if (error) {
color = 'error'
}
const result = await confirm.value.open(title, body, {
color: color,
cancelLabel: $gettext('Ok'),
agreeLabel: $gettext('Return to login'),
})
if (!result) {
return
}
router.push({ name: 'Login' })
}
async function recoverPassword() {
const { valid } = await this.$refs.observer.validate()
if (!valid) {
return
}
const payload = {
email: email.value,
}
loading.value = true
auth
.recoverPassword(payload)
.then(async (resp) => {
loading.value = false
if (resp.status === 200) {
if (resp.data.type === 'sms') {
router.push({ name: 'PasswordRecoverySms' })
} else if (resp.data.type === 'email') {
await showDialog(
$gettext('Info'),
$gettext('Email sent, please check your inbox')
)
}
}
})
.catch(async (err) => {
loading.value = false
if (err.response.status === 404) {
if (
err.response.data.type === 'sms' ||
err.response.data.type === 'email'
) {
errors.value = [err.response.data.reason]
}
} else if (
err.response.status === 503 &&
err.response.data.type === 'email'
) {
await showDialog($gettext('Error'), err.response.data.reason, true)
} else if (err.response.status === 429) {
errors.value = [
$gettext('Too many unsuccessful attempts, please try later.'),
]
}
})
}
</script>
@@ -1,26 +0,0 @@
<template>
<template v-if="smAndDown">
<PasswordRecoverySmsTotpForm />
</template>
<template v-else>
<v-row class="fill-height ma-0">
<v-col class="bg-primary">
<v-img
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
max-width="300"
class="mt-10 ml-8"
/>
</v-col>
<v-col class="text-center">
<PasswordRecoverySmsTotpForm />
</v-col>
</v-row>
</template>
</template>
<script setup lang="js">
import PasswordRecoverySmsTotpForm from '@/components/login/PasswordRecoverySmsTotpForm.vue'
import { useDisplay } from 'vuetify'
const { smAndDown } = useDisplay()
</script>
@@ -1,26 +0,0 @@
<template>
<template v-if="smAndDown">
<PasswordRecoveryForm />
</template>
<template v-else>
<v-row class="fill-height ma-0">
<v-col class="bg-primary">
<v-img
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
max-width="300"
class="mt-10 ml-8"
/>
</v-col>
<v-col class="text-center">
<PasswordRecoveryForm />
</v-col>
</v-row>
</template>
</template>
<script setup lang="js">
import PasswordRecoveryForm from '@/components/login/PasswordRecoveryForm.vue'
import { useDisplay } from 'vuetify'
const { smAndDown } = useDisplay()
</script>
-26
View File
@@ -1,26 +0,0 @@
<template>
<template v-if="smAndDown">
<TwofaForm />
</template>
<template v-else>
<v-row class="fill-height ma-0">
<v-col class="bg-primary">
<v-img
src="@/assets/Modoboa_RVB-BLANC-SANS.png"
max-width="300"
class="mt-10 ml-8"
/>
</v-col>
<v-col class="text-center">
<TwofaForm />
</v-col>
</v-row>
</template>
</template>
<script setup lang="js">
import TwofaForm from '@/components/login/TwofaForm.vue'
import { useDisplay } from 'vuetify'
const { smAndDown } = useDisplay()
</script>