Many stuphs
This commit is contained in:
@ -19,7 +19,8 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||||
boot: [
|
boot: [
|
||||||
'pocketbase'
|
'pocketbase',
|
||||||
|
'helper'
|
||||||
],
|
],
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||||
|
|||||||
33
src/App.vue
33
src/App.vue
@ -6,6 +6,37 @@
|
|||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'App'
|
name: 'App',
|
||||||
|
async created () {
|
||||||
|
if (await this.loggedIn()) {
|
||||||
|
this.$router.push('/dashboard')
|
||||||
|
} else {
|
||||||
|
this.$router.push('/')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$router.beforeEach(async (to, from, next) => {
|
||||||
|
if (await this.loggedIn()) {
|
||||||
|
if (to.path === '/') {
|
||||||
|
this.$router.push('/dashboard')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('logged out')
|
||||||
|
if (to.path !== '/') {
|
||||||
|
this.$router.push('/')
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loggedIn () {
|
||||||
|
let creds = await this.$helpers.getStoredCredentials()
|
||||||
|
let logged = await this.$helpers.verifyCredentials(creds)
|
||||||
|
return logged
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
106
src/boot/helper.js
Normal file
106
src/boot/helper.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { boot } from 'quasar/wrappers'
|
||||||
|
import { Notify, Dialog, LocalStorage } from 'quasar'
|
||||||
|
|
||||||
|
// ...existing code...
|
||||||
|
const helpers = {
|
||||||
|
sleep (ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
},
|
||||||
|
|
||||||
|
formatDate (date, options = { year: 'numeric', month: 'short', day: 'numeric' }, locale = 'fr-FR') {
|
||||||
|
const d = (date instanceof Date) ? date : new Date(date)
|
||||||
|
return new Intl.DateTimeFormat(locale, options).format(d)
|
||||||
|
},
|
||||||
|
|
||||||
|
formatCurrency (amount, locale = 'fr-FR', currency = 'EUR') {
|
||||||
|
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount)
|
||||||
|
},
|
||||||
|
|
||||||
|
uuidv4 () {
|
||||||
|
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
||||||
|
const rnds = new Uint8Array(16)
|
||||||
|
crypto.getRandomValues(rnds)
|
||||||
|
rnds[6] = (rnds[6] & 0x0f) | 0x40
|
||||||
|
rnds[8] = (rnds[8] & 0x3f) | 0x80
|
||||||
|
const toHex = (n) => n.toString(16).padStart(2, '0')
|
||||||
|
return [...rnds].map(toHex).join('').replace(/^(.{8})(.{4})(.{4})(.{4})(.+)$/, '$1-$2-$3-$4-$5')
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||||
|
const r = Math.random() * 16 | 0
|
||||||
|
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
||||||
|
return v.toString(16)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async hashSHA256 (text) {
|
||||||
|
if (!text) return ''
|
||||||
|
const enc = new TextEncoder()
|
||||||
|
const data = enc.encode(text)
|
||||||
|
const subtle = (typeof crypto !== 'undefined' && crypto.subtle) ? crypto.subtle : (window.crypto ? window.crypto.subtle : null)
|
||||||
|
if (!subtle) throw new Error('Web Crypto API not available')
|
||||||
|
const hash = await subtle.digest('SHA-256', data)
|
||||||
|
return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('')
|
||||||
|
},
|
||||||
|
|
||||||
|
copyToClipboard (text) {
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
return navigator.clipboard.writeText(text)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const ta = document.createElement('textarea')
|
||||||
|
ta.value = text
|
||||||
|
ta.setAttribute('readonly', '')
|
||||||
|
ta.style.position = 'absolute'
|
||||||
|
ta.style.left = '-9999px'
|
||||||
|
document.body.appendChild(ta)
|
||||||
|
ta.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(ta)
|
||||||
|
return Promise.resolve()
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
notify (opts) {
|
||||||
|
if (typeof opts === 'string') opts = { message: opts }
|
||||||
|
return Notify.create(opts)
|
||||||
|
},
|
||||||
|
|
||||||
|
async confirm (title = 'Confirmer', message = '', opts = {}) {
|
||||||
|
try {
|
||||||
|
await Dialog.create(Object.assign({ title, message, cancel: true, persistent: true }, opts))
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getStoredCredentials () {
|
||||||
|
// Le projet utilise LocalStorage.getKey('credentials') dans Index.vue
|
||||||
|
// if (LocalStorage && typeof LocalStorage.getKey === 'function') return LocalStorage.getKey('credentials')
|
||||||
|
if (LocalStorage && typeof LocalStorage.getItem === 'function') return LocalStorage.getItem('credentials')
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
setStoredCredentials (creds) {
|
||||||
|
// if (LocalStorage && typeof LocalStorage.setKey === 'function') return LocalStorage.setKey('credentials', creds)
|
||||||
|
if (LocalStorage && typeof LocalStorage.setItem === 'function') return LocalStorage.setItem('credentials', creds)
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
|
||||||
|
async verifyCredentials (creds) {
|
||||||
|
const credsOk = await this.hashSHA256(creds) === '48b0723e62bf43bfad1cfd12406584d8161fa7466aa376465ed5e0ccb9811bde'
|
||||||
|
if (credsOk) this.setStoredCredentials(creds)
|
||||||
|
return credsOk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
// expose as this.$helpers in Options API
|
||||||
|
app.config.globalProperties.$helpers = helpers
|
||||||
|
})
|
||||||
|
|
||||||
|
export { helpers }
|
||||||
@ -191,6 +191,7 @@ export default {
|
|||||||
})
|
})
|
||||||
.onOk(() => {
|
.onOk(() => {
|
||||||
this.$q.localStorage.remove('username')
|
this.$q.localStorage.remove('username')
|
||||||
|
this.$q.localStorage.remove('credentials')
|
||||||
this.$router.push('/')
|
this.$router.push('/')
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page class="flex flex-center flex-">
|
<q-page class="flex flex-center">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img alt="Quasar logo" src="~assets/gyoza.jpg">
|
<img alt="Quasar logo" src="~assets/gyoza.jpg" style="max-width:80vw">
|
||||||
<br/>
|
<br/>
|
||||||
<q-btn label="Go" color="primary" to="/dashboard"></q-btn>
|
<q-input label="Username" v-model="username" class="q-mt-md" autofocus @keydown.enter="login"></q-input>
|
||||||
|
<br/>
|
||||||
|
<q-input label="Password" type="password" v-model="password" @keydown.enter="login"></q-input>
|
||||||
|
<br/>
|
||||||
|
<q-btn label="Login" color="primary" @click="login" ></q-btn>
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
@ -13,6 +17,37 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'PageIndex'
|
name: 'PageIndex',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if (!window.crypto || !window.crypto.subtle) {
|
||||||
|
throw new Error('Web Crypto API is not supported in this browser. Use Chrome 79+.')
|
||||||
|
}
|
||||||
|
// use the helper boot file which exposes $helpers
|
||||||
|
let creds = this.$helpers.getStoredCredentials()
|
||||||
|
console.log('credits', creds)
|
||||||
|
if (creds) {
|
||||||
|
this.$router.push('/dashboard')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async login () {
|
||||||
|
let logged = await this.$helpers.verifyCredentials(this.username + ':' + this.password)
|
||||||
|
console.log('logged', logged)
|
||||||
|
if (logged) {
|
||||||
|
this.$router.push('/dashboard')
|
||||||
|
} else {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: 'Invalid username or password'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user