Compare commits
1 Commits
main
...
feat/pat-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79a6e8e1c6 |
@@ -7,6 +7,10 @@ export {}
|
|||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
AccessTokensGenerate: typeof import('./components/accessTokens/Generate.vue')['default']
|
||||||
|
AccessTokensGenerateModal: typeof import('./components/accessTokens/GenerateModal.vue')['default']
|
||||||
|
AccessTokensList: typeof import('./components/accessTokens/List.vue')['default']
|
||||||
|
AccessTokensOverview: typeof import('./components/accessTokens/Overview.vue')['default']
|
||||||
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
|
||||||
AppBanner: typeof import('./components/app/Banner.vue')['default']
|
AppBanner: typeof import('./components/app/Banner.vue')['default']
|
||||||
AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default']
|
AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default']
|
||||||
@@ -148,7 +152,7 @@ declare module 'vue' {
|
|||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
||||||
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
|
||||||
IconLucideBrush: (typeof import("~icons/lucide/brush"))["default"]
|
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
||||||
@@ -158,9 +162,10 @@ declare module 'vue' {
|
|||||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
||||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
||||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
||||||
IconLucideRss: (typeof import("~icons/lucide/rss"))["default"]
|
IconLucideRss: typeof import('~icons/lucide/rss')['default']
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
IconLucideSearch: typeof import('~icons/lucide/search')['default']
|
||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
|
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
||||||
IconLucideX: typeof import('~icons/lucide/x')['default']
|
IconLucideX: typeof import('~icons/lucide/x')['default']
|
||||||
ImportExportBase: typeof import('./components/importExport/Base.vue')['default']
|
ImportExportBase: typeof import('./components/importExport/Base.vue')['default']
|
||||||
ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default']
|
ImportExportImportExportList: typeof import('./components/importExport/ImportExportList.vue')['default']
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<HoppSmartModal dialog title="New Personal Access Token" @close="hideModal">
|
||||||
|
<template #body>
|
||||||
|
<template v-if="accessToken">
|
||||||
|
<p class="text-amber-500 mb-4 border rounded-md border-amber-600 p-4">
|
||||||
|
Make sure to copy your personal access token now. You won’t be able to
|
||||||
|
see it again!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="rounded-md bg-primaryLight p-4 mt-4 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<div class="text-secondaryDark">{{ accessToken }}</div>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
:icon="copyIcon"
|
||||||
|
@click="copyAccessToken"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-else class="space-y-4">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="text-secondaryDark font-semibold">Label</div>
|
||||||
|
<HoppSmartInput
|
||||||
|
v-model="accessTokenLabel"
|
||||||
|
placeholder=" "
|
||||||
|
class="floating-input"
|
||||||
|
/>
|
||||||
|
<div class="text-secondaryLight">What's this token for?</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="expiration" class="text-secondaryDark font-semibold"
|
||||||
|
>Expiration</label
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 items-center gap-x-2">
|
||||||
|
<tippy
|
||||||
|
interactive
|
||||||
|
trigger="click"
|
||||||
|
theme="popover"
|
||||||
|
:on-shown="tippyActions?.focus()"
|
||||||
|
>
|
||||||
|
<HoppSmartSelectWrapper>
|
||||||
|
<input
|
||||||
|
id="expiration"
|
||||||
|
:value="expiration"
|
||||||
|
readonly
|
||||||
|
class="flex flex-1 cursor-pointer bg-transparent px-4 py-2 rounded border border-divider"
|
||||||
|
/>
|
||||||
|
</HoppSmartSelectWrapper>
|
||||||
|
|
||||||
|
<template #content="{ hide }">
|
||||||
|
<div
|
||||||
|
ref="tippyActions"
|
||||||
|
tabindex="0"
|
||||||
|
role="menu"
|
||||||
|
class="flex flex-col focus:outline-none"
|
||||||
|
@keyup.escape="hide"
|
||||||
|
>
|
||||||
|
<HoppSmartItem
|
||||||
|
v-for="expirationOption in Object.keys(expirationOptions)"
|
||||||
|
:key="expirationOption"
|
||||||
|
:label="expirationOption"
|
||||||
|
:icon="
|
||||||
|
expirationOption === expiration
|
||||||
|
? IconCircleDot
|
||||||
|
: IconCircle
|
||||||
|
"
|
||||||
|
:active="expirationOption === expiration"
|
||||||
|
:aria-selected="expirationOption === expiration"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
expiration = expirationOption
|
||||||
|
hide()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</tippy>
|
||||||
|
|
||||||
|
<span class="text-secondaryLight">{{ expirationDateText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="text-secondaryDark font-semibold">Scope</div>
|
||||||
|
|
||||||
|
<p class="text-secondaryLight">
|
||||||
|
Read-only access to workspace data.<br />
|
||||||
|
Personal Access Tokens can't access your personal workspace.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<HoppButtonSecondary
|
||||||
|
v-if="accessToken"
|
||||||
|
:label="t('action.close')"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="hideModal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-else class="flex items-center gap-x-2">
|
||||||
|
<HoppButtonPrimary
|
||||||
|
:loading="tokenGenerateActionLoading"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
label="Generate Token"
|
||||||
|
@click="generateAccessToken"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HoppButtonSecondary
|
||||||
|
:label="t('action.cancel')"
|
||||||
|
outline
|
||||||
|
filled
|
||||||
|
@click="hideModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</HoppSmartModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { useToast } from "@composables/toast"
|
||||||
|
import { refAutoReset } from "@vueuse/core"
|
||||||
|
import { computed, ref } from "vue"
|
||||||
|
import { TippyComponent } from "vue-tippy"
|
||||||
|
import { copyToClipboard } from "~/helpers/utils/clipboard"
|
||||||
|
import { shortDateTime } from "~/helpers/utils/date"
|
||||||
|
|
||||||
|
import IconCheck from "~icons/lucide/check"
|
||||||
|
import IconCircle from "~icons/lucide/circle"
|
||||||
|
import IconCircleDot from "~icons/lucide/circle-dot"
|
||||||
|
import IconCopy from "~icons/lucide/copy"
|
||||||
|
|
||||||
|
const t = useI18n()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
tokenGenerateActionLoading: boolean
|
||||||
|
accessToken: string | null
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "hide-modal"): void
|
||||||
|
(
|
||||||
|
e: "generate-access-token",
|
||||||
|
{ label, expiryInDays }: { label: string; expiryInDays: number | null }
|
||||||
|
): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Template refs
|
||||||
|
const tippyActions = ref<TippyComponent[] | null>(null)
|
||||||
|
|
||||||
|
const copyIcon = refAutoReset<typeof IconCopy | typeof IconCheck>(
|
||||||
|
IconCopy,
|
||||||
|
1000
|
||||||
|
)
|
||||||
|
|
||||||
|
const accessTokenLabel = ref<string>("")
|
||||||
|
const expiration = ref<string>("30 days")
|
||||||
|
|
||||||
|
const expirationOptions: Record<string, number | null> = {
|
||||||
|
"7 days": 7,
|
||||||
|
"30 days": 30,
|
||||||
|
"60 days": 60,
|
||||||
|
"90 days": 90,
|
||||||
|
"No expiration": null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const expirationDateText = computed(() => {
|
||||||
|
const chosenExpiryInDays = expirationOptions[expiration.value]
|
||||||
|
|
||||||
|
if (chosenExpiryInDays === null) {
|
||||||
|
return "This token will never expire!"
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDate = new Date()
|
||||||
|
currentDate.setDate(currentDate.getDate() + chosenExpiryInDays)
|
||||||
|
|
||||||
|
const expirationDate = shortDateTime(currentDate, false)
|
||||||
|
return `This token will expire on ${expirationDate}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const copyAccessToken = () => {
|
||||||
|
if (!props.accessToken) {
|
||||||
|
toast.error("error.something_went_wrong")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToClipboard(props.accessToken)
|
||||||
|
copyIcon.value = IconCheck
|
||||||
|
|
||||||
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateAccessToken = async () => {
|
||||||
|
if (!accessTokenLabel.value) {
|
||||||
|
toast.error("Please provide a label for the token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emit("generate-access-token", {
|
||||||
|
label: accessTokenLabel.value,
|
||||||
|
expiryInDays: expirationOptions[expiration.value],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideModal = () => emit("hide-modal")
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div class="max-w-2xl space-y-4">
|
||||||
|
<div
|
||||||
|
v-for="{ id, label, lastUsedOn, expiresOn } in accessTokens"
|
||||||
|
:key="id"
|
||||||
|
class="flex items-center justify-between rounded border border-divider p-4"
|
||||||
|
>
|
||||||
|
<span class="font-semibold text-secondaryDark text-sm">
|
||||||
|
{{ label }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-x-4">
|
||||||
|
<div class="text-secondaryLight space-y-1">
|
||||||
|
<div class="space-x-1">
|
||||||
|
<span class="font-semibold">Last used on:</span>
|
||||||
|
<span>
|
||||||
|
{{ shortDateTime(lastUsedOn, false) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-x-1">
|
||||||
|
<span class="font-semibold">Expires on:</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
expiresOn ? shortDateTime(expiresOn, false) : "No expiration"
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HoppButtonSecondary
|
||||||
|
label="Delete"
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
@click="
|
||||||
|
emit('delete-access-token', {
|
||||||
|
tokenId: id,
|
||||||
|
tokenLabel: label,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HoppSmartIntersection
|
||||||
|
v-if="hasMoreTokens"
|
||||||
|
@intersecting="emit('fetch-more-tokens')"
|
||||||
|
>
|
||||||
|
<div v-if="tokensListLoading" class="flex flex-col items-center py-3">
|
||||||
|
<HoppSmartSpinner />
|
||||||
|
</div>
|
||||||
|
</HoppSmartIntersection>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { shortDateTime } from "~/helpers/utils/date"
|
||||||
|
import { AccessToken } from "~/pages/profile.vue"
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
accessTokens: AccessToken[]
|
||||||
|
hasMoreTokens: boolean
|
||||||
|
tokensListLoading: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "fetch-more-tokens"): void
|
||||||
|
(
|
||||||
|
e: "delete-access-token",
|
||||||
|
{ tokenId, tokenLabel }: { tokenId: string; tokenLabel: string }
|
||||||
|
): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="p-4 space-y-4">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<h4 class="font-semibold text-secondaryDark">Personal Access Tokens</h4>
|
||||||
|
|
||||||
|
<p class="text-secondaryLight">
|
||||||
|
Personal access tokens currently helps you connect the CLI to your
|
||||||
|
Hoppscotch account
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HoppButtonSecondary
|
||||||
|
filled
|
||||||
|
outline
|
||||||
|
label="Generate new token"
|
||||||
|
@click="emit('show-access-tokens-generate-modal')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "show-access-tokens-generate-modal"): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
<div
|
<div
|
||||||
v-for="(member, index) in membersList"
|
v-for="(member, index) in membersList"
|
||||||
:key="`member-${index}`"
|
:key="`member-${index}`"
|
||||||
class="flex divide-x divide-dividerLight"
|
class="flex"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="flex flex-1 bg-transparent px-4 py-2"
|
class="flex flex-1 bg-transparent px-4 py-2"
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
export function shortDateTime(date: string | number | Date) {
|
export function shortDateTime(
|
||||||
|
date: string | number | Date,
|
||||||
|
includeTime: boolean = true
|
||||||
|
) {
|
||||||
return new Date(date).toLocaleString("en-US", {
|
return new Date(date).toLocaleString("en-US", {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
hour: "numeric",
|
...(includeTime
|
||||||
minute: "numeric",
|
? {
|
||||||
second: "numeric",
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
second: "numeric",
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -182,38 +182,79 @@
|
|||||||
<ProfileUserDelete />
|
<ProfileUserDelete />
|
||||||
</div>
|
</div>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
|
|
||||||
<HoppSmartTab :id="'teams'" :label="t('team.title')">
|
<HoppSmartTab :id="'teams'" :label="t('team.title')">
|
||||||
<Teams :modal="false" class="p-4" />
|
<Teams :modal="false" class="p-4" />
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
|
|
||||||
|
<HoppSmartTab id="tokens" label="Tokens" class="space-y-4">
|
||||||
|
<AccessTokensOverview
|
||||||
|
@show-access-tokens-generate-modal="
|
||||||
|
showAccessTokensGenerateModal = true
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AccessTokensList
|
||||||
|
:access-tokens="accessTokens"
|
||||||
|
:has-more-tokens="hasMoreTokens"
|
||||||
|
:tokens-list-loading="tokensListLoading"
|
||||||
|
@delete-access-token="showDeleteAccessTokenConfirmation"
|
||||||
|
@fetch-more-tokens="fetchAccessTokens"
|
||||||
|
/>
|
||||||
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AccessTokensGenerateModal
|
||||||
|
v-if="showAccessTokensGenerateModal"
|
||||||
|
:access-token="accessToken"
|
||||||
|
:token-generate-action-loading="tokenGenerateActionLoading"
|
||||||
|
@generate-access-token="generateAccessToken"
|
||||||
|
@hide-modal="hideAccessTokenGenerateModal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HoppSmartConfirmModal
|
||||||
|
:show="confirmDeleteAccessToken"
|
||||||
|
:loading-state="tokenDeleteActionLoading"
|
||||||
|
:title="`Are you sure you want to delete the access token ${tokenToDelete?.label}?`"
|
||||||
|
@hide-modal="confirmDeleteAccessToken = false"
|
||||||
|
@resolve="() => deleteAccessToken()"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watchEffect, computed } from "vue"
|
import axios from "axios"
|
||||||
|
import * as E from "fp-ts/Either"
|
||||||
|
import { Ref, computed, onMounted, ref, watchEffect } from "vue"
|
||||||
|
|
||||||
import { platform } from "~/platform"
|
import { platform } from "~/platform"
|
||||||
|
|
||||||
import { invokeAction } from "~/helpers/actions"
|
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { useToast } from "@composables/toast"
|
|
||||||
import { useSetting } from "@composables/settings"
|
|
||||||
import { useColorMode } from "@composables/theming"
|
|
||||||
import { usePageHead } from "@composables/head"
|
import { usePageHead } from "@composables/head"
|
||||||
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { useSetting } from "@composables/settings"
|
||||||
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
|
import { useColorMode } from "@composables/theming"
|
||||||
|
import { useToast } from "@composables/toast"
|
||||||
|
import { invokeAction } from "~/helpers/actions"
|
||||||
|
|
||||||
import { toggleSetting } from "~/newstore/settings"
|
import { toggleSetting } from "~/newstore/settings"
|
||||||
|
|
||||||
import IconVerified from "~icons/lucide/verified"
|
|
||||||
import IconSettings from "~icons/lucide/settings"
|
import IconSettings from "~icons/lucide/settings"
|
||||||
|
import IconVerified from "~icons/lucide/verified"
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
|
|
||||||
type ProfileTabs = "sync" | "teams"
|
type ProfileTabs = "sync" | "teams"
|
||||||
|
|
||||||
|
export type AccessToken = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
createdOn: Date
|
||||||
|
lastUsedOn: Date
|
||||||
|
expiresOn: Date | null
|
||||||
|
}
|
||||||
|
|
||||||
const selectedProfileTab = ref<ProfileTabs>("sync")
|
const selectedProfileTab = ref<ProfileTabs>("sync")
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -224,6 +265,12 @@ usePageHead({
|
|||||||
title: computed(() => t("navigation.profile")),
|
title: computed(() => t("navigation.profile")),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const accessTokens: Ref<AccessToken[]> = ref([])
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchAccessTokens()
|
||||||
|
})
|
||||||
|
|
||||||
const SYNC_COLLECTIONS = useSetting("syncCollections")
|
const SYNC_COLLECTIONS = useSetting("syncCollections")
|
||||||
const SYNC_ENVIRONMENTS = useSetting("syncEnvironments")
|
const SYNC_ENVIRONMENTS = useSetting("syncEnvironments")
|
||||||
const SYNC_HISTORY = useSetting("syncHistory")
|
const SYNC_HISTORY = useSetting("syncHistory")
|
||||||
@@ -236,6 +283,24 @@ const probableUser = useReadonlyStream(
|
|||||||
platform.auth.getProbableUser()
|
platform.auth.getProbableUser()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const confirmDeleteAccessToken = ref(false)
|
||||||
|
const hasMoreTokens = ref(true)
|
||||||
|
const showAccessTokensGenerateModal = ref(false)
|
||||||
|
const tokenDeleteActionLoading = ref(false)
|
||||||
|
const tokenGenerateActionLoading = ref(false)
|
||||||
|
const tokensListLoading = ref(false)
|
||||||
|
|
||||||
|
const accessToken: Ref<string | null> = ref(null)
|
||||||
|
const tokenToDelete = ref<{ id: string; label: string } | null>(null)
|
||||||
|
|
||||||
|
const limit = 10
|
||||||
|
let offset = 0
|
||||||
|
|
||||||
|
const accessTokenEndpointMetadata = {
|
||||||
|
axiosPlatformConfig: platform.auth.axiosPlatformConfig?.() ?? {},
|
||||||
|
endpointPrefix: `${import.meta.env.VITE_BACKEND_API_URL}/access-tokens`,
|
||||||
|
}
|
||||||
|
|
||||||
const loadingCurrentUser = computed(() => {
|
const loadingCurrentUser = computed(() => {
|
||||||
if (!probableUser.value) return false
|
if (!probableUser.value) return false
|
||||||
else if (!currentUser.value) return true
|
else if (!currentUser.value) return true
|
||||||
@@ -305,4 +370,116 @@ const sendEmailVerification = () => {
|
|||||||
verifyingEmailAddress.value = false
|
verifyingEmailAddress.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchAccessTokens = async () => {
|
||||||
|
tokensListLoading.value = true
|
||||||
|
|
||||||
|
const { axiosPlatformConfig, endpointPrefix } = accessTokenEndpointMetadata
|
||||||
|
|
||||||
|
const endpoint = `${endpointPrefix}/list?offset=${offset}&limit=${limit}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(endpoint, axiosPlatformConfig)
|
||||||
|
|
||||||
|
accessTokens.value.push(...data)
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
offset += data.length
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMoreTokens.value = data.length === limit
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Something went wrong while fetching the list of tokens")
|
||||||
|
} finally {
|
||||||
|
tokensListLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateAccessToken = async ({
|
||||||
|
label,
|
||||||
|
expiryInDays,
|
||||||
|
}: {
|
||||||
|
label: string
|
||||||
|
expiryInDays: number | null
|
||||||
|
}) => {
|
||||||
|
tokenGenerateActionLoading.value = true
|
||||||
|
|
||||||
|
const { axiosPlatformConfig, endpointPrefix } = accessTokenEndpointMetadata
|
||||||
|
|
||||||
|
const endpoint = `${endpointPrefix}/create`
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
label,
|
||||||
|
expiryInDays,
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data }: { data: { token: string; info: AccessToken } } =
|
||||||
|
await axios.post(endpoint, body, axiosPlatformConfig)
|
||||||
|
|
||||||
|
accessTokens.value.unshift(data.info)
|
||||||
|
accessToken.value = data.token
|
||||||
|
|
||||||
|
// Incrementing the offset value by 1 to account for the newly generated token
|
||||||
|
offset += 1
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(`Something went wrong while generating the access token`)
|
||||||
|
} finally {
|
||||||
|
tokenGenerateActionLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAccessToken = async () => {
|
||||||
|
if (tokenToDelete.value === null) {
|
||||||
|
toast.error("error.something_went_wrong")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id: tokenIdToDelete } = tokenToDelete.value
|
||||||
|
|
||||||
|
tokenDeleteActionLoading.value = true
|
||||||
|
|
||||||
|
const { axiosPlatformConfig, endpointPrefix } = accessTokenEndpointMetadata
|
||||||
|
|
||||||
|
const endpoint = `${endpointPrefix}/revoke?id=${tokenIdToDelete}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.delete(endpoint, axiosPlatformConfig)
|
||||||
|
|
||||||
|
accessTokens.value = accessTokens.value.filter(
|
||||||
|
(token) => token.id !== tokenIdToDelete
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decreasing the offset value by 1 to account for the deleted token
|
||||||
|
offset = offset > 0 ? offset - 1 : offset
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Something went wrong while deleting the access token")
|
||||||
|
} finally {
|
||||||
|
tokenDeleteActionLoading.value = false
|
||||||
|
confirmDeleteAccessToken.value = false
|
||||||
|
|
||||||
|
tokenToDelete.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hideAccessTokenGenerateModal = () => {
|
||||||
|
// Reset the reactive state variable holding access token value and hide the modal
|
||||||
|
accessToken.value = null
|
||||||
|
showAccessTokensGenerateModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const showDeleteAccessTokenConfirmation = ({
|
||||||
|
tokenId,
|
||||||
|
tokenLabel,
|
||||||
|
}: {
|
||||||
|
tokenId: string
|
||||||
|
tokenLabel: string
|
||||||
|
}) => {
|
||||||
|
confirmDeleteAccessToken.value = true
|
||||||
|
|
||||||
|
tokenToDelete.value = {
|
||||||
|
id: tokenId,
|
||||||
|
label: tokenLabel,
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user