From 704e0d71dd8d5daf478c226e9f701147c4a9ff6a Mon Sep 17 00:00:00 2001 From: xiongxiao Date: Fri, 27 Mar 2026 13:04:47 +0800 Subject: [PATCH] Auto commit: 2026-03-27 13:04 --- .cnb/docker_sync.yml | 13 +++ .../detail/components/AddDockerDialog.tsx | 70 ++++++++++++ src/pages/cnb-packages/detail/page.tsx | 100 +++++++++++++++--- src/pages/cnb-packages/store/index.ts | 26 ++++- src/pages/repos/store/build.ts | 38 +++++++ 5 files changed, 229 insertions(+), 18 deletions(-) create mode 100644 .cnb/docker_sync.yml create mode 100644 src/pages/cnb-packages/detail/components/AddDockerDialog.tsx diff --git a/.cnb/docker_sync.yml b/.cnb/docker_sync.yml new file mode 100644 index 0000000..aa96682 --- /dev/null +++ b/.cnb/docker_sync.yml @@ -0,0 +1,13 @@ + +$: + api_trigger_event: + - docker: + image: docker.cnb.cool/kevisual/dev-env:latest + services: + - docker + stages: + - name: '执行同步脚本' + run: | + docker pull redis:latest + docker tag redis:latest docker.cnb.cool/kevisual/dev-env/redis:latest + docker push docker.cnb.cool/kevisual/dev-env/redis:latest \ No newline at end of file diff --git a/src/pages/cnb-packages/detail/components/AddDockerDialog.tsx b/src/pages/cnb-packages/detail/components/AddDockerDialog.tsx new file mode 100644 index 0000000..11c9c20 --- /dev/null +++ b/src/pages/cnb-packages/detail/components/AddDockerDialog.tsx @@ -0,0 +1,70 @@ +import { useState, useEffect } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { createDockerBuildConfig } from "@/pages/repos/store/build"; +import { usePackageStore } from "../../store"; +import { useShallow } from "zustand/shallow"; + +interface AddDockerDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + slug: string; +} + +export function AddDockerDialog({ open, onOpenChange, slug }: AddDockerDialogProps) { + const [repo, setRepo] = useState(() => { + return slug; + }); + const [dockerImage, setDockerImage] = useState(() => { + return localStorage.getItem("add-docker-image") || ""; + }); + useEffect(() => { + localStorage.setItem("add-docker-image", dockerImage); + }, [dockerImage]); + const cnbPackageSore = usePackageStore(useShallow((state) => ({ repo: state.repo, dockerBuild: state.dockerBuild }))); + const handleConfirm = async () => { + console.log("repo:", repo, "docker image:", dockerImage); + onOpenChange(false); + const config = createDockerBuildConfig({ repo, image: dockerImage }); + await cnbPackageSore.dockerBuild({ repo, config }); + }; + + return ( + + + + 添加 Docker 镜像 + +
+ + setRepo(e.target.value)} + /> +
+
+ + setDockerImage(e.target.value)} + /> +
+ + + + +
+
+ ); +} \ No newline at end of file diff --git a/src/pages/cnb-packages/detail/page.tsx b/src/pages/cnb-packages/detail/page.tsx index feb020b..15f5c06 100644 --- a/src/pages/cnb-packages/detail/page.tsx +++ b/src/pages/cnb-packages/detail/page.tsx @@ -2,7 +2,7 @@ import { useSearch } from "@tanstack/react-router"; import { MarkItem, usePackageStore } from "../store"; import { PackageItem } from "../store/package-type"; import { useShallow } from "zustand/shallow"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useMemo } from "react"; import dayjs from "dayjs"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; @@ -15,7 +15,7 @@ import { CardAction, } from "@/components/ui/card"; import { SidebarLayout } from "@/pages/sidebar/components"; -import { ArrowLeftIcon, Copy, MoreVertical, Info, ExternalLink } from "lucide-react"; +import { ArrowLeftIcon, Copy, MoreVertical, Info, ExternalLink, Plus, RefreshCw } from "lucide-react"; import { toast } from "sonner"; import { DropdownMenu, @@ -24,6 +24,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { AddDockerDialog } from "./components/AddDockerDialog"; export const App = () => { const searchParams = useSearch({ strict: false }) as { slug?: string; id?: string }; @@ -38,6 +39,19 @@ export const App = () => { const [detailItem, setDetailItem] = useState(null); const [detailLoading, setDetailLoading] = useState(false); + const [addDockerOpen, setAddDockerOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); + + const filteredPackagesList = useMemo(() => { + if (!searchQuery.trim()) return packageStore.packagesList; + const query = searchQuery.toLowerCase(); + return packageStore.packagesList.filter( + (item: PackageItem) => + item.package?.toLowerCase().includes(query) || + item.description?.toLowerCase().includes(query) || + item.labels?.some((label: string) => label.toLowerCase().includes(query)) + ); + }, [packageStore.packagesList, searchQuery]); useEffect(() => { if (slug) { @@ -54,13 +68,16 @@ export const App = () => { }); } }, [id]); + const getGroup = (slug: string | undefined): string => { + if (!slug) return ""; + const parts = slug.split("/").filter(Boolean); + if (parts.length <= 2) return parts[0]; + return parts.slice(0, 2).join("/"); + }; const onCopy = (data: PackageItem) => { - // kevisual/dev-env 取 kevisual,删除最后一个斜杠和后面的内容 - const _group = slug?.split?.('/')?.filter(Boolean); - _group?.shift?.(); - const group = _group?.[0]; - const value = `docker.cnb.cool/${slug}/${data.package}:latest` - } + const group = getGroup(slug); + const value = `docker.cnb.cool/${group}/${data.package}:latest`; + }; const onDetail = (item: PackageItem) => { } @@ -108,15 +125,60 @@ export const App = () => { )} -

制品列表

+
+

制品列表

+
+ setSearchQuery(e.target.value)} + className="h-8 w-48 px-3 text-sm border rounded-md bg-background" + /> + + slug && packageStore.getPackagesList({ slug })} + > + + + } + /> + 刷新 + + + setAddDockerOpen(true)} + > + + + } + /> + 添加 + +
+
+ {packageStore.packagesListLoading ? (
加载中...
- ) : packageStore.packagesList.length === 0 ? ( -
暂无制品数据
+ ) : filteredPackagesList.length === 0 ? ( +
+ {searchQuery ? "未找到匹配的制品" : "暂无制品数据"} +
) : (
- {packageStore.packagesList.map((item: PackageItem) => { - const dockerValue = `docker.cnb.cool/${slug}/${item.package}:latest`; + {filteredPackagesList.map((item: PackageItem) => { + const group = getGroup(slug); + const dockerValue = `docker.cnb.cool/${group}/${item.package}:latest`; const dockerPullValue = `docker pull ${dockerValue}`; return ( @@ -185,10 +247,20 @@ export const App = () => { 复制 docker registry + { + const url = `https://cnb.cool/${slug}/-/packages/${item.package_type}/${item.package}`; + window.open(url, '_blank'); + }} + className="cursor-pointer" + > + + 跳转详情包 + { const type = 'docker'; - const url = `https://cnb.cool/${slug}/-/packages?type=${type}&ordering=last_push_at`; + const url = `https://cnb.cool/${slug}/-/packages?type=${type}&ordering=last_push_at&search=${item.package}`; window.open(url, '_blank'); }} className="cursor-pointer" diff --git a/src/pages/cnb-packages/store/index.ts b/src/pages/cnb-packages/store/index.ts index 5c30379..0ae2b2b 100644 --- a/src/pages/cnb-packages/store/index.ts +++ b/src/pages/cnb-packages/store/index.ts @@ -1,6 +1,7 @@ import { create } from 'zustand'; import { queryApi as markApi } from '@/modules/mark-api'; -import { queryApi as cnbApi } from '@/modules/package-api'; +import { queryApi as packageApi } from '@/modules/package-api'; +import { queryApi as cnbApi } from '@/modules/cnb-api'; import { toast } from 'sonner'; import { PackageItem } from './package-type'; @@ -23,6 +24,7 @@ type PackageState = { setLoading: (loading: boolean) => void; // CNB packages list packagesList: PackageItem[]; + repo: string; packagesListLoading: boolean; getPackagesList: (params: { slug: string, type?: string, ordering?: string, name?: string, page?: number, pageSize?: number }) => Promise; // Dialog states @@ -38,6 +40,7 @@ type PackageState = { updateItem: (id: string, data: { title?: string, tags?: string[], link?: string, summary?: string, description?: string }) => Promise; deleteItem: (id: string) => Promise; getItem: (id: string) => Promise; + dockerBuild: (config: { repo: string, config: string }) => Promise; } export type { PackageState, PackageItem }; @@ -50,11 +53,12 @@ export const usePackageStore = create((set, get) => ({ setLoading: (loading) => set({ loading }), packagesList: [], packagesListLoading: false, + repo: '', getPackagesList: async (params: { slug: string, type?: string, ordering?: string, name?: string, page?: number, pageSize?: number }) => { - const { slug, type = 'all', ordering, name, page = 1, pageSize = 20 } = params; - set({ packagesListLoading: true }); + const { slug, type = 'all', ordering, name, page = 1, pageSize = 99 } = params; + set({ packagesListLoading: true, repo: slug }); try { - const res = await cnbApi.cnb['list-packages']({ + const res = await packageApi.cnb['list-packages']({ slug, type, ordering, @@ -185,5 +189,19 @@ export const usePackageStore = create((set, get) => ({ console.error('获取详情失败', e); return null; } + }, + dockerBuild: async (config) => { + const res = await cnbApi.cnb['cloud-build']({ + repo: config.repo, + branch: 'main', + env: {} as any, + event: 'api_trigger_event', + config: config.config, + }) + if (res.code === 200) { + toast.success('构建已触发') + } else { + toast.error(res.message || '构建触发失败') + } } })); diff --git a/src/pages/repos/store/build.ts b/src/pages/repos/store/build.ts index e755685..bdab99b 100644 --- a/src/pages/repos/store/build.ts +++ b/src/pages/repos/store/build.ts @@ -97,4 +97,42 @@ ${branch}: # - name: 结束阶段 # script: zsh -i -c 'bun run end' ` +} + +export const createDockerBuildConfig = (params: { + repo: string, + // 参考 redis:latest 这种格式的镜像名称,必须包含冒号和标签,如果没有标签则默认为 latest + image: string +}) => { + const toRepo = params.repo!; + let image = params.image!; + const tagIndex = image.lastIndexOf(':'); + if (tagIndex === -1) { + image = `${image}:latest`; + } + const tagVersion = image.split(':').pop()!; + const imageLastPart = image.split('/').pop()!; + const imageNameWithoutTag = imageLastPart.split(':')[0]; + const pullCmd = `docker pull ${image}`; + const tagCmd = `docker tag ${image} docker.cnb.cool/${toRepo}/${imageLastPart}`; + const pushCmd = `docker push docker.cnb.cool/${toRepo}/${imageLastPart}`; + let pushLatestCmd = 'echo "不需要推送 latest 标签"'; + if (tagVersion !== 'latest') { + pushLatestCmd = `docker tag ${image} docker.cnb.cool/${toRepo}/${imageNameWithoutTag}:latest && docker push docker.cnb.cool/${toRepo}/${imageNameWithoutTag}:latest`; + } + return ` +$: + api_trigger_event: + - docker: + image: cnbcool/default-dev-env:latest + services: + - docker + stages: + - name: '执行同步脚本' + script: | + ${pullCmd} + ${tagCmd} + ${pushCmd} + ${pushLatestCmd} +` } \ No newline at end of file