feat(api tokens): add token creation form
This commit is contained in:
parent
a20eef2453
commit
e47ad021a3
@ -142,9 +142,14 @@
|
|||||||
},
|
},
|
||||||
"apiTokens": {
|
"apiTokens": {
|
||||||
"title": "API Tokens",
|
"title": "API Tokens",
|
||||||
"general": "API tokens allow you to use Vikunja's api without user credentials.",
|
"general": "API tokens allow you to use Vikunja's API without user credentials.",
|
||||||
"apiDocs": "Check out the api docs",
|
"apiDocs": "Check out the api docs",
|
||||||
"createToken": "Create a token",
|
"createAToken": "Create a token",
|
||||||
|
"createToken": "Create token",
|
||||||
|
"30d": "30 Days",
|
||||||
|
"60d": "60 Days",
|
||||||
|
"90d": "90 Days",
|
||||||
|
"permissionExplanation": "Permissions allow you to scope what an api token is allowed to do.",
|
||||||
"attributes": {
|
"attributes": {
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
"expiresAt": "Expires at",
|
"expiresAt": "Expires at",
|
||||||
|
@ -10,4 +10,4 @@ export interface IApiToken extends IAbstract {
|
|||||||
permissions: IApiPermission
|
permissions: IApiPermission
|
||||||
expiresAt: Date
|
expiresAt: Date
|
||||||
created: Date
|
created: Date
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export default class ApiTokenModel extends AbstractModel<IApiToken> {
|
|||||||
expiresAt: Date = null
|
expiresAt: Date = null
|
||||||
created: Date = null
|
created: Date = null
|
||||||
|
|
||||||
constructor(data: Partial<IApiToken>) {
|
constructor(data: Partial<IApiToken> = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.assignData(data)
|
this.assignData(data)
|
||||||
|
@ -22,4 +22,15 @@ export default class ApiTokenService extends AbstractService<IApiToken> {
|
|||||||
modelFactory(data: Partial<IApiToken>) {
|
modelFactory(data: Partial<IApiToken>) {
|
||||||
return new ApiTokenModel(data)
|
return new ApiTokenModel(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAvailableRoutes() {
|
||||||
|
const cancel = this.setLoading()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.http.get('/routes')
|
||||||
|
return response.data
|
||||||
|
} finally {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,34 +1,78 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ApiTokenService from '@/services/apiToken'
|
import ApiTokenService from '@/services/apiToken'
|
||||||
import {computed, onMounted, ref} from 'vue'
|
import {computed, onMounted, ref} from 'vue'
|
||||||
import { formatDateShort } from '@/helpers/time/formatDate'
|
import {formatDateShort} from '@/helpers/time/formatDate'
|
||||||
import XButton from '@/components/input/button.vue'
|
import XButton from '@/components/input/button.vue'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
import ApiTokenModel from '@/models/apiTokenModel'
|
||||||
|
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||||
|
import {MILLISECONDS_A_DAY} from '@/constants/date'
|
||||||
|
|
||||||
const service = new ApiTokenService()
|
const service = new ApiTokenService()
|
||||||
const tokens = ref([])
|
const tokens = ref([])
|
||||||
|
|
||||||
const apiDocsUrl = window.API_URL + '/docs'
|
const apiDocsUrl = window.API_URL + '/docs'
|
||||||
|
const showCreateForm = ref(false)
|
||||||
|
const availableRoutes = ref(null)
|
||||||
|
const newToken = ref(new ApiTokenModel())
|
||||||
|
const newTokenExpiry = ref<string | number>(30)
|
||||||
|
const newTokenPermissions = ref({})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
tokens.value = await service.getAll()
|
tokens.value = await service.getAll()
|
||||||
|
availableRoutes.value = await service.getAvailableRoutes()
|
||||||
|
resetPermissions()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function resetPermissions() {
|
||||||
|
newTokenPermissions.value = {}
|
||||||
|
Object.entries(availableRoutes.value).forEach(entry => {
|
||||||
|
const [group, routes] = entry
|
||||||
|
newTokenPermissions.value[group] = {}
|
||||||
|
Object.keys(routes).forEach(r => {
|
||||||
|
newTokenPermissions.value[group][r] = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function deleteToken() {
|
function deleteToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createToken() {
|
async function createToken() {
|
||||||
|
const expiry = Number(newTokenExpiry.value)
|
||||||
|
if(!isNaN(expiry)) {
|
||||||
|
// 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.permissions = {}
|
||||||
|
Object.entries(newTokenPermissions.value).forEach(([key, ps]) => {
|
||||||
|
const all = Object.entries(ps)
|
||||||
|
.filter(([_, v]) => v)
|
||||||
|
.map(p => p[0])
|
||||||
|
console.log({all})
|
||||||
|
if (all.length > 0) {
|
||||||
|
newToken.value.permissions[key] = all
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const token = await service.create(newToken.value)
|
||||||
|
newToken.value = new ApiTokenModel()
|
||||||
|
newTokenExpiry.value = 30
|
||||||
|
resetPermissions()
|
||||||
|
tokens.value.push(token)
|
||||||
|
showCreateForm.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<card :title="$t('user.settings.apiTokens.title')">
|
<card :title="$t('user.settings.apiTokens.title')">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{{ $t('user.settings.apiTokens.general') }}
|
{{ $t('user.settings.apiTokens.general') }}
|
||||||
<BaseButton :href="apiDocsUrl">{{ $t('user.settings.apiTokens.apiDocs') }}</BaseButton>.
|
<BaseButton :href="apiDocsUrl">{{ $t('user.settings.apiTokens.apiDocs') }}</BaseButton>
|
||||||
|
.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<table class="table" v-if="tokens.length > 0">
|
<table class="table" v-if="tokens.length > 0">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ $t('misc.id') }}</th>
|
<th>{{ $t('misc.id') }}</th>
|
||||||
@ -57,13 +101,70 @@ function createToken() {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<x-button icon="plus" class="mb-4" @click="createToken" :loading="service.loading">
|
<form
|
||||||
{{ $t('user.settings.apiTokens.createToken') }}
|
v-if="showCreateForm"
|
||||||
|
@submit.prevent="createToken"
|
||||||
|
>
|
||||||
|
<!-- Title -->
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="apiTokenTitle">{{ $t('user.settings.apiTokens.attributes.title') }}</label>
|
||||||
|
<div class="control">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
id="apiTokenTitle"
|
||||||
|
type="text"
|
||||||
|
v-focus
|
||||||
|
v-model="newToken.title"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Expiry -->
|
||||||
|
<div class="field">
|
||||||
|
<label class="label" for="apiTokenTitle">{{
|
||||||
|
$t('user.settings.apiTokens.attributes.expiresAt')
|
||||||
|
}}</label>
|
||||||
|
<div class="control select">
|
||||||
|
<select class="select" v-model="newTokenExpiry">
|
||||||
|
<option value="30">{{ $t('user.settings.apiTokens.30d') }}</option>
|
||||||
|
<option value="60">{{ $t('user.settings.apiTokens.60d') }}</option>
|
||||||
|
<option value="90">{{ $t('user.settings.apiTokens.90d') }}</option>
|
||||||
|
<option value="custom">{{ $t('misc.custom') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Permissions -->
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">{{ $t('user.settings.apiTokens.attributes.permissions') }}</label>
|
||||||
|
<p>{{ $t('user.settings.apiTokens.permissionExplanation') }}</p>
|
||||||
|
<div v-for="(routes, group) in availableRoutes" class="mb-2" :key="group">
|
||||||
|
<strong>{{ group }}</strong><br/>
|
||||||
|
<fancycheckbox
|
||||||
|
v-for="(paths, route) in routes"
|
||||||
|
:key="group+'-'+route"
|
||||||
|
class="mr-2"
|
||||||
|
v-model="newTokenPermissions[group][route]"
|
||||||
|
>
|
||||||
|
{{ route }}
|
||||||
|
</fancycheckbox>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<x-button :loading="service.loading" @click="createToken">
|
||||||
|
{{ $t('user.settings.apiTokens.createToken') }}
|
||||||
|
</x-button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<x-button
|
||||||
|
v-else
|
||||||
|
icon="plus"
|
||||||
|
class="mb-4"
|
||||||
|
@click="() => showCreateForm = true"
|
||||||
|
:loading="service.loading"
|
||||||
|
>
|
||||||
|
{{ $t('user.settings.apiTokens.createAToken') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
</card>
|
</card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
Loading…
x
Reference in New Issue
Block a user