mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-17 09:33:38 -05:00
Fixed CSS bugs, formatted code
This commit is contained in:
@@ -15,41 +15,41 @@ function App() {
|
||||
if (!data) return <Loading onLoad={setData} />;
|
||||
return (
|
||||
<div
|
||||
className={`flex justify-center min-h-screen bg-${theme}-50 dark:bg-${theme}-950`}
|
||||
className={`flex min-h-screen justify-center bg-${theme}-50 dark:bg-${theme}-950`}
|
||||
>
|
||||
<div className="lg:px-8 sm:px-6 px-4 max-w-[80rem] mx-auto h-full w-full">
|
||||
<div className="flex flex-col max-w-[48rem] mx-auto h-full my-8">
|
||||
<div className="mx-auto h-full w-full max-w-[80rem] px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto my-8 flex h-full max-w-[48rem] flex-col">
|
||||
<div className="flex items-center gap-1">
|
||||
<h1 className="text-5xl lg:text-6xl font-bold dark:text-white">
|
||||
<h1 className="text-5xl font-bold lg:text-6xl dark:text-white">
|
||||
Cup
|
||||
</h1>
|
||||
<Logo />
|
||||
</div>
|
||||
<div
|
||||
className={`shadow-sm bg-white dark:bg-${theme}-900 rounded-md my-8`}
|
||||
className={`bg-white shadow-sm dark:bg-${theme}-900 my-8 rounded-md`}
|
||||
>
|
||||
<dl className="lg:grid-cols-4 md:grid-cols-2 gap-1 grid-cols-1 grid overflow-hidden *:relative">
|
||||
<dl className="grid grid-cols-1 gap-1 overflow-hidden *:relative md:grid-cols-2 lg:grid-cols-4">
|
||||
{Object.entries(data.metrics).map(([name, value]) => (
|
||||
<Statistic name={name} value={value} key={name} />
|
||||
))}
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
className={`shadow-sm bg-white dark:bg-${theme}-900 rounded-md my-8`}
|
||||
className={`bg-white shadow-sm dark:bg-${theme}-900 my-8 rounded-md`}
|
||||
>
|
||||
<div
|
||||
className={`flex justify-between items-center px-6 py-4 text-${theme}-500`}
|
||||
className={`flex items-center justify-between px-6 py-4 text-${theme}-500`}
|
||||
>
|
||||
<LastChecked datetime={data.last_updated} />
|
||||
<RefreshButton />
|
||||
</div>
|
||||
<Search onChange={setSearchQuery}/>
|
||||
<ul
|
||||
className={`dark:divide-${theme}-800 divide-y dark:text-white`}
|
||||
>
|
||||
{data.images.filter((image) => image.reference.includes(searchQuery)).map((image) => (
|
||||
<Image data={image} key={image.reference} />
|
||||
))}
|
||||
<Search onChange={setSearchQuery} />
|
||||
<ul className={`dark:divide-${theme}-800 divide-y dark:text-white`}>
|
||||
{data.images
|
||||
.filter((image) => image.reference.includes(searchQuery))
|
||||
.map((image) => (
|
||||
<Image data={image} key={image.reference} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
<>
|
||||
<button
|
||||
onClick={handleOpen}
|
||||
className={`*:py-4 *:px-6 *:flex *:items-center *:gap-3 w-full`}
|
||||
className={`w-full *:flex *:items-center *:gap-3 *:px-6 *:py-4`}
|
||||
>
|
||||
<li className="break-all">
|
||||
<IconCube className="size-6 shrink-0" />
|
||||
@@ -70,7 +70,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
{data.result.has_update == false && (
|
||||
<WithTooltip
|
||||
text="Up to date"
|
||||
className="text-green-500 ml-auto size-6 shrink-0"
|
||||
className="ml-auto size-6 shrink-0 text-green-500"
|
||||
>
|
||||
<IconCircleCheckFilled />
|
||||
</WithTooltip>
|
||||
@@ -78,7 +78,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
{data.result.has_update == true && (
|
||||
<WithTooltip
|
||||
text="Update available"
|
||||
className="text-blue-500 ml-auto size-6 shrink-0"
|
||||
className="ml-auto size-6 shrink-0 text-blue-500"
|
||||
>
|
||||
<IconCircleArrowUpFilled />
|
||||
</WithTooltip>
|
||||
@@ -86,7 +86,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
{data.result.has_update == null && (
|
||||
<WithTooltip
|
||||
text="Unknown"
|
||||
className="text-gray-500 ml-auto size-6 shrink-0"
|
||||
className="ml-auto size-6 shrink-0 text-gray-500"
|
||||
>
|
||||
<IconHelpCircleFilled />
|
||||
</WithTooltip>
|
||||
@@ -102,12 +102,12 @@ export default function Image({ data }: { data: Image }) {
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<DialogPanel
|
||||
transition
|
||||
className={`relative transform overflow-hidden rounded-lg bg-white dark:bg-${theme}-900 dark:text-white text-left shadow-xl transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg md:max-w-xl lg:max-w-2xl data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95`}
|
||||
className={`relative transform overflow-hidden rounded-lg bg-white dark:bg-${theme}-900 text-left shadow-xl transition-all data-[closed]:translate-y-4 data-[closed]:opacity-0 data-[enter]:duration-300 data-[leave]:duration-200 data-[enter]:ease-out data-[leave]:ease-in sm:my-8 sm:w-full sm:max-w-lg data-[closed]:sm:translate-y-0 data-[closed]:sm:scale-95 md:max-w-xl lg:max-w-2xl dark:text-white`}
|
||||
>
|
||||
<div
|
||||
className={`py-4 px-6 flex flex-col gap-3 text-${theme}-400 dark:text-${theme}-600`}
|
||||
className={`flex flex-col gap-3 px-6 py-4 text-${theme}-400 dark:text-${theme}-600`}
|
||||
>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<IconCube className="size-6 shrink-0 text-black dark:text-white" />
|
||||
<DialogTitle className="text-black dark:text-white">
|
||||
{url ? (
|
||||
@@ -116,7 +116,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block after:bg-white after:h-[2px] after:bottom-[4px] after:left-0 after:scale-x-0 after:block after:relative after:w-full after:transition-transform after:duration-300 hover:after:scale-x-100"
|
||||
className="inline-block after:relative after:bottom-[1px] after:left-0 after:block after:h-[2px] after:w-full after:scale-x-0 after:bg-black after:dark:bg-white after:transition-transform after:duration-300 hover:after:scale-x-100"
|
||||
>
|
||||
{data.reference}
|
||||
</a>
|
||||
@@ -133,30 +133,30 @@ export default function Image({ data }: { data: Image }) {
|
||||
<div className="flex items-center gap-3">
|
||||
{data.result.has_update == false && (
|
||||
<>
|
||||
<IconCircleCheckFilled className="text-green-500 size-6 shrink-0" />
|
||||
<IconCircleCheckFilled className="size-6 shrink-0 text-green-500" />
|
||||
Up to date
|
||||
</>
|
||||
)}
|
||||
{data.result.has_update == true && (
|
||||
<>
|
||||
<IconCircleArrowUpFilled className="text-blue-500 size-6 shrink-0" />
|
||||
<IconCircleArrowUpFilled className="size-6 shrink-0 text-blue-500" />
|
||||
Update available
|
||||
</>
|
||||
)}
|
||||
{data.result.has_update == null && (
|
||||
<>
|
||||
<IconHelpCircleFilled className="text-gray-500 size-6 shrink-0" />
|
||||
<IconHelpCircleFilled className="size-6 shrink-0 text-gray-500" />
|
||||
Unknown
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<IconStopwatch className="text-gray-500 size-6 shrink-0" />
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<IconStopwatch className="size-6 shrink-0 text-gray-500" />
|
||||
Checked in {data.time} ms
|
||||
</div>
|
||||
{data.result.error && (
|
||||
<div className="bg-yellow-400/10 flex items-center gap-3 overflow-hidden break-all rounded-md px-3 py-2 mb-4">
|
||||
<IconAlertTriangleFilled className="text-yellow-500 size-5 shrink-0" />
|
||||
<div className="mb-4 flex items-center gap-3 overflow-hidden break-all rounded-md bg-yellow-400/10 px-3 py-2">
|
||||
<IconAlertTriangleFilled className="size-5 shrink-0 text-yellow-500" />
|
||||
{data.result.error}
|
||||
</div>
|
||||
)}
|
||||
@@ -164,7 +164,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
<div className="flex flex-col gap-1">
|
||||
Pull command
|
||||
<div
|
||||
className={`bg-${theme}-50 dark:bg-${theme}-950 text-gray-500 flex items-center rounded-md px-3 py-2 font-mono mb-4 group relative`}
|
||||
className={`bg-${theme}-100 dark:bg-${theme}-950 group relative mb-4 flex items-center rounded-md px-3 py-2 font-mono text-gray-500`}
|
||||
>
|
||||
<p className="overflow-scroll">
|
||||
docker pull {data.reference}
|
||||
@@ -174,9 +174,9 @@ export default function Image({ data }: { data: Image }) {
|
||||
<IconCheck className="absolute right-3" />
|
||||
) : (
|
||||
<button
|
||||
className="absolute right-3 opacity-0 group-hover:opacity-100 transition-opacity duration-50"
|
||||
className="duration-50 absolute right-3 opacity-0 transition-opacity group-hover:opacity-100"
|
||||
onClick={handleCopy(
|
||||
`docker pull ${data.reference}`
|
||||
`docker pull ${data.reference}`,
|
||||
)}
|
||||
>
|
||||
<IconCopy />
|
||||
@@ -188,7 +188,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
<div className="flex flex-col gap-1">
|
||||
Local digests
|
||||
<div
|
||||
className={`bg-${theme}-50 dark:bg-${theme}-950 text-gray-500 rounded-md px-3 py-2 font-mono scrollable`}
|
||||
className={`bg-${theme}-100 dark:bg-${theme}-950 scrollable rounded-md px-3 py-2 font-mono text-gray-500`}
|
||||
>
|
||||
<p className="overflow-x-scroll">
|
||||
{data.local_digests.join("\n")}
|
||||
@@ -199,7 +199,7 @@ export default function Image({ data }: { data: Image }) {
|
||||
<div className="flex flex-col gap-1">
|
||||
Remote digest
|
||||
<div
|
||||
className={`bg-${theme}-50 dark:bg-${theme}-950 text-gray-500 rounded-md px-3 py-2 font-mono`}
|
||||
className={`bg-${theme}-100 dark:bg-${theme}-950 rounded-md px-3 py-2 font-mono text-gray-500`}
|
||||
>
|
||||
<p className="overflow-x-scroll">{data.remote_digest}</p>
|
||||
</div>
|
||||
|
||||
@@ -8,22 +8,25 @@ export default function Loading({ onLoad }: { onLoad: (data: Data) => void }) {
|
||||
process.env.NODE_ENV === "production"
|
||||
? "/api/v1/full"
|
||||
: `http://${window.location.hostname}:8000/api/v1/full`,
|
||||
).then((response) => response.json().then((data) => {onLoad(data as Data)}));
|
||||
).then((response) =>
|
||||
response.json().then((data) => {
|
||||
onLoad(data as Data);
|
||||
}),
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className={`flex justify-center min-h-screen bg-${theme}-50 dark:bg-${theme}-950`}
|
||||
className={`flex min-h-screen justify-center bg-${theme}-50 dark:bg-${theme}-950`}
|
||||
>
|
||||
<div className="lg:px-8 sm:px-6 px-4 max-w-[80rem] mx-auto h-full w-full absolute overflow-hidden">
|
||||
<div className="flex flex-col max-w-[48rem] mx-auto h-full my-8">
|
||||
<div className="absolute mx-auto h-full w-full max-w-[80rem] overflow-hidden px-4 sm:px-6 lg:px-8">
|
||||
<div className="mx-auto my-8 flex h-full max-w-[48rem] flex-col">
|
||||
<div className="flex items-center gap-1">
|
||||
<h1 className="text-5xl lg:text-6xl font-bold dark:text-white">
|
||||
<h1 className="text-5xl font-bold lg:text-6xl dark:text-white">
|
||||
Cup
|
||||
</h1>
|
||||
<Logo />
|
||||
</div>
|
||||
<div
|
||||
className={`h-full flex justify-center
|
||||
items-center gap-1 text-${theme}-500 dark:text-${theme}-400`}
|
||||
className={`flex h-full items-center justify-center gap-1 text-${theme}-500 dark:text-${theme}-400`}
|
||||
>
|
||||
Loading <IconLoader2 className="animate-spin" />
|
||||
</div>
|
||||
|
||||
@@ -25,25 +25,28 @@ export default function Search({
|
||||
return (
|
||||
<div className={`w-full px-6 text-${theme}-500`}>
|
||||
<div
|
||||
className={`flex items-center w-full rounded-md border border-${theme}-300 dark:border-${theme}-700 px-2 gap-1 bg-${theme}-200 dark:bg-${theme}-800 flex-nowrap peer`}
|
||||
className={`flex w-full items-center rounded-md border border-${theme}-300 dark:border-${theme}-700 gap-1 px-2 bg-${theme}-200 dark:bg-${theme}-800 peer flex-nowrap`}
|
||||
>
|
||||
<IconSearch className="size-5" />
|
||||
<div className="w-full">
|
||||
<input
|
||||
className={`w-full h-10 text-sm text-${theme}-600 dark:text-${theme}-400 focus:outline-none peer bg-transparent placeholder:text-${theme}-500`}
|
||||
className={`h-10 w-full text-sm text-${theme}-600 dark:text-${theme}-400 peer bg-transparent focus:outline-none placeholder:text-${theme}-500`}
|
||||
placeholder="Search"
|
||||
onChange={handleChange}
|
||||
value={searchQuery}
|
||||
></input>
|
||||
</div>
|
||||
{showClear && (
|
||||
<button onClick={handleClear} className={`hover:text-${theme}-600 dark:hover:text-${theme}-400`}>
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className={`hover:text-${theme}-600 dark:hover:text-${theme}-400`}
|
||||
>
|
||||
<IconX className="size-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="relative -translate-y-[8px] h-[8px] border-b-blue-600 border-b-2 w-0 peer-has-[:focus]:w-full transition-all duration-200 rounded-md left-1/2 -translate-x-1/2"
|
||||
className="relative left-1/2 h-[8px] w-0 -translate-x-1/2 -translate-y-[8px] rounded-md border-b-2 border-b-blue-600 transition-all duration-200 peer-has-[:focus]:w-full"
|
||||
style={{ clipPath: "inset(calc(100% - 2px) 0 0 0)" }}
|
||||
></div>
|
||||
</div>
|
||||
|
||||
@@ -19,27 +19,27 @@ export default function Statistic({
|
||||
<div
|
||||
className={`before:bg-${theme}-200 before:dark:bg-${theme}-800 after:bg-${theme}-200 after:dark:bg-${theme}-800 gi`}
|
||||
>
|
||||
<div className="xl:px-8 px-6 py-4 gap-y-2 gap-x-4 justify-between align-baseline flex flex-col h-full">
|
||||
<div className="flex h-full flex-col justify-between gap-x-4 gap-y-2 px-6 py-4 align-baseline xl:px-8">
|
||||
<dt
|
||||
className={`text-${theme}-500 dark:text-${theme}-400 leading-6 font-medium`}
|
||||
className={`text-${theme}-500 dark:text-${theme}-400 font-medium leading-6`}
|
||||
>
|
||||
{name}
|
||||
</dt>
|
||||
<div className="flex gap-1 justify-between items-center">
|
||||
<dd className="text-black dark:text-white tracking-tight leading-10 font-medium text-3xl w-full">
|
||||
<div className="flex items-center justify-between gap-1">
|
||||
<dd className="w-full text-3xl font-medium leading-10 tracking-tight text-black dark:text-white">
|
||||
{value}
|
||||
</dd>
|
||||
{name == "Monitored images" && (
|
||||
<IconEyeFilled className="size-6 text-black dark:text-white shrink-0" />
|
||||
<IconEyeFilled className="size-6 shrink-0 text-black dark:text-white" />
|
||||
)}
|
||||
{name == "Up to date" && (
|
||||
<IconCircleCheckFilled className="size-6 text-green-500 shrink-0" />
|
||||
<IconCircleCheckFilled className="size-6 shrink-0 text-green-500" />
|
||||
)}
|
||||
{name == "Update available" && (
|
||||
<IconCircleArrowUpFilled className="size-6 text-blue-500 shrink-0" />
|
||||
<IconCircleArrowUpFilled className="size-6 shrink-0 text-blue-500" />
|
||||
)}
|
||||
{name == "Unknown" && (
|
||||
<IconHelpCircleFilled className="size-6 text-gray-500 shrink-0" />
|
||||
<IconHelpCircleFilled className="size-6 shrink-0 text-gray-500" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,4 +51,4 @@
|
||||
html::-webkit-scrollbar-thumb:hover {
|
||||
background: #b5b5b5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,20 +7,20 @@ export interface Data {
|
||||
};
|
||||
images: Image[];
|
||||
last_updated: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
reference: string,
|
||||
reference: string;
|
||||
parts: {
|
||||
registry: string,
|
||||
repository: string,
|
||||
tag: string,
|
||||
},
|
||||
local_digests: string[],
|
||||
remote_digest: string,
|
||||
registry: string;
|
||||
repository: string;
|
||||
tag: string;
|
||||
};
|
||||
local_digests: string[];
|
||||
remote_digest: string;
|
||||
result: {
|
||||
has_update: boolean | null,
|
||||
error: string | null
|
||||
},
|
||||
time: number
|
||||
}
|
||||
has_update: boolean | null;
|
||||
error: string | null;
|
||||
};
|
||||
time: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user