mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-02 15:46:51 +00:00
fix: new aui
This commit is contained in:
131
packages/nc-gui/components/dashboard/settings/base/MCP/Code.vue
Normal file
131
packages/nc-gui/components/dashboard/settings/base/MCP/Code.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { LoadingOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
interface Props {
|
||||
code: string
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const code = toRef(props, 'code')
|
||||
|
||||
const indicator = h(LoadingOutlined, {
|
||||
style: {
|
||||
fontSize: '2rem',
|
||||
},
|
||||
spin: true,
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { copy } = useCopy()
|
||||
|
||||
const isCopied = ref(false)
|
||||
|
||||
const onCopyToClipboard = async () => {
|
||||
try {
|
||||
await copy(code.value)
|
||||
// Copied to clipboard
|
||||
message.info(t('msg.info.copiedToClipboard'))
|
||||
|
||||
isCopied.value = true
|
||||
|
||||
setTimeout(() => {
|
||||
isCopied.value = false
|
||||
}, 5000)
|
||||
} catch (e: any) {
|
||||
message.error(e.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="nc-mcp-code-tab-wrapper h-80 flex flex-col mt-2">
|
||||
<div class="flex h-9 bg-gray-50 border-b-1 border-nc-border-gray-medium rounded-t-lg items-center px-3">
|
||||
<div class="flex-1 text-nc-content-gray leading-5">JSON</div>
|
||||
<NcButton type="text" size="small" class="!hover:bg-gray-200" @click="onCopyToClipboard">
|
||||
<div class="flex items-center gap-2 text-small leading-[18px] min-w-80px justify-center">
|
||||
<GeneralIcon
|
||||
:icon="isCopied ? 'circleCheck' : 'copy'"
|
||||
class="h-4 w-4"
|
||||
:class="{
|
||||
'text-gray-700': !isCopied,
|
||||
'text-green-700': isCopied,
|
||||
}"
|
||||
/>
|
||||
{{ isCopied ? $t('general.copied') : $t('general.copy') }}
|
||||
</div>
|
||||
</NcButton>
|
||||
</div>
|
||||
<Suspense>
|
||||
<MonacoEditor
|
||||
class="h-72 !rounded-b-lg overflow-hidden !bg-gray-50"
|
||||
:model-value="code"
|
||||
:read-only="true"
|
||||
lang="json"
|
||||
:validate="false"
|
||||
:disable-deep-compare="true"
|
||||
:monaco-config="{
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
fontSize: 13,
|
||||
lineHeight: 18,
|
||||
padding: {
|
||||
top: 12,
|
||||
bottom: 12,
|
||||
},
|
||||
overviewRulerBorder: false,
|
||||
overviewRulerLanes: 0,
|
||||
hideCursorInOverviewRuler: true,
|
||||
lineDecorationsWidth: 12,
|
||||
lineNumbersMinChars: 0,
|
||||
roundedSelection: false,
|
||||
selectOnLineNumbers: false,
|
||||
scrollBeyondLastLine: false,
|
||||
contextmenu: false,
|
||||
glyphMargin: false,
|
||||
folding: false,
|
||||
bracketPairColorization: { enabled: false },
|
||||
wordWrap: 'on',
|
||||
scrollbar: {
|
||||
horizontal: 'hidden',
|
||||
verticalScrollbarSize: 6,
|
||||
},
|
||||
renderIndentGuides: false,
|
||||
wrappingStrategy: 'advanced',
|
||||
renderLineHighlight: 'none',
|
||||
tabSize: 2,
|
||||
detectIndentation: false,
|
||||
insertSpaces: true,
|
||||
lineNumbers: 'off',
|
||||
}"
|
||||
hide-minimap
|
||||
/>
|
||||
<template #fallback>
|
||||
<div class="h-full w-full flex flex-col justify-center items-center mt-28">
|
||||
<a-spin size="large" :indicator="indicator" />
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.nc-mcp-code-tab-wrapper {
|
||||
@apply !bg-nc-bg-gray-extra-light border-1 border-nc-border-gray-medium rounded-lg flex-1;
|
||||
|
||||
.monaco-editor {
|
||||
@apply !border-0 !rounded-b-lg pr-3;
|
||||
}
|
||||
.overflow-guard {
|
||||
@apply !border-0 !rounded-b-lg;
|
||||
}
|
||||
.monaco-editor,
|
||||
.monaco-diff-editor,
|
||||
.monaco-component {
|
||||
--vscode-editor-background: #f9f9fa;
|
||||
--vscode-editorGutter-background: #f9f9fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
252
packages/nc-gui/components/dashboard/settings/base/MCP/Modal.vue
Normal file
252
packages/nc-gui/components/dashboard/settings/base/MCP/Modal.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
value: boolean
|
||||
token: MCPTokenExtendedType
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emits = defineEmits(['close', 'update:value', 'update:token'])
|
||||
|
||||
const modalVisible = useVModel(props, 'value')
|
||||
|
||||
const { appInfo } = useGlobal()
|
||||
|
||||
const { openedProject } = storeToRefs(useBases())
|
||||
|
||||
const token = useVModel(props, 'token')
|
||||
|
||||
const supportedDocs = [
|
||||
{
|
||||
title: 'Getting with MCP Server',
|
||||
href: 'https://docs.nocodb.com/automation/webhook/create-webhook/',
|
||||
},
|
||||
{
|
||||
title: 'Setting up MCP Server with Claude',
|
||||
href: 'https://docs.nocodb.com/automation/webhook/webhook-overview',
|
||||
},
|
||||
{
|
||||
title: 'Setting up MCP Server with Cursor',
|
||||
href: 'https://docs.nocodb.com/automation/webhook/create-webhook#webhook-with-custom-payload-',
|
||||
},
|
||||
{
|
||||
title: 'Setting up MCP Server with Windsurf',
|
||||
href: 'https://docs.nocodb.com/automation/webhook/create-webhook#webhook-with-conditions',
|
||||
},
|
||||
]
|
||||
|
||||
const { updateMcpToken } = useMcpSettings()
|
||||
|
||||
const regenerateToken = async () => {
|
||||
const newToken = await updateMcpToken(token.value)
|
||||
|
||||
console.log(newToken)
|
||||
|
||||
if (newToken) {
|
||||
token.value = newToken
|
||||
}
|
||||
}
|
||||
|
||||
const closeModal = () => {
|
||||
emits('close')
|
||||
modalVisible.value = false
|
||||
}
|
||||
|
||||
const activeTab = ref<'claude' | 'cursor' | 'windsurf'>('claude')
|
||||
|
||||
const code = computed(
|
||||
() => `
|
||||
{
|
||||
"mcpServers": {
|
||||
"NocoDB Base - ${openedProject.value?.title}": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"mcp-remote",
|
||||
"${appInfo.value.ncSiteUrl}/mcp/${token.value.id}",
|
||||
"--header",
|
||||
"xc-mcp-token: ${token.value?.token ?? 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'}"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcModal v-model:visible="modalVisible" :show-separator="true" size="large" wrap-class-name="nc-modal-mcp-token-create-edit">
|
||||
<template #header>
|
||||
<div class="flex w-full items-center px-4 py-2 justify-between">
|
||||
<div class="flex items-center gap-3 flex-1">
|
||||
<GeneralIcon class="text-gray-900 h-5 w-5" icon="mcp" />
|
||||
<span class="text-gray-900 truncate font-semibold text-xl">
|
||||
{{ token.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end items-center gap-3 flex-1">
|
||||
<NcButton type="text" size="small" data-testid="nc-close-webhook-modal" @click.stop="closeModal">
|
||||
<GeneralIcon icon="close" />
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex bg-white rounded-b-2xl h-[calc(100%_-_66px)]">
|
||||
<div
|
||||
ref="containerElem"
|
||||
class="h-full flex-1 flex flex-col overflow-y-auto scroll-smooth nc-scrollbar-thin px-12 py-6 mx-auto"
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="text-nc-content-gray font-bold leading-6">
|
||||
{{ $t('labels.mcpSetup') }}
|
||||
</div>
|
||||
|
||||
<NcTabs v-model:activeKey="activeTab">
|
||||
<a-tab-pane key="claude" class="!h-full">
|
||||
<template #tab>
|
||||
<span
|
||||
:class="{
|
||||
'text-brand-500 font-medium': activeTab === 'claude',
|
||||
'text-gray-700': activeTab !== 'claude',
|
||||
}"
|
||||
class="text-sm"
|
||||
>
|
||||
Claude
|
||||
</span>
|
||||
</template>
|
||||
<div class="relative flex flex-col leading-6 text-nc-content-gray-subtle2 gap-3 my-3">
|
||||
Get started with the NocoDB MCP with Claude Desktop in 3 simple steps
|
||||
|
||||
<ol class="list-decimal pl-5">
|
||||
<li>Navigate to Claude Desktop settings from the navigation bar.</li>
|
||||
<li>Go to the Develop Tab, and click on “Edit Config”.</li>
|
||||
<li>Add the JSON configuration that’s provided after creating a token in claude_desktop_config.json</li>
|
||||
</ol>
|
||||
|
||||
<NcButton type="secondary" class="w-44" size="small" :loading="token.loading" @click="regenerateToken(token)">
|
||||
{{ $t('labels.regenerateToken') }}
|
||||
</NcButton>
|
||||
|
||||
<DashboardSettingsBaseMCPCode :key="code" :code="code" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="cursor" class="!h-full">
|
||||
<template #tab>
|
||||
<span
|
||||
:class="{
|
||||
'text-brand-500 font-medium': activeTab === 'cursor',
|
||||
'text-gray-700': activeTab !== 'cursor',
|
||||
}"
|
||||
class="text-sm"
|
||||
>
|
||||
Cursor
|
||||
</span>
|
||||
</template>
|
||||
<div class="relative flex flex-col leading-6 text-nc-content-gray-subtle2 gap-3 my-3">
|
||||
Get started with the NocoDB MCP with Cursor in 3 simple steps
|
||||
|
||||
<ol class="list-decimal pl-5">
|
||||
<li>Open Cursor Settings (press Shift+Cmd+J)</li>
|
||||
<li>Select the "MCP" tab and click "Add MCP Server" .</li>
|
||||
<li>Add the JSON configuration that’s provided after creating a token.</li>
|
||||
</ol>
|
||||
|
||||
<NcButton type="secondary" class="w-44" size="small" :loading="token.loading" @click="regenerateToken(token)">
|
||||
{{ $t('labels.regenerateToken') }}
|
||||
</NcButton>
|
||||
<DashboardSettingsBaseMCPCode :key="code" :code="code" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="windsurf" class="!h-full">
|
||||
<template #tab>
|
||||
<span
|
||||
:class="{
|
||||
'text-brand-500 font-medium': activeTab === 'windsurf',
|
||||
'text-gray-700': activeTab !== 'windsurf',
|
||||
}"
|
||||
class="text-sm"
|
||||
>
|
||||
Windsurf
|
||||
</span>
|
||||
</template>
|
||||
<div class="relative flex flex-col leading-6 text-nc-content-gray-subtle2 gap-3 my-3">
|
||||
Get started with the NocoDB MCP with Windsurf in 4 simple steps
|
||||
|
||||
<ol class="list-decimal pl-5">
|
||||
<li>Access Windsurf settings and Select Cascade Tab in the left sidebar</li>
|
||||
<li>Click on Add Server.</li>
|
||||
<li>Now click on Add Custom Server in the modal.</li>
|
||||
<li>Paste the JSON configuration that’s provided after creating a token in the opened file</li>
|
||||
</ol>
|
||||
|
||||
<NcButton type="secondary" class="w-44" size="small" :loading="token.loading" @click="regenerateToken(token)">
|
||||
{{ $t('labels.regenerateToken') }}
|
||||
</NcButton>
|
||||
|
||||
<DashboardSettingsBaseMCPCode :code="code" />
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</NcTabs>
|
||||
</div>
|
||||
|
||||
<NcAlert type="info" class="mt-3">
|
||||
<template #message>
|
||||
{{ $t('labels.mcpTokenVisibilityInfo') }}
|
||||
</template>
|
||||
<template #description>
|
||||
{{ $t('labels.mcpTokenVisibilityInfoDescription') }} <br />
|
||||
{{ $t('labels.mcpTokenVisibilityInfoDescription2') }}
|
||||
</template>
|
||||
</NcAlert>
|
||||
</div>
|
||||
<div class="h-full bg-gray-50 border-l-1 w-80 p-5 rounded-br-2xl border-gray-200">
|
||||
<div class="w-full flex flex-col gap-3">
|
||||
<h2 class="text-sm text-gray-700 font-semibold !my-0">{{ $t('labels.supportDocs') }}</h2>
|
||||
<div>
|
||||
<div v-for="(doc, idx) of supportedDocs" :key="idx" class="flex items-center gap-1">
|
||||
<div class="h-7 w-7 flex items-center justify-center">
|
||||
<GeneralIcon icon="bookOpen" class="flex-none w-4 h-4 text-gray-500" />
|
||||
</div>
|
||||
<NuxtLink
|
||||
:href="doc.href"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="!text-gray-500 text-sm !no-underline !hover:underline"
|
||||
>
|
||||
{{ doc.title }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NcModal>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.nc-modal-mcp-token-create-edit {
|
||||
z-index: 1050;
|
||||
a {
|
||||
@apply !no-underline !text-gray-700 !hover:text-primary;
|
||||
}
|
||||
.nc-modal {
|
||||
@apply !p-0;
|
||||
height: min(calc(100vh - 100px), 1024px);
|
||||
max-height: min(calc(100vh - 100px), 1024px) !important;
|
||||
}
|
||||
|
||||
.nc-modal-header {
|
||||
@apply !mb-0 !pb-0;
|
||||
}
|
||||
|
||||
.ant-tabs-nav {
|
||||
@apply !pl-0;
|
||||
}
|
||||
|
||||
.ant-tabs-tab {
|
||||
@apply pt-1 pb-1.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
265
packages/nc-gui/components/dashboard/settings/base/MCP/index.vue
Normal file
265
packages/nc-gui/components/dashboard/settings/base/MCP/index.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { appInfo } = useGlobal()
|
||||
|
||||
const { copy } = useCopy()
|
||||
|
||||
const { sorts, sortDirection, loadSorts, handleGetSortedData, saveOrUpdate: saveOrUpdateSort } = useUserSorts('Webhook') // Using 'Webhook' as the sort type since 'MCPToken' isn't defined
|
||||
|
||||
const orderBy = computed<Record<string, SordDirectionType>>({
|
||||
get: () => {
|
||||
return sortDirection.value
|
||||
},
|
||||
set: (value: Record<string, SordDirectionType>) => {
|
||||
// Check if value is an empty object
|
||||
if (Object.keys(value).length === 0) {
|
||||
saveOrUpdateSort({})
|
||||
return
|
||||
}
|
||||
|
||||
const entries = Object.entries(value)
|
||||
if (entries.length > 0) {
|
||||
const [field, direction] = entries[0]
|
||||
saveOrUpdateSort({
|
||||
field,
|
||||
direction,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
mcpTokens,
|
||||
createMcpToken,
|
||||
listMcpTokens,
|
||||
cancelNewMcpToken,
|
||||
isUnsavedMCPTokenPending,
|
||||
addNewMcpToken,
|
||||
isCreatingMcpToken,
|
||||
newMcpTokenTitle,
|
||||
updateMcpToken,
|
||||
} = useMcpSettings()
|
||||
|
||||
const sortedMcpTokens = computed(() => handleGetSortedData(mcpTokens.value, sorts.value))
|
||||
|
||||
const columns = [
|
||||
{
|
||||
key: 'name',
|
||||
title: t('general.name'),
|
||||
name: 'Token',
|
||||
minWidth: 397,
|
||||
padding: '12px 24px',
|
||||
showOrderBy: true,
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
key: 'created_at',
|
||||
title: t('labels.createdOn'),
|
||||
width: 150,
|
||||
minWidth: 180,
|
||||
showOrderBy: true,
|
||||
dataIndex: 'created_at',
|
||||
},
|
||||
{
|
||||
key: 'action',
|
||||
title: t('general.action'),
|
||||
width: 162,
|
||||
minWidth: 162,
|
||||
justify: 'justify-end',
|
||||
align: 'center',
|
||||
},
|
||||
] as NcTableColumnProps[]
|
||||
|
||||
onMounted(async () => {
|
||||
loadSorts()
|
||||
await listMcpTokens()
|
||||
})
|
||||
|
||||
const isTokenModalVisible = ref(false)
|
||||
|
||||
const activeToken = ref<MCPTokenExtendedType | null>(null)
|
||||
|
||||
const handleOpenTokenModal = (token: MCPTokenExtendedType) => {
|
||||
activeToken.value = token
|
||||
isTokenModalVisible.value = true
|
||||
}
|
||||
|
||||
const createTokenWithExpiry = async (token: Partial<MCPTokenExtendedType>) => {
|
||||
const res = await createMcpToken(token)
|
||||
|
||||
if (res) {
|
||||
handleOpenTokenModal(res)
|
||||
}
|
||||
}
|
||||
|
||||
const regenerateToken = async (token: MCPTokenExtendedType) => {
|
||||
const newToken = await updateMcpToken(token)
|
||||
|
||||
if (newToken) {
|
||||
handleOpenTokenModal(newToken)
|
||||
}
|
||||
}
|
||||
|
||||
const closeModal = async () => {
|
||||
activeToken.value = null
|
||||
isTokenModalVisible.value = false
|
||||
await listMcpTokens()
|
||||
}
|
||||
|
||||
const confirmDeleteToken = (token: MCPTokenExtendedType) => {
|
||||
const isOpen = ref(true)
|
||||
|
||||
const { close } = useDialog(resolveComponent('DlgMCPDelete'), {
|
||||
'modelValue': isOpen,
|
||||
'mcpToken': token,
|
||||
'onUpdate:modelValue': closeDialog,
|
||||
'onDeleted': async () => {
|
||||
closeDialog()
|
||||
await listMcpTokens()
|
||||
},
|
||||
})
|
||||
|
||||
function closeDialog() {
|
||||
isOpen.value = false
|
||||
close(1000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="isCreatingMcpToken" class="absolute w-full h-full inset-0 flex items-center justify-center z-90 bg-black/12">
|
||||
<div
|
||||
v-if="isCreatingMcpToken"
|
||||
style="box-shadow: 0px 8px 8px -4px rgba(0, 0, 0, 0.04), 0px 20px 24px -4px rgba(0, 0, 0, 0.1)"
|
||||
class="bg-white p-6 flex flex-col w-[488px] rounded-2xl"
|
||||
>
|
||||
<div class="text-nc-content-gray-emphasis text-lg font-bold">{{ $t('labels.creatingToken') }}</div>
|
||||
<div class="text-nc-gray-subtle2 mt-2">
|
||||
{{ $t('labels.creatingTokenDescription') }}
|
||||
</div>
|
||||
|
||||
<div class="w-full flex justify-between items-center gap-3 mt-5">
|
||||
<GeneralLoader size="xlarge" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="text-nc-content-gray-emphasis font-semibold text-lg">
|
||||
{{ $t('labels.modelContextProtocol') }}
|
||||
</div>
|
||||
|
||||
<div class="text-nc-content-gray-subtle2 mt-2 leading-5">
|
||||
{{ $t('labels.mcpSubText') }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center mt-6 gap-5">
|
||||
<NcButton
|
||||
:disabled="isUnsavedMCPTokenPending"
|
||||
type="ghost"
|
||||
class="!text-primary"
|
||||
data-testid="add-new-mcp-token"
|
||||
size="small"
|
||||
:class="{
|
||||
'!text-nc-content-inverted-primary-disabled': isUnsavedMCPTokenPending,
|
||||
}"
|
||||
@click="addNewMcpToken"
|
||||
>
|
||||
{{ $t('labels.newMCPEndpoint') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
<NcTable
|
||||
v-model:order-by="orderBy"
|
||||
:columns="columns"
|
||||
header-row-height="44px"
|
||||
row-height="44px"
|
||||
:data="sortedMcpTokens"
|
||||
class="h-full mt-5"
|
||||
body-row-class-name="nc-base-settings-mcp-token-item"
|
||||
>
|
||||
<template #bodyCell="{ column, record: token }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<NcTooltip
|
||||
v-if="!token.isNew"
|
||||
class="truncate text-gray-800 font-semibold text-sm"
|
||||
@click="handleOpenTokenModal(token)"
|
||||
>
|
||||
{{ token.title }}
|
||||
|
||||
<template #title>
|
||||
<div class="text-[10px] leading-[14px] uppercase font-semibold pt-1 text-gray-300">
|
||||
{{ $t('labels.createdOn') }}
|
||||
</div>
|
||||
<div class="mt-1 text-[13px]">
|
||||
{{ dayjs(token.created_at).format('D MMMM YYYY, hh:mm A') }}
|
||||
</div>
|
||||
<div class="text-[10px] leading-[14px] uppercase font-semibold mt-2 text-gray-300">
|
||||
{{ $t('labels.createdBy') }}
|
||||
</div>
|
||||
<div class="mt-1 pb-1 text-[13px]">
|
||||
{{ token.created_display_name }}
|
||||
</div>
|
||||
</template>
|
||||
</NcTooltip>
|
||||
<a-input v-else v-model:value="newMcpTokenTitle" class="new-token-title" placeholder="Token name" />
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'created_at'">
|
||||
<div v-if="!token.isNew && token.created_at" class="text-nc-content-gray-subtle" @click="handleOpenTokenModal(token)">
|
||||
{{ dayjs(token.created_at).format('D MMM YYYY') }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<NcDropdown v-if="!token.isNew">
|
||||
<NcButton type="secondary" size="small">
|
||||
<GeneralIcon icon="threeDotVertical" />
|
||||
</NcButton>
|
||||
|
||||
<template #overlay>
|
||||
<NcMenu variant="small">
|
||||
<NcMenuItem @click="regenerateToken(token)">
|
||||
<GeneralIcon icon="refresh" />
|
||||
{{ $t('labels.regenerateToken') }}
|
||||
</NcMenuItem>
|
||||
<NcDivider />
|
||||
<NcMenuItem class="!text-nc-content-red-dark !hover:bg-nc-bg-red-light" @click="confirmDeleteToken(token)">
|
||||
<GeneralIcon icon="ncTrash2" />
|
||||
{{ $t('labels.deleteToken') }}
|
||||
</NcMenuItem>
|
||||
</NcMenu>
|
||||
</template>
|
||||
</NcDropdown>
|
||||
<div v-else>
|
||||
<div class="flex gap-2">
|
||||
<NcButton data-testid="cancel-token-btn" type="secondary" size="small" @click="cancelNewMcpToken()">
|
||||
{{ $t('general.cancel') }}
|
||||
</NcButton>
|
||||
|
||||
<NcButton data-testid="create-token-btn" type="primary" size="small" @click="createTokenWithExpiry(token)">
|
||||
{{ $t('general.save') }}
|
||||
</NcButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</NcTable>
|
||||
|
||||
<DashboardSettingsBaseMCPModal
|
||||
v-if="isTokenModalVisible"
|
||||
v-model:visible="isTokenModalVisible"
|
||||
v-model:token="activeToken"
|
||||
@close="closeModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ant-input {
|
||||
@apply rounded-lg py-1 px-3 w-398 h-8 border-1 focus:border-brand-500 border-gray-200;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user