Auto commit: 2026-03-27 13:04

This commit is contained in:
xiongxiao
2026-03-27 13:04:47 +08:00
committed by cnb
parent 8984913fcd
commit 704e0d71dd
5 changed files with 229 additions and 18 deletions

View File

@@ -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 (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle> Docker </DialogTitle>
</DialogHeader>
<div className="space-y-2">
<label className="text-sm font-medium">Repo</label>
<Input
placeholder="kevisual/dev-env"
value={repo}
onChange={(e) => setRepo(e.target.value)}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">Docker</label>
<Input
placeholder="redis:latest"
value={dockerImage}
onChange={(e) => setDockerImage(e.target.value)}
/>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button onClick={handleConfirm}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -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<MarkItem | null>(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 = () => {
</div>
)}
<h2 className="text-xl font-semibold mb-4"></h2>
<div className="flex items-center gap-3 mb-4">
<h2 className="text-xl font-semibold"></h2>
<div className="flex items-center gap-1">
<input
type="text"
placeholder="搜索制品..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="h-8 w-48 px-3 text-sm border rounded-md bg-background"
/>
<Tooltip>
<TooltipTrigger
render={
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 cursor-pointer"
onClick={() => slug && packageStore.getPackagesList({ slug })}
>
<RefreshCw className="w-4 h-4" />
</Button>
}
/>
<TooltipContent></TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger
render={
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 cursor-pointer"
onClick={() => setAddDockerOpen(true)}
>
<Plus className="w-4 h-4" />
</Button>
}
/>
<TooltipContent></TooltipContent>
</Tooltip>
</div>
</div>
<AddDockerDialog open={addDockerOpen} onOpenChange={setAddDockerOpen} slug={slug || ""} />
{packageStore.packagesListLoading ? (
<div className="text-center py-10 text-muted-foreground">...</div>
) : packageStore.packagesList.length === 0 ? (
<div className="text-center py-10 text-muted-foreground"></div>
) : filteredPackagesList.length === 0 ? (
<div className="text-center py-10 text-muted-foreground">
{searchQuery ? "未找到匹配的制品" : "暂无制品数据"}
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{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 (
<Card key={item.package}>
@@ -185,10 +247,20 @@ export const App = () => {
<Copy className="w-4 h-4 mr-2" />
docker registry
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
const url = `https://cnb.cool/${slug}/-/packages/${item.package_type}/${item.package}`;
window.open(url, '_blank');
}}
className="cursor-pointer"
>
<ExternalLink className="w-4 h-4 mr-2" />
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
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"