feat(web): add "部署 Sub Agent" menu + page (pick resources + template -> deploy)
Customer-facing, plain Chinese (no env/technical jargon). New sidebar item
"部署 Sub Agent" -> /deploy-agent. The page:
- Step 1: pick which bound resources the agent may use (multi-select cards from
/api/resources).
- Step 2: pick an agent template (cards show Chinese name + description from
/api/heicode/agent-templates).
- Deploy -> POST /api/heicode/agents {template_id, binding_ids}.
- "我的 Agent" list (/api/heicode/agents) with status, copy-address, stop, delete.
Frontend builds clean (tsc + rsbuild; routeTree regenerated). zh label added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,365 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import {
|
||||||
|
Bot,
|
||||||
|
CheckCircle2,
|
||||||
|
Copy,
|
||||||
|
Database,
|
||||||
|
GitBranch,
|
||||||
|
RefreshCw,
|
||||||
|
Rocket,
|
||||||
|
Server,
|
||||||
|
Square,
|
||||||
|
Trash2,
|
||||||
|
} from 'lucide-react'
|
||||||
|
import { toast } from 'sonner'
|
||||||
|
import { api } from '@/lib/api'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
|
|
||||||
|
type ResourceItem = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
resource_type: string
|
||||||
|
provider: string
|
||||||
|
external_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TemplateItem = {
|
||||||
|
template_id: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
model: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AgentItem = {
|
||||||
|
agent_id: string
|
||||||
|
template_id: string
|
||||||
|
subdomain: string
|
||||||
|
access_token: string
|
||||||
|
binding_ids: number[]
|
||||||
|
status: string
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const RESOURCE_ICON: Record<string, typeof GitBranch> = {
|
||||||
|
git: GitBranch,
|
||||||
|
vm: Server,
|
||||||
|
database: Database,
|
||||||
|
blob: Database,
|
||||||
|
}
|
||||||
|
|
||||||
|
function resourceTypeLabel(t: string): string {
|
||||||
|
return (
|
||||||
|
{ git: '代码仓库', vm: '虚拟机', database: '数据库', blob: '对象存储' }[t] ??
|
||||||
|
t
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function statusLabel(s: string): { text: string; cls: string } {
|
||||||
|
const v = (s || '').toLowerCase()
|
||||||
|
if (['running', 'active', 'ready'].includes(v))
|
||||||
|
return { text: '运行中', cls: 'bg-emerald-500/15 text-emerald-300 ring-emerald-500/30' }
|
||||||
|
if (['stopped'].includes(v))
|
||||||
|
return { text: '已停止', cls: 'bg-muted/40 text-muted-foreground ring-border' }
|
||||||
|
if (['failed', 'error', 'unhealthy', 'crashed'].includes(v))
|
||||||
|
return { text: '异常', cls: 'bg-rose-500/15 text-rose-300 ring-rose-500/30' }
|
||||||
|
return { text: s || '—', cls: 'bg-amber-500/15 text-amber-300 ring-amber-500/30' }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listResources(): Promise<ResourceItem[]> {
|
||||||
|
const res = await api.get<{ data?: { items?: ResourceItem[] } }>(
|
||||||
|
'/api/resources/?status=active'
|
||||||
|
)
|
||||||
|
return res.data?.data?.items ?? []
|
||||||
|
}
|
||||||
|
async function listTemplates(): Promise<TemplateItem[]> {
|
||||||
|
const res = await api.get<{ data?: { items?: TemplateItem[] } }>(
|
||||||
|
'/api/heicode/agent-templates'
|
||||||
|
)
|
||||||
|
return res.data?.data?.items ?? []
|
||||||
|
}
|
||||||
|
async function listAgents(): Promise<AgentItem[]> {
|
||||||
|
const res = await api.get<{ data?: { items?: AgentItem[] } }>(
|
||||||
|
'/api/heicode/agents'
|
||||||
|
)
|
||||||
|
return res.data?.data?.items ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeploySubAgentPage() {
|
||||||
|
const qc = useQueryClient()
|
||||||
|
const [selectedBindings, setSelectedBindings] = useState<number[]>([])
|
||||||
|
const [selectedTemplate, setSelectedTemplate] = useState<string>('')
|
||||||
|
|
||||||
|
const resourcesQ = useQuery({
|
||||||
|
queryKey: ['resources', 'active'],
|
||||||
|
queryFn: listResources,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
const templatesQ = useQuery({
|
||||||
|
queryKey: ['agent-templates'],
|
||||||
|
queryFn: listTemplates,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
const agentsQ = useQuery({
|
||||||
|
queryKey: ['heicode-agents'],
|
||||||
|
queryFn: listAgents,
|
||||||
|
refetchInterval: 30_000,
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const deploy = useMutation({
|
||||||
|
mutationFn: () =>
|
||||||
|
api.post('/api/heicode/agents', {
|
||||||
|
template_id: selectedTemplate,
|
||||||
|
binding_ids: selectedBindings,
|
||||||
|
}),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('Agent 已开始部署')
|
||||||
|
setSelectedBindings([])
|
||||||
|
setSelectedTemplate('')
|
||||||
|
void qc.invalidateQueries({ queryKey: ['heicode-agents'] })
|
||||||
|
},
|
||||||
|
onError: (e) =>
|
||||||
|
toast.error(e instanceof Error ? e.message : '部署失败,请稍后重试'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggleBinding = (id: number) =>
|
||||||
|
setSelectedBindings((s) =>
|
||||||
|
s.includes(id) ? s.filter((x) => x !== id) : [...s, id]
|
||||||
|
)
|
||||||
|
|
||||||
|
const resources = resourcesQ.data ?? []
|
||||||
|
const templates = templatesQ.data ?? []
|
||||||
|
const canDeploy = Boolean(selectedTemplate) && !deploy.isPending
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='space-y-6'>
|
||||||
|
{/* Header */}
|
||||||
|
<div className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] bg-[color-mix(in_oklch,var(--card)_70%,transparent)] p-5'>
|
||||||
|
<p className='text-muted-foreground text-[11px] font-semibold tracking-[0.16em] uppercase'>
|
||||||
|
Heicode Manager
|
||||||
|
</p>
|
||||||
|
<h2 className='mt-1 flex items-center gap-2 text-xl font-semibold'>
|
||||||
|
<Bot className='text-primary h-5 w-5' /> 部署 Sub Agent
|
||||||
|
</h2>
|
||||||
|
<p className='text-muted-foreground mt-1 text-sm'>
|
||||||
|
选择要让 Agent 使用的资源,挑一个 Agent 模板,一键部署到云端。部署后即可在桌面客户端里使用。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Step 1: pick resources */}
|
||||||
|
<section className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] bg-[color-mix(in_oklch,var(--card)_70%,transparent)] p-5'>
|
||||||
|
<h3 className='text-base font-semibold'>1. 选择资源</h3>
|
||||||
|
<p className='text-muted-foreground mt-0.5 text-xs'>
|
||||||
|
勾选这个 Agent 可以使用的已绑定资源(可多选,也可不选)。没绑定过的资源请先去「资源绑定」添加。
|
||||||
|
</p>
|
||||||
|
{resourcesQ.isLoading ? (
|
||||||
|
<div className='mt-4 grid gap-3 sm:grid-cols-2'>
|
||||||
|
{[0, 1].map((i) => (
|
||||||
|
<Skeleton key={i} className='h-16 rounded-xl' />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : resources.length === 0 ? (
|
||||||
|
<p className='text-muted-foreground mt-4 rounded-xl border border-dashed p-6 text-center text-sm'>
|
||||||
|
还没有绑定任何资源。
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className='mt-4 grid gap-3 sm:grid-cols-2'>
|
||||||
|
{resources.map((r) => {
|
||||||
|
const Icon = RESOURCE_ICON[r.resource_type] ?? Database
|
||||||
|
const on = selectedBindings.includes(r.id)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
key={r.id}
|
||||||
|
onClick={() => toggleBinding(r.id)}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center gap-3 rounded-xl border p-3 text-left transition',
|
||||||
|
on
|
||||||
|
? 'border-primary/60 bg-[color-mix(in_oklch,var(--primary)_12%,transparent)]'
|
||||||
|
: 'border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] hover:border-primary/40'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'flex h-8 w-8 shrink-0 items-center justify-center rounded-lg',
|
||||||
|
on ? 'bg-primary/20 text-primary' : 'bg-muted/50 text-muted-foreground'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon className='h-4 w-4' />
|
||||||
|
</span>
|
||||||
|
<span className='min-w-0 flex-1'>
|
||||||
|
<span className='block truncate text-sm font-medium'>{r.name}</span>
|
||||||
|
<span className='text-muted-foreground block truncate text-[11px]'>
|
||||||
|
{resourceTypeLabel(r.resource_type)} · {r.provider}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
{on && <CheckCircle2 className='text-primary h-4 w-4 shrink-0' />}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Step 2: pick template */}
|
||||||
|
<section className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] bg-[color-mix(in_oklch,var(--card)_70%,transparent)] p-5'>
|
||||||
|
<h3 className='text-base font-semibold'>2. 选择 Agent 模板</h3>
|
||||||
|
<p className='text-muted-foreground mt-0.5 text-xs'>选择一个 Agent 的角色与能力。</p>
|
||||||
|
{templatesQ.isLoading ? (
|
||||||
|
<div className='mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-3'>
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<Skeleton key={i} className='h-20 rounded-xl' />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-3'>
|
||||||
|
{templates.map((tpl) => {
|
||||||
|
const on = selectedTemplate === tpl.template_id
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
key={tpl.template_id}
|
||||||
|
onClick={() => setSelectedTemplate(tpl.template_id)}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col gap-1 rounded-xl border p-4 text-left transition',
|
||||||
|
on
|
||||||
|
? 'border-primary/60 bg-[color-mix(in_oklch,var(--primary)_12%,transparent)]'
|
||||||
|
: 'border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] hover:border-primary/40'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className='flex items-center justify-between'>
|
||||||
|
<span className='text-sm font-semibold'>{tpl.name}</span>
|
||||||
|
{on && <CheckCircle2 className='text-primary h-4 w-4' />}
|
||||||
|
</span>
|
||||||
|
<span className='text-muted-foreground text-xs leading-relaxed'>
|
||||||
|
{tpl.description}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Deploy bar */}
|
||||||
|
<div className='flex items-center justify-between rounded-2xl border border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] bg-[color-mix(in_oklch,var(--card)_70%,transparent)] p-4'>
|
||||||
|
<p className='text-muted-foreground text-sm'>
|
||||||
|
已选 <span className='text-foreground font-semibold'>{selectedBindings.length}</span> 个资源
|
||||||
|
{selectedTemplate ? ',模板已选择' : ',未选择模板'}
|
||||||
|
</p>
|
||||||
|
<Button onClick={() => deploy.mutate()} disabled={!canDeploy} className='gap-1.5'>
|
||||||
|
<Rocket className='h-4 w-4' />
|
||||||
|
{deploy.isPending ? '部署中…' : '部署 Agent'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* My agents */}
|
||||||
|
<section className='rounded-2xl border border-[color-mix(in_oklch,var(--primary)_22%,var(--border))] bg-[color-mix(in_oklch,var(--card)_70%,transparent)] p-5'>
|
||||||
|
<div className='flex items-center justify-between'>
|
||||||
|
<h3 className='text-base font-semibold'>我的 Agent</h3>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
size='sm'
|
||||||
|
className='gap-1 text-xs'
|
||||||
|
onClick={() => void qc.invalidateQueries({ queryKey: ['heicode-agents'] })}
|
||||||
|
>
|
||||||
|
<RefreshCw className='h-3.5 w-3.5' /> 刷新
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{agentsQ.isLoading ? (
|
||||||
|
<div className='mt-4 space-y-3'>
|
||||||
|
{[0, 1].map((i) => (
|
||||||
|
<Skeleton key={i} className='h-16 rounded-xl' />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (agentsQ.data ?? []).length === 0 ? (
|
||||||
|
<p className='text-muted-foreground mt-4 rounded-xl border border-dashed p-6 text-center text-sm'>
|
||||||
|
还没有部署过 Agent。
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className='mt-4 space-y-3'>
|
||||||
|
{(agentsQ.data ?? []).map((a) => (
|
||||||
|
<AgentRow key={a.agent_id} agent={a} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function AgentRow({ agent }: { agent: AgentItem }) {
|
||||||
|
const qc = useQueryClient()
|
||||||
|
const st = statusLabel(agent.status)
|
||||||
|
|
||||||
|
const stop = useMutation({
|
||||||
|
mutationFn: () => api.post(`/api/heicode/agents/${agent.agent_id}/stop`),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('已停止')
|
||||||
|
void qc.invalidateQueries({ queryKey: ['heicode-agents'] })
|
||||||
|
},
|
||||||
|
onError: (e) => toast.error(e instanceof Error ? e.message : '操作失败'),
|
||||||
|
})
|
||||||
|
const del = useMutation({
|
||||||
|
mutationFn: () => api.delete(`/api/heicode/agents/${agent.agent_id}`),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('已删除')
|
||||||
|
void qc.invalidateQueries({ queryKey: ['heicode-agents'] })
|
||||||
|
},
|
||||||
|
onError: (e) => toast.error(e instanceof Error ? e.message : '操作失败'),
|
||||||
|
})
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
if (!agent.subdomain) return
|
||||||
|
void navigator.clipboard.writeText(agent.subdomain)
|
||||||
|
toast.success('已复制访问地址')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className='flex flex-col gap-2 rounded-xl border border-[color-mix(in_oklch,var(--primary)_18%,var(--border))] bg-[color-mix(in_oklch,var(--card)_55%,transparent)] p-4 sm:flex-row sm:items-center sm:justify-between'>
|
||||||
|
<div className='min-w-0'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<span className='truncate text-sm font-medium'>{agent.template_id}</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold ring-1 ring-inset',
|
||||||
|
st.cls
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{st.text}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className='text-muted-foreground mt-0.5 truncate font-mono text-[11px]'>
|
||||||
|
{agent.subdomain || '地址生成中…'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='flex shrink-0 items-center gap-1'>
|
||||||
|
<Button variant='outline' size='sm' className='h-8 gap-1 text-xs' onClick={copy} disabled={!agent.subdomain}>
|
||||||
|
<Copy className='h-3.5 w-3.5' /> 复制地址
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
size='sm'
|
||||||
|
className='h-8 gap-1 text-xs'
|
||||||
|
onClick={() => stop.mutate()}
|
||||||
|
disabled={stop.isPending}
|
||||||
|
>
|
||||||
|
<Square className='h-3.5 w-3.5' /> 停止
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
size='icon'
|
||||||
|
className='h-8 w-8 text-rose-400'
|
||||||
|
onClick={() => del.mutate()}
|
||||||
|
disabled={del.isPending}
|
||||||
|
title='删除'
|
||||||
|
>
|
||||||
|
<Trash2 className='h-3.5 w-3.5' />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
Bot,
|
||||||
Command,
|
Command,
|
||||||
Download,
|
Download,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
@@ -52,6 +53,11 @@ export function useSidebarData(): SidebarData {
|
|||||||
url: '/resources',
|
url: '/resources',
|
||||||
icon: GitBranch,
|
icon: GitBranch,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('Deploy sub agent'),
|
||||||
|
url: '/deploy-agent',
|
||||||
|
icon: Bot,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('Agent runs'),
|
title: t('Agent runs'),
|
||||||
url: '/deployments',
|
url: '/deployments',
|
||||||
|
|||||||
@@ -212,6 +212,7 @@
|
|||||||
"All Status": "所有状态",
|
"All Status": "所有状态",
|
||||||
"All statuses": "全部状态",
|
"All statuses": "全部状态",
|
||||||
"Agent runs": "运行总览",
|
"Agent runs": "运行总览",
|
||||||
|
"Deploy sub agent": "部署 Sub Agent",
|
||||||
"Resource binding": "资源绑定",
|
"Resource binding": "资源绑定",
|
||||||
"Bind your git repos / VMs / databases / blob. Credentials go straight to Azure Key Vault — only a secret_ref is stored.": "绑定你的 git 仓库 / 虚拟机 / 数据库 / 对象存储。凭据直接写入 Azure Key Vault,本地只保存 secret_ref。",
|
"Bind your git repos / VMs / databases / blob. Credentials go straight to Azure Key Vault — only a secret_ref is stored.": "绑定你的 git 仓库 / 虚拟机 / 数据库 / 对象存储。凭据直接写入 Azure Key Vault,本地只保存 secret_ref。",
|
||||||
"No resources bound yet": "还没有绑定任何资源",
|
"No resources bound yet": "还没有绑定任何资源",
|
||||||
|
|||||||
+22
@@ -46,6 +46,7 @@ import { Route as AuthenticatedKeysIndexRouteImport } from './routes/_authentica
|
|||||||
import { Route as AuthenticatedDevicesIndexRouteImport } from './routes/_authenticated/devices/index'
|
import { Route as AuthenticatedDevicesIndexRouteImport } from './routes/_authenticated/devices/index'
|
||||||
import { Route as AuthenticatedDesktopClientIndexRouteImport } from './routes/_authenticated/desktop-client/index'
|
import { Route as AuthenticatedDesktopClientIndexRouteImport } from './routes/_authenticated/desktop-client/index'
|
||||||
import { Route as AuthenticatedDeploymentsIndexRouteImport } from './routes/_authenticated/deployments/index'
|
import { Route as AuthenticatedDeploymentsIndexRouteImport } from './routes/_authenticated/deployments/index'
|
||||||
|
import { Route as AuthenticatedDeployAgentIndexRouteImport } from './routes/_authenticated/deploy-agent/index'
|
||||||
import { Route as AuthenticatedDashboardIndexRouteImport } from './routes/_authenticated/dashboard/index'
|
import { Route as AuthenticatedDashboardIndexRouteImport } from './routes/_authenticated/dashboard/index'
|
||||||
import { Route as AuthenticatedChannelsIndexRouteImport } from './routes/_authenticated/channels/index'
|
import { Route as AuthenticatedChannelsIndexRouteImport } from './routes/_authenticated/channels/index'
|
||||||
import { Route as AuthenticatedAvailableModelsIndexRouteImport } from './routes/_authenticated/available-models/index'
|
import { Route as AuthenticatedAvailableModelsIndexRouteImport } from './routes/_authenticated/available-models/index'
|
||||||
@@ -267,6 +268,12 @@ const AuthenticatedDeploymentsIndexRoute =
|
|||||||
path: '/deployments/',
|
path: '/deployments/',
|
||||||
getParentRoute: () => AuthenticatedRouteRoute,
|
getParentRoute: () => AuthenticatedRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AuthenticatedDeployAgentIndexRoute =
|
||||||
|
AuthenticatedDeployAgentIndexRouteImport.update({
|
||||||
|
id: '/deploy-agent/',
|
||||||
|
path: '/deploy-agent/',
|
||||||
|
getParentRoute: () => AuthenticatedRouteRoute,
|
||||||
|
} as any)
|
||||||
const AuthenticatedDashboardIndexRoute =
|
const AuthenticatedDashboardIndexRoute =
|
||||||
AuthenticatedDashboardIndexRouteImport.update({
|
AuthenticatedDashboardIndexRouteImport.update({
|
||||||
id: '/dashboard/',
|
id: '/dashboard/',
|
||||||
@@ -440,6 +447,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/available-models/': typeof AuthenticatedAvailableModelsIndexRoute
|
'/available-models/': typeof AuthenticatedAvailableModelsIndexRoute
|
||||||
'/channels/': typeof AuthenticatedChannelsIndexRoute
|
'/channels/': typeof AuthenticatedChannelsIndexRoute
|
||||||
'/dashboard/': typeof AuthenticatedDashboardIndexRoute
|
'/dashboard/': typeof AuthenticatedDashboardIndexRoute
|
||||||
|
'/deploy-agent/': typeof AuthenticatedDeployAgentIndexRoute
|
||||||
'/deployments/': typeof AuthenticatedDeploymentsIndexRoute
|
'/deployments/': typeof AuthenticatedDeploymentsIndexRoute
|
||||||
'/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute
|
'/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute
|
||||||
'/devices/': typeof AuthenticatedDevicesIndexRoute
|
'/devices/': typeof AuthenticatedDevicesIndexRoute
|
||||||
@@ -500,6 +508,7 @@ export interface FileRoutesByTo {
|
|||||||
'/available-models': typeof AuthenticatedAvailableModelsIndexRoute
|
'/available-models': typeof AuthenticatedAvailableModelsIndexRoute
|
||||||
'/channels': typeof AuthenticatedChannelsIndexRoute
|
'/channels': typeof AuthenticatedChannelsIndexRoute
|
||||||
'/dashboard': typeof AuthenticatedDashboardIndexRoute
|
'/dashboard': typeof AuthenticatedDashboardIndexRoute
|
||||||
|
'/deploy-agent': typeof AuthenticatedDeployAgentIndexRoute
|
||||||
'/deployments': typeof AuthenticatedDeploymentsIndexRoute
|
'/deployments': typeof AuthenticatedDeploymentsIndexRoute
|
||||||
'/desktop-client': typeof AuthenticatedDesktopClientIndexRoute
|
'/desktop-client': typeof AuthenticatedDesktopClientIndexRoute
|
||||||
'/devices': typeof AuthenticatedDevicesIndexRoute
|
'/devices': typeof AuthenticatedDevicesIndexRoute
|
||||||
@@ -564,6 +573,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticated/available-models/': typeof AuthenticatedAvailableModelsIndexRoute
|
'/_authenticated/available-models/': typeof AuthenticatedAvailableModelsIndexRoute
|
||||||
'/_authenticated/channels/': typeof AuthenticatedChannelsIndexRoute
|
'/_authenticated/channels/': typeof AuthenticatedChannelsIndexRoute
|
||||||
'/_authenticated/dashboard/': typeof AuthenticatedDashboardIndexRoute
|
'/_authenticated/dashboard/': typeof AuthenticatedDashboardIndexRoute
|
||||||
|
'/_authenticated/deploy-agent/': typeof AuthenticatedDeployAgentIndexRoute
|
||||||
'/_authenticated/deployments/': typeof AuthenticatedDeploymentsIndexRoute
|
'/_authenticated/deployments/': typeof AuthenticatedDeploymentsIndexRoute
|
||||||
'/_authenticated/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute
|
'/_authenticated/desktop-client/': typeof AuthenticatedDesktopClientIndexRoute
|
||||||
'/_authenticated/devices/': typeof AuthenticatedDevicesIndexRoute
|
'/_authenticated/devices/': typeof AuthenticatedDevicesIndexRoute
|
||||||
@@ -627,6 +637,7 @@ export interface FileRouteTypes {
|
|||||||
| '/available-models/'
|
| '/available-models/'
|
||||||
| '/channels/'
|
| '/channels/'
|
||||||
| '/dashboard/'
|
| '/dashboard/'
|
||||||
|
| '/deploy-agent/'
|
||||||
| '/deployments/'
|
| '/deployments/'
|
||||||
| '/desktop-client/'
|
| '/desktop-client/'
|
||||||
| '/devices/'
|
| '/devices/'
|
||||||
@@ -687,6 +698,7 @@ export interface FileRouteTypes {
|
|||||||
| '/available-models'
|
| '/available-models'
|
||||||
| '/channels'
|
| '/channels'
|
||||||
| '/dashboard'
|
| '/dashboard'
|
||||||
|
| '/deploy-agent'
|
||||||
| '/deployments'
|
| '/deployments'
|
||||||
| '/desktop-client'
|
| '/desktop-client'
|
||||||
| '/devices'
|
| '/devices'
|
||||||
@@ -750,6 +762,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticated/available-models/'
|
| '/_authenticated/available-models/'
|
||||||
| '/_authenticated/channels/'
|
| '/_authenticated/channels/'
|
||||||
| '/_authenticated/dashboard/'
|
| '/_authenticated/dashboard/'
|
||||||
|
| '/_authenticated/deploy-agent/'
|
||||||
| '/_authenticated/deployments/'
|
| '/_authenticated/deployments/'
|
||||||
| '/_authenticated/desktop-client/'
|
| '/_authenticated/desktop-client/'
|
||||||
| '/_authenticated/devices/'
|
| '/_authenticated/devices/'
|
||||||
@@ -1060,6 +1073,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AuthenticatedDeploymentsIndexRouteImport
|
preLoaderRoute: typeof AuthenticatedDeploymentsIndexRouteImport
|
||||||
parentRoute: typeof AuthenticatedRouteRoute
|
parentRoute: typeof AuthenticatedRouteRoute
|
||||||
}
|
}
|
||||||
|
'/_authenticated/deploy-agent/': {
|
||||||
|
id: '/_authenticated/deploy-agent/'
|
||||||
|
path: '/deploy-agent'
|
||||||
|
fullPath: '/deploy-agent/'
|
||||||
|
preLoaderRoute: typeof AuthenticatedDeployAgentIndexRouteImport
|
||||||
|
parentRoute: typeof AuthenticatedRouteRoute
|
||||||
|
}
|
||||||
'/_authenticated/dashboard/': {
|
'/_authenticated/dashboard/': {
|
||||||
id: '/_authenticated/dashboard/'
|
id: '/_authenticated/dashboard/'
|
||||||
path: '/dashboard'
|
path: '/dashboard'
|
||||||
@@ -1324,6 +1344,7 @@ interface AuthenticatedRouteRouteChildren {
|
|||||||
AuthenticatedAvailableModelsIndexRoute: typeof AuthenticatedAvailableModelsIndexRoute
|
AuthenticatedAvailableModelsIndexRoute: typeof AuthenticatedAvailableModelsIndexRoute
|
||||||
AuthenticatedChannelsIndexRoute: typeof AuthenticatedChannelsIndexRoute
|
AuthenticatedChannelsIndexRoute: typeof AuthenticatedChannelsIndexRoute
|
||||||
AuthenticatedDashboardIndexRoute: typeof AuthenticatedDashboardIndexRoute
|
AuthenticatedDashboardIndexRoute: typeof AuthenticatedDashboardIndexRoute
|
||||||
|
AuthenticatedDeployAgentIndexRoute: typeof AuthenticatedDeployAgentIndexRoute
|
||||||
AuthenticatedDeploymentsIndexRoute: typeof AuthenticatedDeploymentsIndexRoute
|
AuthenticatedDeploymentsIndexRoute: typeof AuthenticatedDeploymentsIndexRoute
|
||||||
AuthenticatedDesktopClientIndexRoute: typeof AuthenticatedDesktopClientIndexRoute
|
AuthenticatedDesktopClientIndexRoute: typeof AuthenticatedDesktopClientIndexRoute
|
||||||
AuthenticatedDevicesIndexRoute: typeof AuthenticatedDevicesIndexRoute
|
AuthenticatedDevicesIndexRoute: typeof AuthenticatedDevicesIndexRoute
|
||||||
@@ -1353,6 +1374,7 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
|||||||
AuthenticatedAvailableModelsIndexRoute,
|
AuthenticatedAvailableModelsIndexRoute,
|
||||||
AuthenticatedChannelsIndexRoute: AuthenticatedChannelsIndexRoute,
|
AuthenticatedChannelsIndexRoute: AuthenticatedChannelsIndexRoute,
|
||||||
AuthenticatedDashboardIndexRoute: AuthenticatedDashboardIndexRoute,
|
AuthenticatedDashboardIndexRoute: AuthenticatedDashboardIndexRoute,
|
||||||
|
AuthenticatedDeployAgentIndexRoute: AuthenticatedDeployAgentIndexRoute,
|
||||||
AuthenticatedDeploymentsIndexRoute: AuthenticatedDeploymentsIndexRoute,
|
AuthenticatedDeploymentsIndexRoute: AuthenticatedDeploymentsIndexRoute,
|
||||||
AuthenticatedDesktopClientIndexRoute: AuthenticatedDesktopClientIndexRoute,
|
AuthenticatedDesktopClientIndexRoute: AuthenticatedDesktopClientIndexRoute,
|
||||||
AuthenticatedDevicesIndexRoute: AuthenticatedDevicesIndexRoute,
|
AuthenticatedDevicesIndexRoute: AuthenticatedDevicesIndexRoute,
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { DeploySubAgentPage } from '@/features/deploy-agent/deploy-agent-page'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_authenticated/deploy-agent/')({
|
||||||
|
component: DeploySubAgentPage,
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user