2024-08-31 02:59:05 +00:00
< template >
< div class = "page" >
2024-08-31 03:04:14 +00:00
< div class = "manager" >
< div class = "keys-header" >
< h2 class = "heading" > API keys < / h2 >
< div class = "keys-actions" >
< Icon
v - tooltip = "'Flush all keys'"
icon = "stack-cancel"
@ click = "flushKeys"
/ >
< button
class = "button"
@ click = "createKey"
> New key < / button >
< / div >
2024-08-31 02:59:05 +00:00
< / div >
2024-08-31 03:04:14 +00:00
< div
v - if = "newKey"
class = "newkey"
2024-08-31 02:59:05 +00:00
>
2024-08-31 20:15:39 +00:00
< p class = "key-info" > Successfully generated key with identifier < strong class = "newkey-identifier ellipsis" > { { newKey . identifier } } < / strong > : < / p >
< input
: value = "newKey.key"
class = "input ellipsis"
@ click = "copyKey"
>
2024-08-31 03:04:14 +00:00
< p class = "key-info" > Please store this key securely , you will < strong > not < / strong > be able to retrieve it later . If you lose it , you must generate a new key . < / p >
< / div >
2024-08-31 02:59:05 +00:00
2024-08-31 03:04:14 +00:00
< ul
v - if = "keys.length > 0"
class = "keys nolist"
>
< li
v - for = "key in keys"
: key = "`key-${key.id}`"
class = "key"
>
< div class = "key-row key-header" >
< strong class = "key-value key-identifier ellipsis" > { { key . identifier } } < / strong >
2024-08-31 02:59:05 +00:00
2024-08-31 03:04:14 +00:00
< span class = "key-actions" >
< Icon
icon = "bin"
@ click = "removeKey(key)"
/ >
< / span >
< / div >
2024-08-31 02:59:05 +00:00
2024-08-31 03:04:14 +00:00
< div class = "key-row key-details" >
< span class = "key-value key-created" >
< Icon icon = "plus-circle" / >
2024-08-31 02:59:05 +00:00
< time
2024-08-31 22:08:05 +00:00
v - tooltip = "`Created ${format(key.createdAt, 'yyyy-MM-dd hh:mm:ss')}`"
2024-08-31 03:04:14 +00:00
: datetime = "key.createdAt.toISOString()"
2024-08-31 20:15:39 +00:00
> { { formatDistanceStrict ( key . createdAt , now ) } } ago < / time >
2024-08-31 03:04:14 +00:00
< / span >
< span class = "key-value key-used" >
< Icon icon = "history" / >
< template v-if ="key.lastUsedAt" >
< time
2024-08-31 22:08:05 +00:00
v - tooltip = "`Last used ${format(key.lastUsedAt, 'yyyy-MM-dd hh:mm:ss')} from IP ${key.lastUsedIp}`"
2024-08-31 03:04:14 +00:00
: datetime = "key.lastUsedAt.toISOString()"
2024-08-31 20:15:39 +00:00
> { { formatDistanceStrict ( key . lastUsedAt , now ) } } ago < / time >
2024-08-31 03:04:14 +00:00
< / template >
< template v-else > Never < / template >
< / span >
< / div >
< / li >
< / ul >
< div
v - if = "keys.length > 0"
class = "info"
>
< h3 class = "info-heading" > HTTP headers < / h3 >
2024-08-31 02:59:05 +00:00
2024-08-31 03:04:14 +00:00
< code class = "headers" >
2024-08-31 03:08:50 +00:00
API - User : { { user . id } } < br >
API - Key : YourSecurelyStoredApiKey12345678
2024-08-31 03:04:14 +00:00
< / code >
< / div >
2024-08-31 02:59:05 +00:00
< / div >
< / div >
< / template >
< script setup >
import { ref , inject } from 'vue' ;
2024-08-31 20:15:39 +00:00
import { format , formatDistanceStrict } from 'date-fns' ;
2024-08-31 02:59:05 +00:00
import { get , post , del } from '#/src/api.js' ;
import events from '#/src/events.js' ;
const pageContext = inject ( 'pageContext' ) ;
2024-08-31 20:15:39 +00:00
const now = pageContext . meta . now ;
2024-08-31 02:59:05 +00:00
const user = pageContext . user ;
const keys = ref ( pageContext . pageProps . keys ) ;
const newKey = ref ( null ) ;
async function createKey ( ) {
const key = await post ( '/keys' , null , {
appendErrorMessage : true ,
} ) ;
newKey . value = key ;
keys . value = await get ( '/me/keys' ) ;
}
async function removeKey ( key ) {
2024-08-31 03:13:00 +00:00
if ( confirm ( ` Are you sure you want to remove API key ' ${ key . identifier } '? It can not be restored. ` ) ) { // eslint-disable-line no-restricted-globals, no-alert
2024-08-31 02:59:05 +00:00
newKey . value = null ;
await del ( ` /me/keys/ ${ key . identifier } ` ) ;
keys . value = await get ( '/me/keys' ) ;
}
}
async function flushKeys ( ) {
if ( confirm ( 'Are you sure you want to remove ALL your API keys? They can not be restored.' ) ) { // eslint-disable-line no-restricted-globals, no-alert
newKey . value = null ;
await del ( '/me/keys' ) ;
keys . value = [ ] ;
}
}
function copyKey ( event ) {
event . target . select ( ) ;
navigator . clipboard . writeText ( newKey . value . key ) ;
events . emit ( 'feedback' , {
type : 'success' ,
message : 'Key copied to clipboard' ,
} ) ;
}
< / script >
< style scoped >
. page {
2024-08-31 03:04:14 +00:00
display : flex ;
2024-08-31 02:59:05 +00:00
flex - grow : 1 ;
2024-08-31 03:04:14 +00:00
justify - content : center ;
}
. manager {
width : 1200 px ;
max - width : 100 % ;
box - sizing : border - box ;
2024-08-31 02:59:05 +00:00
padding : 1 rem ;
}
. keys - header {
display : flex ;
justify - content : space - between ;
align - items : center ;
margin - bottom : .5 rem ;
}
. keys - actions {
display : flex ;
gap : 1 rem ;
align - items : center ;
. icon {
padding : .5 rem 1 rem ;
}
}
. keys - actions ,
. key - actions {
. icon {
height : 100 % ;
fill : var ( -- glass ) ;
& : hover {
fill : var ( -- error ) ;
cursor : pointer ;
}
}
}
. keys {
display : grid ;
grid - template - columns : repeat ( auto - fill , minmax ( 25 rem , 1 fr ) ) ;
gap : .5 rem ;
margin - bottom : 2 rem ;
}
. key {
background : var ( -- background ) ;
box - shadow : 0 0 3 px var ( -- shadow - weak - 30 ) ;
font - size : .9 rem ;
}
. key - row {
display : flex ;
justify - content : space - between ;
overflow : hidden ;
}
. key - value {
display : flex ;
align - items : center ;
gap : .25 rem ;
box - sizing : border - box ;
. icon {
width : .9 rem ;
height : .9 rem ;
fill : var ( -- glass - strong - 10 ) ;
}
}
. key - header . key - value {
padding : .5 rem .5 rem .25 rem .5 rem ;
}
. key - details . key - value {
padding : .25 rem .5 rem .5 rem .5 rem ;
}
. key - identifier {
display : inline - block ;
width : 0 ;
flex - grow : 1 ;
}
. key - actions . icon {
padding : 0 .5 rem .5 rem .5 rem ;
}
. newkey {
2024-08-31 20:15:39 +00:00
max - width : 100 % ;
2024-08-31 02:59:05 +00:00
display : inline - block ;
2024-08-31 20:15:39 +00:00
box - sizing : border - box ;
padding : .5 rem .75 rem ;
2024-08-31 02:59:05 +00:00
margin - bottom : 1 rem ;
background : var ( -- enabled - background ) ;
border : solid 1 px var ( -- success ) ;
border - radius : .25 rem ;
line - height : 1.5 ;
. input {
width : 24 rem ;
2024-08-31 20:15:39 +00:00
max - width : 100 % ;
padding : .25 rem .5 rem ;
margin - bottom : .5 rem ;
2024-08-31 02:59:05 +00:00
font - weight : bold ;
}
}
2024-08-31 20:15:39 +00:00
. newkey - identifier {
white - space : nowrap ;
}
2024-08-31 02:59:05 +00:00
. key - info {
2024-08-31 20:15:39 +00:00
margin : 0 ;
& : not ( : last - child ) {
margin - bottom : .25 rem ;
}
2024-08-31 02:59:05 +00:00
}
. headers {
display : block ;
max - width : 100 % ;
padding : .5 rem 0 ;
white - space : nowrap ;
overflow : auto ;
}
. info - heading {
margin : 0 ;
}
@ media ( -- small - 20 ) {
. keys {
grid - template - columns : 1 fr ;
}
}
< / style >