Many stuphs

This commit is contained in:
2026-03-15 13:40:41 +01:00
parent 76a8dc3995
commit 0d4688da4d
6 changed files with 180 additions and 6 deletions

View File

@ -19,7 +19,8 @@ module.exports = configure(function (/* ctx */) {
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: [
'pocketbase'
'pocketbase',
'helper'
],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css

View File

@ -6,6 +6,37 @@
import { defineComponent } from 'vue'
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>

106
src/boot/helper.js Normal file
View 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 }

View File

@ -191,6 +191,7 @@ export default {
})
.onOk(() => {
this.$q.localStorage.remove('username')
this.$q.localStorage.remove('credentials')
this.$router.push('/')
})
},

View File

@ -1,9 +1,13 @@
<template>
<q-page class="flex flex-center flex-">
<q-page class="flex flex-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/>
<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>
</q-page>
</template>
@ -13,6 +17,37 @@
<script>
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>