feat(api tokens): add basic api token overview
This commit is contained in:
parent
7b57b10804
commit
a20eef2453
@ -139,6 +139,17 @@
|
|||||||
"system": "System",
|
"system": "System",
|
||||||
"dark": "Dark"
|
"dark": "Dark"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"apiTokens": {
|
||||||
|
"title": "API Tokens",
|
||||||
|
"general": "API tokens allow you to use Vikunja's api without user credentials.",
|
||||||
|
"apiDocs": "Check out the api docs",
|
||||||
|
"createToken": "Create a token",
|
||||||
|
"attributes": {
|
||||||
|
"title": "Title",
|
||||||
|
"expiresAt": "Expires at",
|
||||||
|
"permissions": "Permissions"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletion": {
|
"deletion": {
|
||||||
|
13
src/modelTypes/IApiToken.ts
Normal file
13
src/modelTypes/IApiToken.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type {IAbstract} from '@/modelTypes/IAbstract'
|
||||||
|
|
||||||
|
export interface IApiPermission {
|
||||||
|
[key: string]: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IApiToken extends IAbstract {
|
||||||
|
id: number
|
||||||
|
token: string
|
||||||
|
permissions: IApiPermission
|
||||||
|
expiresAt: Date
|
||||||
|
created: Date
|
||||||
|
}
|
20
src/models/apiTokenModel.ts
Normal file
20
src/models/apiTokenModel.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import AbstractModel from '@/models/abstractModel'
|
||||||
|
import type {IApiToken} from '@/modelTypes/IApiToken'
|
||||||
|
|
||||||
|
export default class ApiTokenModel extends AbstractModel<IApiToken> {
|
||||||
|
id = 0
|
||||||
|
token = ''
|
||||||
|
permissions = null
|
||||||
|
expiresAt: Date = null
|
||||||
|
created: Date = null
|
||||||
|
|
||||||
|
constructor(data: Partial<IApiToken>) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.assignData(data)
|
||||||
|
|
||||||
|
this.expiresAt = new Date(this.expiresAt)
|
||||||
|
this.created = new Date(this.created)
|
||||||
|
this.updated = new Date(this.updated)
|
||||||
|
}
|
||||||
|
}
|
@ -65,6 +65,7 @@ const UserSettingsEmailUpdateComponent = () => import('@/views/user/settings/Ema
|
|||||||
const UserSettingsGeneralComponent = () => import('@/views/user/settings/General.vue')
|
const UserSettingsGeneralComponent = () => import('@/views/user/settings/General.vue')
|
||||||
const UserSettingsPasswordUpdateComponent = () => import('@/views/user/settings/PasswordUpdate.vue')
|
const UserSettingsPasswordUpdateComponent = () => import('@/views/user/settings/PasswordUpdate.vue')
|
||||||
const UserSettingsTOTPComponent = () => import('@/views/user/settings/TOTP.vue')
|
const UserSettingsTOTPComponent = () => import('@/views/user/settings/TOTP.vue')
|
||||||
|
const UserSettingsApiTokensComponent = () => import('@/views/user/settings/ApiTokens.vue')
|
||||||
|
|
||||||
// Project Handling
|
// Project Handling
|
||||||
const NewProjectComponent = () => import('@/views/project/NewProject.vue')
|
const NewProjectComponent = () => import('@/views/project/NewProject.vue')
|
||||||
@ -183,6 +184,11 @@ const router = createRouter({
|
|||||||
name: 'user.settings.totp',
|
name: 'user.settings.totp',
|
||||||
component: UserSettingsTOTPComponent,
|
component: UserSettingsTOTPComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/user/settings/api-tokens',
|
||||||
|
name: 'user.settings.apiTokens',
|
||||||
|
component: UserSettingsApiTokensComponent,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
25
src/services/apiToken.ts
Normal file
25
src/services/apiToken.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import AbstractService from '@/services/abstractService'
|
||||||
|
import type {IApiToken} from '@/modelTypes/IApiToken'
|
||||||
|
import ApiTokenModel from '@/models/apiTokenModel'
|
||||||
|
|
||||||
|
export default class ApiTokenService extends AbstractService<IApiToken> {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
create: '/tokens',
|
||||||
|
getAll: '/tokens',
|
||||||
|
delete: '/tokens/{id}',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
processModel(model: IApiToken) {
|
||||||
|
return {
|
||||||
|
...model,
|
||||||
|
expiresAt: new Date(model.expiresAt).toISOString(),
|
||||||
|
created: new Date(model.created).toISOString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modelFactory(data: Partial<IApiToken>) {
|
||||||
|
return new ApiTokenModel(data)
|
||||||
|
}
|
||||||
|
}
|
@ -75,6 +75,10 @@ const navigationItems = computed(() => {
|
|||||||
routeName: 'user.settings.caldav',
|
routeName: 'user.settings.caldav',
|
||||||
condition: caldavEnabled.value,
|
condition: caldavEnabled.value,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('user.settings.apiTokens.title'),
|
||||||
|
routeName: 'user.settings.apiTokens',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('user.deletion.title'),
|
title: t('user.deletion.title'),
|
||||||
routeName: 'user.settings.deletion',
|
routeName: 'user.settings.deletion',
|
||||||
|
69
src/views/user/settings/ApiTokens.vue
Normal file
69
src/views/user/settings/ApiTokens.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import ApiTokenService from '@/services/apiToken'
|
||||||
|
import {computed, onMounted, ref} from 'vue'
|
||||||
|
import { formatDateShort } from '@/helpers/time/formatDate'
|
||||||
|
import XButton from '@/components/input/button.vue'
|
||||||
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
|
||||||
|
const service = new ApiTokenService()
|
||||||
|
const tokens = ref([])
|
||||||
|
|
||||||
|
const apiDocsUrl = window.API_URL + '/docs'
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
tokens.value = await service.getAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
function deleteToken() {
|
||||||
|
}
|
||||||
|
|
||||||
|
function createToken() {
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<card :title="$t('user.settings.apiTokens.title')">
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{ $t('user.settings.apiTokens.general') }}
|
||||||
|
<BaseButton :href="apiDocsUrl">{{ $t('user.settings.apiTokens.apiDocs') }}</BaseButton>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<table class="table" v-if="tokens.length > 0">
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('misc.id') }}</th>
|
||||||
|
<th>{{ $t('user.settings.apiTokens.attributes.title') }}</th>
|
||||||
|
<th>{{ $t('user.settings.apiTokens.attributes.permissions') }}</th>
|
||||||
|
<th>{{ $t('user.settings.apiTokens.attributes.expiresAt') }}</th>
|
||||||
|
<th>{{ $t('misc.created') }}</th>
|
||||||
|
<th class="has-text-right">{{ $t('misc.actions') }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="tk in tokens" :key="tk.id">
|
||||||
|
<td>{{ tk.id }}</td>
|
||||||
|
<td>{{ tk.title }}</td>
|
||||||
|
<td>
|
||||||
|
<template v-for="(v, p) in tk.permissions">
|
||||||
|
<strong>{{ p }}:</strong>
|
||||||
|
{{ v.join(', ') }}
|
||||||
|
<br/>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
<td>{{ formatDateShort(tk.expiresAt) }}</td>
|
||||||
|
<td>{{ formatDateShort(tk.created) }}</td>
|
||||||
|
<td class="has-text-right">
|
||||||
|
<x-button variant="secondary" @click="deleteToken(tk)">
|
||||||
|
{{ $t('misc.delete') }}
|
||||||
|
</x-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<x-button icon="plus" class="mb-4" @click="createToken" :loading="service.loading">
|
||||||
|
{{ $t('user.settings.apiTokens.createToken') }}
|
||||||
|
</x-button>
|
||||||
|
</card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user