feat(api tokens): allow custom selection of expiry dates
This commit is contained in:
parent
021f92303d
commit
0bb85870db
@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
data-input
|
data-input
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
ref="root"
|
ref="root"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -20,39 +20,39 @@ type Options = flatpickr.Options.Options
|
|||||||
type DateOption = flatpickr.Options.DateOption
|
type DateOption = flatpickr.Options.DateOption
|
||||||
|
|
||||||
function camelToKebab(string: string) {
|
function camelToKebab(string: string) {
|
||||||
return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
function arrayify<T = unknown>(obj: T) {
|
function arrayify<T = unknown>(obj: T) {
|
||||||
return obj instanceof Array
|
return obj instanceof Array
|
||||||
? obj
|
? obj
|
||||||
: [obj]
|
: [obj]
|
||||||
}
|
}
|
||||||
|
|
||||||
function nullify<T = unknown>(value: T) {
|
function nullify<T = unknown>(value: T) {
|
||||||
return (value && (value as unknown[]).length)
|
return (value && (value as unknown[]).length)
|
||||||
? value
|
? value
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events to emit, copied from flatpickr source
|
// Events to emit, copied from flatpickr source
|
||||||
const includedEvents = [
|
const includedEvents = [
|
||||||
'onChange',
|
'onChange',
|
||||||
'onClose',
|
'onClose',
|
||||||
'onDestroy',
|
'onDestroy',
|
||||||
'onMonthChange',
|
'onMonthChange',
|
||||||
'onOpen',
|
'onOpen',
|
||||||
'onYearChange',
|
'onYearChange',
|
||||||
] as HookKey[]
|
] as HookKey[]
|
||||||
|
|
||||||
// Let's not emit these events by default
|
// Let's not emit these events by default
|
||||||
const excludedEvents = [
|
const excludedEvents = [
|
||||||
'onValueUpdate',
|
'onValueUpdate',
|
||||||
'onDayCreate',
|
'onDayCreate',
|
||||||
'onParseConfig',
|
'onParseConfig',
|
||||||
'onReady',
|
'onReady',
|
||||||
'onPreCalendarPosition',
|
'onPreCalendarPosition',
|
||||||
'onKeyDown',
|
'onKeyDown',
|
||||||
] as HookKey[]
|
] as HookKey[]
|
||||||
|
|
||||||
// Keep a copy of all events for later use
|
// Keep a copy of all events for later use
|
||||||
@ -100,11 +100,11 @@ const attrs = useAttrs()
|
|||||||
|
|
||||||
const root = ref<HTMLInputElement | null>(null)
|
const root = ref<HTMLInputElement | null>(null)
|
||||||
const fp = ref<flatpickr.Instance | null>(null)
|
const fp = ref<flatpickr.Instance | null>(null)
|
||||||
const safeConfig = ref<Options>({ ...props.config })
|
const safeConfig = ref<Options>({...props.config})
|
||||||
|
|
||||||
function prepareConfig() {
|
function prepareConfig() {
|
||||||
// Don't mutate original object on parent component
|
// Don't mutate original object on parent component
|
||||||
const newConfig: Options = { ...props.config }
|
const newConfig: Options = {...props.config}
|
||||||
|
|
||||||
props.events.forEach((hook) => {
|
props.events.forEach((hook) => {
|
||||||
// Respect global callbacks registered via setDefault() method
|
// Respect global callbacks registered via setDefault() method
|
||||||
@ -147,9 +147,9 @@ onMounted(() => {
|
|||||||
prepareConfig()
|
prepareConfig()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the HTML node where flatpickr to be attached
|
* Get the HTML node where flatpickr to be attached
|
||||||
* Bind on parent element if wrap is true
|
* Bind on parent element if wrap is true
|
||||||
*/
|
*/
|
||||||
const element = props.config.wrap
|
const element = props.config.wrap
|
||||||
? root.value.parentNode
|
? root.value.parentNode
|
||||||
: root.value
|
: root.value
|
||||||
@ -179,7 +179,7 @@ watch(config, () => {
|
|||||||
fp.value.set(name, safeConfig.value[name])
|
fp.value.set(name, safeConfig.value[name])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, {deep:true})
|
}, {deep: true})
|
||||||
|
|
||||||
const fpInput = computed(() => {
|
const fpInput = computed(() => {
|
||||||
if (!fp.value) return
|
if (!fp.value) return
|
||||||
@ -198,8 +198,8 @@ watchEffect(() => fpInput.value?.addEventListener('blur', onBlur))
|
|||||||
onBeforeUnmount(() => fpInput.value?.removeEventListener('blur', onBlur))
|
onBeforeUnmount(() => fpInput.value?.removeEventListener('blur', onBlur))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Watch for the disabled property and sets the value to the real input.
|
* Watch for the disabled property and sets the value to the real input.
|
||||||
*/
|
*/
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (disabled.value) {
|
if (disabled.value) {
|
||||||
fpInput.value?.setAttribute('disabled', '')
|
fpInput.value?.setAttribute('disabled', '')
|
||||||
|
@ -7,6 +7,10 @@ import BaseButton from '@/components/base/BaseButton.vue'
|
|||||||
import ApiTokenModel from '@/models/apiTokenModel'
|
import ApiTokenModel from '@/models/apiTokenModel'
|
||||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||||
import {MILLISECONDS_A_DAY} from '@/constants/date'
|
import {MILLISECONDS_A_DAY} from '@/constants/date'
|
||||||
|
import flatPickr from 'vue-flatpickr-component'
|
||||||
|
import 'flatpickr/dist/flatpickr.css'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
import {useAuthStore} from '@/stores/auth'
|
||||||
|
|
||||||
const service = new ApiTokenService()
|
const service = new ApiTokenService()
|
||||||
const tokens = ref([])
|
const tokens = ref([])
|
||||||
@ -15,10 +19,27 @@ const showCreateForm = ref(false)
|
|||||||
const availableRoutes = ref(null)
|
const availableRoutes = ref(null)
|
||||||
const newToken = ref(new ApiTokenModel())
|
const newToken = ref(new ApiTokenModel())
|
||||||
const newTokenExpiry = ref<string | number>(30)
|
const newTokenExpiry = ref<string | number>(30)
|
||||||
|
const newTokenExpiryCustom = ref(new Date())
|
||||||
const newTokenPermissions = ref({})
|
const newTokenPermissions = ref({})
|
||||||
const newTokenTitleValid = ref(true)
|
const newTokenTitleValid = ref(true)
|
||||||
const apiTokenTitle = ref()
|
const apiTokenTitle = ref()
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const flatPickerConfig = computed(() => ({
|
||||||
|
altFormat: t('date.altFormatLong'),
|
||||||
|
altInput: true,
|
||||||
|
dateFormat: 'Y-m-d H:i',
|
||||||
|
enableTime: true,
|
||||||
|
time_24hr: true,
|
||||||
|
locale: {
|
||||||
|
firstDayOfWeek: authStore.settings.weekStart,
|
||||||
|
},
|
||||||
|
minDate: now,
|
||||||
|
}))
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
tokens.value = await service.getAll()
|
tokens.value = await service.getAll()
|
||||||
availableRoutes.value = await service.getAvailableRoutes()
|
availableRoutes.value = await service.getAvailableRoutes()
|
||||||
@ -49,6 +70,8 @@ async function createToken() {
|
|||||||
if (!isNaN(expiry)) {
|
if (!isNaN(expiry)) {
|
||||||
// if it's a number, we assume it's the number of days in the future
|
// if it's a number, we assume it's the number of days in the future
|
||||||
newToken.value.expiresAt = new Date((new Date()) + expiry * MILLISECONDS_A_DAY)
|
newToken.value.expiresAt = new Date((new Date()) + expiry * MILLISECONDS_A_DAY)
|
||||||
|
} else {
|
||||||
|
newToken.value.expiresAt = new Date(newTokenExpiryCustom.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
newToken.value.permissions = {}
|
newToken.value.permissions = {}
|
||||||
@ -65,6 +88,7 @@ async function createToken() {
|
|||||||
const token = await service.create(newToken.value)
|
const token = await service.create(newToken.value)
|
||||||
newToken.value = new ApiTokenModel()
|
newToken.value = new ApiTokenModel()
|
||||||
newTokenExpiry.value = 30
|
newTokenExpiry.value = 30
|
||||||
|
newTokenExpiryCustom.value = new Date()
|
||||||
resetPermissions()
|
resetPermissions()
|
||||||
tokens.value.push(token)
|
tokens.value.push(token)
|
||||||
showCreateForm.value = false
|
showCreateForm.value = false
|
||||||
@ -139,13 +163,21 @@ async function createToken() {
|
|||||||
<label class="label" for="apiTokenExpiry">
|
<label class="label" for="apiTokenExpiry">
|
||||||
{{ $t('user.settings.apiTokens.attributes.expiresAt') }}
|
{{ $t('user.settings.apiTokens.attributes.expiresAt') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="control select">
|
<div class="is-flex">
|
||||||
<select class="select" v-model="newTokenExpiry" id="apiTokenExpiry">
|
<div class="control select">
|
||||||
<option value="30">{{ $t('user.settings.apiTokens.30d') }}</option>
|
<select class="select" v-model="newTokenExpiry" id="apiTokenExpiry">
|
||||||
<option value="60">{{ $t('user.settings.apiTokens.60d') }}</option>
|
<option value="30">{{ $t('user.settings.apiTokens.30d') }}</option>
|
||||||
<option value="90">{{ $t('user.settings.apiTokens.90d') }}</option>
|
<option value="60">{{ $t('user.settings.apiTokens.60d') }}</option>
|
||||||
<option value="custom">{{ $t('misc.custom') }}</option>
|
<option value="90">{{ $t('user.settings.apiTokens.90d') }}</option>
|
||||||
</select>
|
<option value="custom">{{ $t('misc.custom') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<flat-pickr
|
||||||
|
v-if="newTokenExpiry === 'custom'"
|
||||||
|
class="ml-2"
|
||||||
|
:config="flatPickerConfig"
|
||||||
|
v-model="newTokenExpiryCustom"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user