mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-16 17:13:46 -05:00
feat: show which containers use a specific image
This commit is contained in:
@@ -123,7 +123,7 @@ function App() {
|
||||
<Server name={server} key={server}>
|
||||
{images
|
||||
.filter((image) =>
|
||||
filters.onlyInUse ? !!image.in_use : true,
|
||||
filters.onlyInUse ? image.used_by.length > 0 : true,
|
||||
)
|
||||
.filter((image) =>
|
||||
filters.registries.length == 0
|
||||
|
||||
@@ -4,6 +4,9 @@ import {
|
||||
DialogBackdrop,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
Disclosure,
|
||||
DisclosureButton,
|
||||
DisclosurePanel,
|
||||
} from "@headlessui/react";
|
||||
import { WithTooltip } from "./ui/Tooltip";
|
||||
import type { Image } from "../types";
|
||||
@@ -11,15 +14,17 @@ import { theme } from "../theme";
|
||||
import { CodeBlock } from "./CodeBlock";
|
||||
import {
|
||||
Box,
|
||||
ChevronDown,
|
||||
CircleArrowUp,
|
||||
CircleCheck,
|
||||
Container,
|
||||
HelpCircle,
|
||||
Timer,
|
||||
TriangleAlert,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import Badge from "./Badge";
|
||||
import { getDescription } from "../utils";
|
||||
import { cn, getDescription, truncateArray } from "../utils";
|
||||
|
||||
const clickable_registries = [
|
||||
"registry-1.docker.io",
|
||||
@@ -30,12 +35,16 @@ const clickable_registries = [
|
||||
|
||||
export default function Image({ data }: { data: Image }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [showUsedBy, setShowUsedBy] = useState(false);
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
const toggleShowUsedBy = () => {
|
||||
setShowUsedBy(!showUsedBy)
|
||||
}
|
||||
const new_reference =
|
||||
data.result.info?.type == "version"
|
||||
? data.reference.split(":")[0] + ":" + data.result.info.new_tag
|
||||
@@ -140,6 +149,21 @@ export default function Image({ data }: { data: Image }) {
|
||||
Checked in <b>{data.time}</b> ms
|
||||
</span>
|
||||
</div>
|
||||
{data.used_by.length !== 0 && (
|
||||
<div className="flex items-start gap-3">
|
||||
<Container className="size-6 shrink-0 text-gray-500" />
|
||||
<div className="flex items-start gap-1">
|
||||
<span className="shrink-0">Used by</span>
|
||||
{data.used_by.length > 1 ? (
|
||||
<button onClick={toggleShowUsedBy} className={cn("flex gap-x-2 flex-wrap font-bold", !showUsedBy && "underline")}>
|
||||
{showUsedBy ? data.used_by.map((container) => <><pre>{container}</pre></>) : truncateArray(data.used_by)}
|
||||
</button>
|
||||
) : (
|
||||
data.used_by[0]
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{data.result.error && (
|
||||
<div className="break-before mt-4 flex items-center gap-3 overflow-hidden rounded-md bg-yellow-400/10 px-3 py-2">
|
||||
<TriangleAlert className="size-6 shrink-0 text-yellow-500" />
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "@headlessui/react";
|
||||
import { ChevronDown, Check } from "lucide-react";
|
||||
import { theme } from "../../theme";
|
||||
import { cn } from "../../utils";
|
||||
import { cn, truncateArray } from "../../utils";
|
||||
import { Server } from "lucide-react";
|
||||
|
||||
export default function Select({
|
||||
@@ -27,7 +27,7 @@ export default function Select({
|
||||
<div className="relative">
|
||||
<ListboxButton
|
||||
className={cn(
|
||||
`flex overflow-x-hidden w-full gap-2 rounded-md bg-${theme}-100 dark:bg-${theme}-900 border border-${theme}-200 dark:border-${theme}-700 group relative items-center py-1.5 pl-3 pr-2 text-left transition-colors duration-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-500 sm:text-sm/6`,
|
||||
`flex w-full gap-2 overflow-x-hidden rounded-md bg-${theme}-100 dark:bg-${theme}-900 border border-${theme}-200 dark:border-${theme}-700 group relative items-center py-1.5 pl-3 pr-2 text-left transition-colors duration-200 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-500 sm:text-sm/6`,
|
||||
selectedItems.length == 0
|
||||
? `text-${theme}-600 dark:text-${theme}-400 hover:text-black hover:dark:text-white`
|
||||
: "text-black dark:text-white",
|
||||
@@ -44,15 +44,14 @@ export default function Select({
|
||||
/>
|
||||
)}
|
||||
<span className="truncate">
|
||||
{selectedItems.length == 0
|
||||
? placeholder
|
||||
: selectedItems.length == 1
|
||||
? selectedItems[0]
|
||||
: `${selectedItems[0]} +${(selectedItems.length - 1).toString()} more`}</span>
|
||||
{selectedItems.length == 0
|
||||
? placeholder
|
||||
: truncateArray(selectedItems)}
|
||||
</span>
|
||||
|
||||
<ChevronDown
|
||||
aria-hidden="true"
|
||||
className={`size-5 shrink-0 ml-auto self-center text-${theme}-600 dark:text-${theme}-400 transition-colors duration-200 group-hover:text-black sm:size-4 group-hover:dark:text-white`}
|
||||
className={`ml-auto size-5 shrink-0 self-center text-${theme}-600 dark:text-${theme}-400 transition-colors duration-200 group-hover:text-black sm:size-4 group-hover:dark:text-white`}
|
||||
/>
|
||||
<div
|
||||
className="absolute -bottom-px left-1/2 h-full w-0 -translate-x-1/2 rounded-md border-b-2 border-b-blue-600 transition-all duration-200 group-data-[open]:w-[calc(100%+2px)]"
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface Image {
|
||||
};
|
||||
time: number;
|
||||
server: string | null;
|
||||
in_use: boolean | null;
|
||||
used_by: string[];
|
||||
}
|
||||
|
||||
interface VersionInfo {
|
||||
|
||||
@@ -28,3 +28,11 @@ export function getDescription(image: Image) {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
export function truncateArray(arr: string[]) {
|
||||
if (arr.length > 1) {
|
||||
return `${arr[0]} +${(arr.length - 1).toString()} more`
|
||||
} else if (arr.length == 1) {
|
||||
return arr[0]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user