Files
speckle-server/packages/frontend-2/components/settings/user/developer/Applications/Applications.vue
T
2024-11-26 12:22:39 +01:00

190 lines
5.7 KiB
Vue

<template>
<div class="flex flex-col gap-4">
<SettingsSectionHeader
subheading
title="Applications"
:buttons="[
{
props: {
color: 'outline',
to: 'https://speckle.guide/dev/apps.html',
target: '_blank',
external: true
},
label: 'Open docs'
},
{
props: {
onClick: openCreateApplicationDialog
},
label: 'New application'
}
]"
>
<p class="text-body-xs pt-6 md:pt-4 text-foreground">
Register and manage third-party Speckle Apps that, once authorised by a user on
this server, can act on their behalf.
</p>
</SettingsSectionHeader>
<LayoutTable
:columns="[
{ id: 'name', header: 'Name', classes: 'col-span-3' },
{ id: 'id', header: 'ID', classes: 'col-span-2' },
{
id: 'scope',
header: 'Scope',
classes: 'col-span-6 whitespace-break-spaces text-xs'
},
{ id: 'actions', header: '', classes: 'col-span-1 flex justify-end' }
]"
:items="applications"
>
<template #name="{ item }">
{{ item.name }}
</template>
<template #id="{ item }">
<span class="rounded text-xs font-mono bg-foundation-page p-2">
{{ item.id }}
</span>
</template>
<template #scope="{ item }">
{{ getItemScopes(item) }}
</template>
<template #actions="{ item }">
<LayoutMenu
v-model:open="showActionsMenu[item.id]"
:items="actionItems"
mount-menu-on-body
:menu-position="HorizontalDirection.Left"
@chosen="({ item: actionItem }) => onActionChosen(actionItem, item)"
>
<FormButton
:color="showActionsMenu[item.id] ? 'outline' : 'subtle'"
hide-text
:icon-right="showActionsMenu[item.id] ? XMarkIcon : EllipsisHorizontalIcon"
@click.stop="toggleMenu(item.id)"
/>
</LayoutMenu>
</template>
</LayoutTable>
<SettingsUserDeveloperApplicationsCreateEditDialog
v-model:open="showCreateEditApplicationDialog"
:application="applicationToEdit"
@application-created="handleApplicationCreated"
/>
<SettingsUserDeveloperApplicationsSuccessDialog
v-model:open="showCreateApplicationSuccessDialog"
:application="createdApplication"
/>
<SettingsUserDeveloperApplicationsRevealSecretDialog
v-model:open="showRevealSecretDialog"
:application="applicationToReveal"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useQuery } from '@vue/apollo-composable'
import { EllipsisHorizontalIcon, XMarkIcon } from '@heroicons/vue/24/outline'
import { developerSettingsApplicationsQuery } from '~~/lib/developer-settings/graphql/queries'
import type { ApplicationItem } from '~~/lib/developer-settings/helpers/types'
import { HorizontalDirection } from '~~/lib/common/composables/window'
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
const emit = defineEmits<{
(e: 'delete', item: ApplicationItem): void
}>()
const { result: applicationsResult, refetch: refetchApplications } = useQuery(
developerSettingsApplicationsQuery
)
const showCreateEditApplicationDialog = ref(false)
const showCreateApplicationSuccessDialog = ref(false)
const showRevealSecretDialog = ref(false)
const showActionsMenu = ref<Record<string, boolean>>({})
const applicationToEdit = ref<ApplicationItem | undefined>(undefined)
const applicationToReveal = ref<ApplicationItem | null>(null)
const createdApplication = ref<ApplicationItem | null>(null)
const applications = computed<ApplicationItem[]>(() => {
return applicationsResult.value?.activeUser?.createdApps || []
})
enum ActionTypes {
EditApplication = 'edit-application',
RevealSecret = 'reveal-secret',
RemoveApplication = 'remove-application'
}
const actionItems: LayoutMenuItem[][] = [
[
{
title: 'Edit application...',
id: ActionTypes.EditApplication
},
{
title: 'Reveal secret...',
id: ActionTypes.RevealSecret
},
{
title: 'Remove application...',
id: ActionTypes.RemoveApplication
}
]
]
const onActionChosen = (actionItem: LayoutMenuItem, application: ApplicationItem) => {
if (actionItem.id === ActionTypes.EditApplication) {
openEditApplicationDialog(application)
} else if (actionItem.id === ActionTypes.RevealSecret) {
applicationToReveal.value = application
showRevealSecretDialog.value = true
} else if (actionItem.id === ActionTypes.RemoveApplication) {
emit('delete', application)
}
}
const toggleMenu = (itemId: string) => {
showActionsMenu.value[itemId] = !showActionsMenu.value[itemId]
}
const openCreateApplicationDialog = () => {
applicationToEdit.value = undefined
showCreateEditApplicationDialog.value = true
}
const openEditApplicationDialog = (item: ApplicationItem) => {
applicationToEdit.value = item
showCreateEditApplicationDialog.value = true
}
const handleApplicationCreated = (applicationId: string) => {
refetchApplications()?.then(() => {
const newApplication = applications.value.find((app) => app.id === applicationId)
if (newApplication) {
createdApplication.value = newApplication
showCreateApplicationSuccessDialog.value = true
}
})
}
const getItemScopes = (item: ApplicationItem): string => {
if (!item.scopes || item.scopes.length === 0) return 'No scopes available'
return item.scopes
.map((scope) => {
if (typeof scope === 'string') return `"${scope}"`
if (typeof scope === 'object' && scope !== null) {
return `"${scope.name}"`
}
})
.join(', ')
}
</script>