diff --git a/src/pages/repos/modules/WorkspaceDetailDialog.tsx b/src/pages/repos/modules/WorkspaceDetailDialog.tsx index e90ea71..6da64dd 100644 --- a/src/pages/repos/modules/WorkspaceDetailDialog.tsx +++ b/src/pages/repos/modules/WorkspaceDetailDialog.tsx @@ -1,10 +1,15 @@ import { Dialog, DialogContent, - DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' +import { + Tooltip, + TooltipContent, + TooltipTrigger, + TooltipProvider +} from '@/components/ui/tooltip' import { useRepoStore } from '../store' import type { WorkspaceOpen } from '../store' import { @@ -19,11 +24,13 @@ import { Zap, Copy, Check, - Square + Square, + Link } from 'lucide-react' -import { useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import { toast } from 'sonner' import { useShallow } from 'zustand/shallow' +import clsx from 'clsx' type LinkItemKey = keyof WorkspaceOpen; interface LinkItem { @@ -35,7 +42,6 @@ interface LinkItem { } const LinkItem = ({ label, icon, url }: { label: string; icon: React.ReactNode; url?: string }) => { - const [isHovered, setIsHovered] = useState(false) const [isCopied, setIsCopied] = useState(false) const handleClick = () => { @@ -43,7 +49,7 @@ const LinkItem = ({ label, icon, url }: { label: string; icon: React.ReactNode; copy() return; } - if (url) { + if (url && url.startsWith('http')) { window.open(url, '_blank') } } @@ -57,41 +63,38 @@ const LinkItem = ({ label, icon, url }: { label: string; icon: React.ReactNode; toast.error('复制失败') } } - const handleCopy = async (e: React.MouseEvent) => { - e.stopPropagation() - if (!url) return - copy() - } return ( - + + + +
+
+ {icon} +
+ {label} + {url && ( +
{ + e.stopPropagation() + copy() + }} + role="button" + className="w-6 h-6 flex items-center justify-center text-neutral-500 hover:text-neutral-900 hover:bg-neutral-100 rounded transition-colors cursor-pointer" + > + {isCopied ? : } +
+ )} +
+
+ +

{url}

+
+
+
) } @@ -124,6 +127,34 @@ const DevTabContent = ({ linkItems, workspaceLink, stopWorkspace }: { ) } +// Link tab 内容(暂留空) +const LinkTabContent = () => { + const store = useRepoStore(useShallow((state) => ({ + selectWorkspace: state.selectWorkspace, + workspaceSecretLink: state.workspaceSecretLink, + }))) + const links = store.workspaceSecretLink.map(item => ({ + label: item.title, + url: item.value + })) + if (links.length === 0) { + return ( +
+ 暂无链接, 或工作区未启动 +
+ ) + } + return ( +
+
+ {links.map(link => ( + } url={link.url} /> + ))} +
+
+ ) +} + // Work tab 内容(暂留,需要根据 business_id 做事情) const WorkTabContent = () => { const store = useRepoStore(useShallow((state) => ({ @@ -178,7 +209,7 @@ const WorkTabContent = () => { } export function WorkspaceDetailDialog() { - const { showWorkspaceDialog, setShowWorkspaceDialog, workspaceLink, stopWorkspace, workspaceTab, setWorkspaceTab, selectWorkspace } = useRepoStore(useShallow((state) => ({ + const { showWorkspaceDialog, setShowWorkspaceDialog, workspaceLink, stopWorkspace, workspaceTab, setWorkspaceTab, getWorkspaceSecretLink, selectWorkspace, workspaceSecretLink } = useRepoStore(useShallow((state) => ({ showWorkspaceDialog: state.showWorkspaceDialog, setShowWorkspaceDialog: state.setShowWorkspaceDialog, workspaceLink: state.workspaceLink, @@ -186,6 +217,8 @@ export function WorkspaceDetailDialog() { workspaceTab: state.workspaceTab, setWorkspaceTab: state.setWorkspaceTab, selectWorkspace: state.selectWorkspace, + getWorkspaceSecretLink: state.getWorkspaceSecretLink, + workspaceSecretLink: state.workspaceSecretLink }))) const linkItems: LinkItem[] = [ { @@ -252,6 +285,11 @@ export function WorkspaceDetailDialog() { getUrl: (data) => data.codebuddycn }, ].sort((a, b) => (a.order || 0) - (b.order || 0)) + useEffect(() => { + if (selectWorkspace) { + getWorkspaceSecretLink(selectWorkspace) + } + }, [selectWorkspace]) return ( @@ -284,12 +322,29 @@ export function WorkspaceDetailDialog() {
)} +
{/* Tab 内容 */}
- {workspaceTab === 'dev' ? ( + {workspaceTab === 'dev' && ( - ) : ( + )} + {workspaceTab === 'link' && ( + + )} + {workspaceTab === 'work' && ( )}
diff --git a/src/pages/repos/store/index.ts b/src/pages/repos/store/index.ts index acc6a14..e9190c7 100644 --- a/src/pages/repos/store/index.ts +++ b/src/pages/repos/store/index.ts @@ -5,6 +5,7 @@ import { queryApi as cnbApi } from '@/modules/cnb-api' import { WorkspaceInfo } from '@kevisual/cnb' import { createBuildConfig, createCommitBlankConfig, createDevConfig } from './build'; import { useLayoutStore } from '@/pages/auth/store'; +import { Query } from '@kevisual/query'; interface DisplayModule { activity: boolean; contributors: boolean; @@ -51,7 +52,7 @@ interface Data { pinned_time: string; } -type WorkspaceTabType = 'dev' | 'work' +type WorkspaceTabType = 'dev' | 'work' | 'link' type BuildConfig = { repo: string; @@ -101,6 +102,8 @@ type State = { deleteBuildConfig: (params: { repo: Data, user?: any }) => Promise; initBuildConfig: (params: { repo: Data, user?: any }) => Promise; buildWorkspace: () => Promise; + workspaceSecretLink: { title: string, key: string, value?: string }[]; + getWorkspaceSecretLink: (workspace: WorkspaceInfo) => Promise; } export const useRepoStore = create((set, get) => { @@ -248,6 +251,50 @@ export const useRepoStore = create((set, get) => { toast.error(res.message || '构建触发失败') } }, + workspaceSecretLink: [], + getWorkspaceSecretLink: async (workspace) => { + console.log('获取工作区链接', workspace) + const business_id = workspace?.business_id; + const baseURL = `https://${business_id}-51515.cnb.run/client/router`; + console.log('工作区链接', baseURL) + const url = new URL(baseURL); + const token = localStorage.getItem('token'); + // const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZC1rZXktMSJ9.eyJzdWIiOiJ1c2VyOjBlNzAwZGM4LTkwZGQtNDFiNy05MWRkLTMzNmVhNTFkZTNkMiIsIm5hbWUiOiJyb290IiwiZXhwIjoxNzczMTI4NTMwLCJpc3MiOiJodHRwczovL2NvbnZleC5rZXZpc3VhbC5jbiIsImlhdCI6MTc3MzEyMTMzMCwiYXVkIjoiY29udmV4LWFwcCJ9.g4kANiPc352QFBfa0yb4gl98mLHTruL_3HvIaKYwN1Qy3-P8QV6X_WhqgMOskQphNGsBFC-LRmZq2808GnqwpjDTE0ekXbsO4L9C-D6F3mBMwowqpvmURCRVg6Ys6LSkzw4sM75VbHpfFX3ZQVtZymvAWhxxxvjhdKGPdrdw5bNymTbCw-Y9NrYW6u2mExLrvrfXl3vJqaCz7obj_mR-G_2PB3g5KPQYhWCl8--TkYOS9fiNIYlcacnO36bZXhHheHFZEr_gb8UG5ECg0ND8hsH8TijiYBAY6T93nhGrZG7E0oQY3xXsVm-mkvXP2tLCXwKH7SFmH4M0tdZLRqLqKw' + url.searchParams.set('path', 'cnb_board'); + url.searchParams.set('key', 'live'); + const res = await fetch(url.toString(), { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + } + }).then(res => res.json()); + const labelData: { title: string, key: string, value?: string }[] = [ + { + title: 'Opencode Secret', + key: 'opencodeUrlSecret', + }, + { + title: 'Openclaw Secret', + key: 'openclawUrlSecret', + }, + { + title: 'StartTime', + key: 'buildStartTime', + } + ]; + if (res.code === 200) { + const list = res.data?.list || []; + const workspaceSecretLink: { title: string, key: string, value?: string }[] = []; + labelData.forEach(item => { + const find = list.find((l: any) => l.key === item.key); + if (find) { + workspaceSecretLink.push({ ...item, value: find.value }); + } + }) + set({ workspaceSecretLink }) + } + }, getItem: async (repo: string) => { const { setLoading } = get(); setLoading(true);