feat: edit and delete MCP

This commit is contained in:
Quicy
2026-01-22 14:37:00 +08:00
parent 3afba6974e
commit dc0e279243
6 changed files with 148 additions and 56 deletions
+18
View File
@@ -28,3 +28,21 @@ export type MCPConnection =
| StdioMCPConnection
| HTTPMCPConnection
| SSEMCPConnection
export interface MCPListItem{
id: string;
type: string;
name: string;
config: {
cwd: string;
env: Record<string, string>;
args: string[];
type: string;
command: string;
};
active: boolean;
user: string;
createdAt: string;
updatedAt: string;
}
+9 -9
View File
@@ -1,18 +1,18 @@
<script setup lang="ts">
import type { PrimitiveProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { BadgeVariants } from '.'
import { reactiveOmit } from '@vueuse/core'
import { Primitive } from 'reka-ui'
import type { PrimitiveProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import type { BadgeVariants } from "."
import { reactiveOmit } from "@vueuse/core"
import { Primitive } from "reka-ui"
import { cn } from '#/lib/utils'
import { badgeVariants } from '.'
import { badgeVariants } from "."
const props = defineProps<PrimitiveProps & {
variant?: BadgeVariants['variant']
class?: HTMLAttributes['class']
variant?: BadgeVariants["variant"]
class?: HTMLAttributes["class"]
}>()
const delegatedProps = reactiveOmit(props, 'class')
const delegatedProps = reactiveOmit(props, "class")
</script>
<template>
+32 -15
View File
@@ -196,10 +196,7 @@
<FormItem>
<FormControl>
<section class="flex gap-4">
<Label
for="airplane-mode"
>开启</Label>
<Label for="airplane-mode">开启</Label>
<Switch
id="airplane-mode"
:model-value="componentField.modelValue"
@@ -262,9 +259,10 @@ import {
import z from 'zod'
import { toTypedSchema } from '@vee-validate/zod'
import { useForm } from 'vee-validate'
import { ref,inject } from 'vue'
import { useMutation,useQueryCache } from '@pinia/colada'
import { ref, inject, watch } from 'vue'
import { useMutation, useQueryCache } from '@pinia/colada'
import request from '@/utils/request'
import { type MCPListItem as MCPType } from '@memoh/shared'
const validateSchema = toTypedSchema(z.object({
name: z.string().min(1),
@@ -273,9 +271,9 @@ const validateSchema = toTypedSchema(z.object({
command: z.string().min(1),
args: z.array(z.coerce.string().check(z.minLength(1))).min(1),
env: z.looseObject({}),
cwd:z.string().min(1)
cwd: z.string().min(1)
}),
active:z.coerce.boolean()
active: z.coerce.boolean()
}))
const envList = ref<string[]>([])
@@ -284,22 +282,41 @@ const form = useForm({
})
const queryCache=useQueryCache()
const queryCache = useQueryCache()
const { mutate: fetchMCP } = useMutation({
mutation: (data: Parameters<(Parameters<typeof form.handleSubmit>)[0]>[0]) => request({
url: '/mcp/',
method: 'post',
url: mcpEditData.value?.id ? `/mcp/${mcpEditData.value.id}` : '/mcp/',
method: mcpEditData.value?.id ? 'put' : 'post',
data
}),
onSettled:()=>queryCache.invalidateQueries({key:['mcp']})
onSettled: () => queryCache.invalidateQueries({ key: ['mcp'] })
})
const open = inject('open', ref(false))
const mcpEditData = inject('mcpEditData', ref<{
name: string,
config: MCPType['config'],
active: boolean
id: string
} | null>(null))
watch(open, () => {
if (open.value && mcpEditData.value) {
form.setValues(mcpEditData.value)
}
if (!open.value) {
mcpEditData.value = null
}
}, {
immediate: true
})
const open=inject('open',ref(false))
const createMCP = form.handleSubmit(async (value) => {
// console.log(value)
try {
console.log(mcpEditData.value)
fetchMCP(value)
open.value=false
open.value = false
} catch {
return
}
@@ -46,7 +46,7 @@ const table = useVueTable({
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableBody class="[&_td]:py-4!">
<template v-if="table.getRowModel().rows?.length">
<TableRow
v-for="row in table.getRowModel().rows"
+87 -30
View File
@@ -1,55 +1,111 @@
<template>
<section>
<section class="[&_td:last-child]:w-40">
<CreateMCP />
<DataTable
:columns="columns"
:data="[]"
:data="mcpFormatData"
/>
</section>
</template>
<script setup lang="ts">
import { useQuery } from '@pinia/colada'
import { useQuery,useMutation,useQueryCache } from '@pinia/colada'
import request from '@/utils/request'
import { watch, h, provide,ref } from 'vue'
import { watch, h, provide,ref, computed,reactive } from 'vue'
import DataTable from '@/components/DataTable/index.vue'
import CreateMCP from '@/components/CreateMCP/index.vue'
import { type ColumnDef } from '@tanstack/vue-table'
import {
Badge,
Button
} from '@memoh/ui'
import { type MCPListItem as MCPType } from '@memoh/shared'
const open=ref(false)
provide('open',open)
const columns = [
{
accessorKey: 'modelId',
header: () => h('div', { class: 'text-left py-4' }, 'Name'),
cell({ row }) {
return h('div', { class: 'text-left py-4' }, row.getValue('modelId'))
}
},
{
accessorKey: 'baseUrl',
header: () => h('div', { class: 'text-left' }, 'Base Url'),
},
{
accessorKey: 'apiKey',
header: () => h('div', { class: 'text-left' }, 'Api Key'),
},
{
accessorKey: 'clientType',
header: () => h('div', { class: 'text-left' }, 'Client Type'),
},
const open = ref(false)
const editMCPData = ref<{
name: string,
config: MCPType['config'],
active: boolean
id:string
}|null>(null)
provide('open', open)
provide('mcpEditData',editMCPData)
const queryCache=useQueryCache()
const { mutate:DeleteMCP}= useMutation({
mutation: (id:string) => request({
url: `/mcp/${id}`,
method:'DELETE'
}),
onSettled() {
queryCache.invalidateQueries({
key:['mcp']
})
}
})
const columns:ColumnDef<MCPType>[] = [
{
accessorKey: 'name',
header: () => h('div', { class: 'text-left' }, 'Name'),
header: () => h('div', { class: 'text-left py-4' }, 'Name'),
},
{
accessorKey: 'type',
header: () => h('div', { class: 'text-left' }, 'Type'),
},
{
accessorKey: 'config.command',
header: () => h('div', { class: 'text-left' }, 'Command'),
},
{
accessorKey: 'config.cwd',
header: () => h('div', { class: 'text-left' }, 'Cwd'),
},
{
accessorKey: 'config.args',
header: () => h('div', { class: 'text-left' }, 'Arguments'),
cell: ({ row }) => h('div', {class:'flex gap-4'}, row.original.config.args.map((argTxt) => {
return h(Badge, {
variant:'default'
},()=>argTxt)
}))
},
{
accessorKey: 'config.env',
header: () => h('div', { class: 'text-left' }, 'Env'),
cell: ({ row }) => h('div', { class: 'flex gap-4' }, Object.entries(row.original.config.env).map(([key,value]) => {
return h(Badge, {
variant: 'outline'
}, ()=>`${key}:${value}`)
}))
},
{
accessorKey: 'control',
header: () => h('div', { class: 'text-center' }, '操作'),
cell: ({ row }) => h('div', {class:'flex gap-2'}, [
h(Button, {
onClick() {
editMCPData.value = {
name: row.original.name,
config: {...row.original.config},
active: row.original.active,
id:row.original.id
}
open.value=true
}
}, ()=>'编辑'),
h(Button, {
variant: 'destructive',
async onClick() {
try {
await DeleteMCP(row.original.id)
} catch {
return
}
}
},()=>'删除')
])
}
]
@@ -60,7 +116,8 @@ const { data: mcpData } = useQuery({
})
})
watch(mcpData, () => {
console.log(mcpData.value?.data)
const mcpFormatData = computed(() => {
return mcpData.value?.data?.items??[]
})
</script>
+1 -1
View File
@@ -96,7 +96,7 @@ const columns: ComputedRef<ColumnDef<ModelType>[]> = computed(() => [
accessorKey: 'modelId',
header: () => h('div', { class: 'text-left py-4' }, 'Name'),
cell({ row }) {
return h('div', { class: 'text-left py-4' }, row.getValue('modelId'))
return h('div', { class: 'text-left' }, row.getValue('modelId'))
}
},
{