m/cup
1
0
mirror of https://github.com/sergi0g/cup.git synced 2025-11-10 06:03:50 -05:00

95 Commits

Author SHA1 Message Date
Sergio
8d0da37e36 Fix more broken docs styles 2025-02-21 19:51:03 +02:00
Sergio
780d7a088d Fix eslint warnings in docs 2025-02-21 19:37:36 +02:00
Sergio
bcb9f63735 Fix broken styles 2025-02-21 19:33:30 +02:00
Sergio
4d691dd5fa Serialize current and local values to avoid having to recompute them when fetching API data. Also fixes the bug where the versions aren't displayed in the CLI output for remote servers 2025-02-21 17:09:00 +02:00
Sergio
685219ea62 Add option to refresh all servers 2025-02-21 17:05:32 +02:00
Sergio
756462cd7c Make local images collapsible 2025-02-21 16:35:15 +02:00
Sergio
f020ac0906 Fix clippy lint (this code has been there since... forever, why hasn't this been detected) 2025-02-21 16:02:03 +02:00
Sergio
4b03a48d88 Tiny web UI changes 2025-02-19 20:31:57 +02:00
Sergio
ba1cfac64b Add base path to docs (nothing changes, I just want to override it) 2025-02-18 18:06:19 +02:00
Sergio
05d4c7c630 I think you can guess 2025-02-18 17:47:14 +02:00
Sergio
cf22ec300f Fix a few more clippy lints 2025-02-18 16:58:25 +02:00
Sergio
5b428dbf67 Fix some clippy lints 2025-02-18 16:57:08 +02:00
Sergio
787a730ab5 Update colors 2025-02-15 16:08:55 +02:00
Sergio
925989fd80 Redesign UI a bit 2025-02-15 14:38:58 +02:00
Sergio
5656003058 Fix small bug in API 2025-02-15 13:02:33 +02:00
Sergio
f79d7ff03a Switch to Lucide Icons 2025-02-15 12:50:59 +02:00
Sergio
550fb955a3 Refactor logging, create context for passing around between functions instead of config 2025-02-14 19:24:35 +02:00
Sergio
6ae95bf83b Update API and improve CLI output 2025-02-14 18:28:18 +02:00
Sergio
2262df0355 Fix typo in homepage 2025-02-14 18:27:29 +02:00
Sergio
1beb7dc020 Add docs for multiple servers 2025-02-07 17:44:46 +02:00
Sergio
a0de565367 Update docs about JSON CLI output, fix small typo on homepage 2025-02-07 17:32:19 +02:00
Sergio
0314ef2f05 Add multiple server support to Liquid UI, CSS fixes 2025-02-07 17:25:06 +02:00
Sergio
f1c8a45122 Remove unused components in docs, fix errors, improve docs workflow 2025-02-02 15:00:41 +02:00
Sergio
ce3f8176f1 Skip deploying docs for branches other than main 2025-02-02 14:52:36 +02:00
Sergio
8b520182ed Update docs actions (attempt 1) 2025-02-02 14:48:40 +02:00
Sergio
e8fee79d20 Merge branch 'main' into v3 2025-02-02 14:45:37 +02:00
Sergio
24f160803a Add docs about integrations 2025-02-02 14:33:14 +02:00
Sergio
2ef77c9a55 Fix mobile dialog not taking screen width 2025-02-01 13:53:15 +02:00
Sergio
a5bbdd0e33 Upgrade docs 2025-02-01 13:41:21 +02:00
Sergio
b5aa0309ee Tiny improvement to version handling, add a new debug log 2025-02-01 10:28:17 +02:00
Sergio
4bbb53cd67 Show unknown when image tag does not exist 2025-01-15 17:09:19 +02:00
Sergio
3ac6fb57e9 Fix crash when checking a remote tag and it is the latest available 2025-01-15 16:44:26 +02:00
Sergio
ead74dadd6 Update docs
Some checks failed
Deploy github pages / build (push) Has been cancelled
Deploy github pages / deploy (push) Has been cancelled
2025-01-05 13:48:04 +02:00
Sergio
6e6afdb757 Update docs (#55) 2025-01-05 13:46:42 +02:00
Sergio
0c10134829 Fix #55 2025-01-05 13:42:15 +02:00
Sergio
c0c7f7c0e9 Finished basic functionality for multiple servers in the backend.
No special CLI or Liquid support yet and also no refresh support
2025-01-03 16:10:17 +02:00
Sergio
aeeffaccba Get updates from multiple servers (part one: get the data) 2025-01-02 20:11:31 +02:00
Sergio
a1711b7ac8 Fix workflows
Some checks failed
Deploy github pages / build (push) Has been cancelled
Deploy github pages / deploy (push) Has been cancelled
https://github.com/cross-rs/cross/issues/1561
2025-01-02 15:09:35 +02:00
Sergio
9d628e3ab2 Implement refresh schedule 2025-01-02 14:37:33 +02:00
Sergio
d3b18a6587 Update schema 2025-01-02 14:07:10 +02:00
Sergio
76a812f52f Update tailwind CSS safelist 2025-01-02 13:23:26 +02:00
Sergio
fe779c9c4e Probably meaningless change of some Option<&String>s to Option<&str>s 2025-01-02 13:00:32 +02:00
Sergio
84609d5189 Various small changes and optimizations related to timekeeping, logging and JSON handling. Binary size shrunk by 0.2 MB! 2025-01-02 12:48:35 +02:00
Sergio
ded441cf75 Add support for connecting with both unix and http docker sockets. Hasn't been tested yet. 2025-01-02 12:47:52 +02:00
Sergio
0a8295fff4 Improve semver version handling (decrease false positives)
Some checks are pending
Deploy github pages / build (push) Waiting to run
Deploy github pages / deploy (push) Blocked by required conditions
2025-01-01 15:25:49 +02:00
Sergio
9c8e6ccdea Work on liquid a bit, format code 2025-01-01 14:29:17 +02:00
Sergio
f1e1bcbf1c update docs 2025-01-01 12:51:19 +02:00
Sergio
31f7bfbbcb Start updating docs
Some checks failed
Deploy github pages / build (push) Has been cancelled
Deploy github pages / deploy (push) Has been cancelled
2024-12-20 21:20:50 +02:00
Sergio
15eb553e50 Update "try it out" command 2024-12-20 20:33:03 +02:00
Sergio
359147770f Create basic homepage and format docs 2024-12-20 19:24:22 +02:00
Sergio
0a4e302322 Maybe I shouldn't have removed that h-full 2024-12-20 18:26:27 +02:00
Sergio
5ed64c92fd Change statistic style on large screens 2024-12-20 18:20:28 +02:00
Sergio
6d08d75ac3 Make statistics grid smaller on mobile 2024-12-20 18:13:15 +02:00
Sergio
dc38b84e87 Bug fixes for the previous UI improvements (I tested properly this time) 2024-12-20 17:49:21 +02:00
Sergio
09b6880295 Various frontend improvements 2024-12-20 17:35:39 +02:00
Sergio
4f1075b2b2 Add error message to API response 2024-12-20 17:00:41 +02:00
Sergio
c84270603f Readd theme-color meta tag and make it work properly 2024-12-08 20:59:47 +02:00
Sergio
4aa28f2cc5 Remove incorrect theme-color meta property 2024-12-08 19:33:50 +02:00
Sergio
eadda5f776 Add servers option (no-op at the moment) 2024-12-07 17:18:36 +02:00
Sergio
622b156eed Add agent mode, fix config version bug 2024-12-07 17:08:34 +02:00
Sergio
dca19b5ae2 Whoops wrong server versioning 2024-12-07 17:02:21 +02:00
Sergio
f6ac43aac0 Change API versioning to match Cup version 2024-12-07 16:30:07 +02:00
Sergio
e5e60c4abc Add requirement for version key to new config 2024-12-07 16:28:06 +02:00
Sergio
33a72c8c0d Change config, add schema 2024-12-07 16:21:01 +02:00
Sergio
e544ef6ca5 Slightly optimize version regex 2024-12-06 17:47:31 +02:00
Sergio
afc34a0847 Update gitignore 2024-12-05 21:28:42 +02:00
Sergio
ce08e00bb4 Replace reference regex with homemade logic. Can we go faster? 2024-12-05 21:07:13 +02:00
Sergio
6a77b85141 Remove used async keyword from 2 functions in Image 2024-12-05 20:23:07 +02:00
Sergio
215e88ae0f Switch to serde for config parsing 2024-12-05 20:22:26 +02:00
Sergio
178acfb2f6 Add debug option to CLI 2024-12-05 18:13:53 +02:00
Sergio
59894343de Add error message when user doesn't specify command 2024-12-04 20:00:42 +02:00
Sergio
61bc60493f This is the last time I promise 2024-12-04 19:52:51 +02:00
Sergio
be7d55d126 Remove some more clone usage 2024-12-04 19:45:12 +02:00
Sergio
36a3a13c04 Remove some clone usage 2024-12-04 19:44:04 +02:00
Sergio
d85fadfb39 Test split 2024-11-21 19:26:17 +02:00
Sergio
0f95be26dc Remove usage of panic 2024-11-17 19:54:07 +02:00
Sergio
0b7e064980 Update sort_update_vec function, fix lints and enable testing in CI 2024-11-16 14:58:16 +02:00
Sergio
9e9bb78db7 Fix broken tests 2024-11-16 13:14:52 +02:00
Sergio
88d346b480 Support checking for version updates for images that aren't available locally 2024-11-16 13:05:46 +02:00
Sergio
4519c534a1 Move usage of get_images_from_docker_daemon to get_updates 2024-11-16 12:38:00 +02:00
Sergio
6b83f51749 Improve a little code section in registry.rs 2024-11-16 12:25:10 +02:00
Sergio
0c3f293fa8 Clippy 2024-11-15 13:31:42 +02:00
Sergio
d94abecf35 Nearly complete versioning support. Fixed old bugs where not all tags were fetched. 2024-11-15 13:21:30 +02:00
Sergio
c11b5e6432 OMG WE CAN DO SEMVER FOR THE CLI AND THE RESULTS LOOK CORRECT 2024-11-01 21:39:15 +02:00
Sergio
022dc0b2cb Merge branch 'main' into v3 2024-10-27 20:25:06 +02:00
Sergio
51609da4ff Merge remote-tracking branch 'origin/main' into v3 2024-10-27 18:15:47 +02:00
Sergio
3ed79e69bd Add semver regex and function to match tags (preparing to implement semver checking) 2024-10-27 18:09:26 +02:00
Sergio
078a51c4fa Fix CSS bug and tweak some stuff 2024-10-25 17:51:33 +03:00
Sergio
8d70d7ae4d Fixed CSS bugs, formatted code 2024-10-25 17:09:54 +03:00
Sergio
6d45409928 Add copy pull command button to image info 2024-10-25 16:59:42 +03:00
Sergio
bcfb9ef27a Update frontend 2024-10-25 16:26:28 +03:00
Sergio
5c4de36052 Add Content-Type header to API responses 2024-10-25 12:17:52 +03:00
Sergio
eda30229e2 Clippy 2024-10-25 12:13:25 +03:00
Sergio
8fd012efbe Added new full json API route and changed API routes 2024-10-25 12:12:59 +03:00
Sergio
8ab073d562 Changed how updates are managed after checking (preparing for the new API) 2024-10-25 11:32:59 +03:00
51 changed files with 1046 additions and 1181 deletions

View File

@@ -1,11 +0,0 @@
FROM --platform=$BUILDPLATFORM alpine AS builder
ARG TARGETARCH
ARG TARGETOS
COPY binaries/* /
RUN mv cup-$TARGETOS-$TARGETARCH cup
FROM scratch
COPY --from=builder /cup /cup
ENTRYPOINT ["/cup"]

View File

@@ -1,51 +0,0 @@
name: Build Image
inputs:
tags:
description: "Docker image tags"
required: true
gh-token:
description: "Github token"
required: true
runs:
using: 'composite'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download binaries
uses: actions/download-artifact@v4
with:
path: .
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/sergi0g/cup
tags: ${{ inputs.tags }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: sergi0g
password: ${{ inputs.gh-token }}
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ./.github/actions/build-image/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -36,7 +36,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Bun
uses: oven-sh/setup-bun@v1
@@ -62,24 +62,38 @@ jobs:
cup-linux-arm64
build-image:
needs:
- get-tag
- build-binaries
needs: get-tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: ./.github/actions/build-image
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
tags: |
${{ needs.get-tag.outputs.tag }}
gh-token: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64, linux/arm64
push: true
tags: ghcr.io/sergi0g/cup:${{ needs.get-tag.outputs.tag }}
cache-from: type=gha
cache-to: type=gha,mode=max
nightly-release:
runs-on: ubuntu-latest
needs:
- build-binaries
- build-image
needs: [build-binaries, get-tag]
steps:
- name: Download binaries
uses: actions/download-artifact@v4

View File

@@ -60,19 +60,34 @@ jobs:
cup-linux-arm64
build-image:
needs:
- get-tag
- build-binaries
needs: get-tag
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: ./.github/actions/build-image
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
tags: |
${{ needs.get-tag.outputs.tag }}
latest
gh-token: ${{ secrets.GITHUB_TOKEN }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64, linux/arm64
push: true
tags: ghcr.io/sergi0g/cup:${{ needs.get-tag.outputs.tag }},ghcr.io/sergi0g/cup:latest
cache-from: type=gha
cache-to: type=gha,mode=max
release:
runs-on: ubuntu-latest
@@ -92,4 +107,4 @@ jobs:
prerelease: true
tag_name: ${{ needs.get-tag.outputs.tag }}
name: ${{ needs.get-tag.outputs.tag }}
files: binaries/*
files: binaries/*

1157
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,15 @@
[package]
name = "cup"
version = "3.0.1"
version = "3.0.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.7", features = ["derive"] }
indicatif = { version = "0.17.8", optional = true }
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
xitca-web = { version = "0.6.2", optional = true }
xitca-web = { version = "0.5.0", optional = true }
liquid = { version = "0.26.6", optional = true }
bollard = "0.18.1"
bollard = "0.16.1"
once_cell = "1.19.0"
http-auth = { version = "0.1.9", default-features = false }
termsize = { version = "0.1.8", optional = true }
@@ -17,11 +17,11 @@ regex = { version = "1.10.5", default-features = false, features = ["perf"] }
chrono = { version = "0.4.38", default-features = false, features = ["std", "alloc", "clock"], optional = true }
reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls"] }
futures = "0.3.30"
reqwest-retry = "0.7.0"
reqwest-retry = "0.6.1"
reqwest-middleware = "0.3.3"
rustc-hash = "2.0.0"
http-link = "1.0.1"
itertools = "0.14.0"
itertools = "0.13.0"
serde_json = "1.0.133"
serde = "1.0.215"
tokio-cron-scheduler = { version = "0.13.0", default-features = false, optional = true }

View File

@@ -15,7 +15,7 @@ RUN ~/.bun/bin/bun install
RUN ~/.bun/bin/bun run build
### Build Cup ###
FROM rust:1-alpine AS build
FROM rust:1.80.1-alpine AS build
# Requirements
RUN apk add musl-dev

View File

@@ -17,18 +17,18 @@ _If you like this project and/or use Cup, please consider starring the project
- Supports most registries, including Docker Hub, ghcr.io, Quay, lscr.io and even Gitea (or derivatives)
- Doesn't exhaust any rate limits. This is the original reason I created Cup. It was inspired by [What's up docker?](https://github.com/getwud/wud) which would always use it up.
- Beautiful CLI and web interface for checking on your containers any time.
- The binary is tiny! At the time of writing it's just 5.4 MB. No more pulling 100+ MB docker images for a such a simple program.
- The binary is tiny! At the time of writing it's just 5.1 MB. No more pulling 100+ MB docker images for a such a simple program.
- JSON output for both the CLI and web interface so you can connect Cup to integrations. It's easy to parse and makes webhooks and pretty dashboards simple to set up!
## Documentation 📘
Take a look at https://cup.sergi0g.dev/docs!
Take a look at https://sergi0g.github.io/cup/docs!
## Limitations
Cup is a work in progress. It might not have as many features as other alternatives. If one of these features is really important for you, please consider using another tool.
- Cup cannot directly trigger your integrations. If you want that to happen automatically, please use What's up Docker instead. Cup was created to be simple. The data is there, and it's up to you to retrieve it (e.g. by running `cup check -r` with a cronjob or periodically requesting the `/api/v3/json` url from the server).
- Cup cannot directly trigger your integrations. If you want that to happen automatically, please use What's up Docker instead. Cup was created to be simple. The data is there, and it's up to you to retrieve it (e.g. by running `cup check -r` with a cronjob or periodically requesting the `/json` url from the server).
## Roadmap
Take a sneak peek at what's coming up in future releases on the [roadmap](https://github.com/users/sergi0g/projects/2)!
@@ -44,7 +44,7 @@ Here are some ideas to get you started:
- Help optimize Cup and make it even better!
- Add more features to the web UI
For more information, check the [docs](https://cup.sergi0g.dev/docs/contributing)!
For more information, check the [docs](https://sergi0g.github.io/cup/docs/contributing)!
## Support

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

@@ -0,0 +1,15 @@
import React from "react";
export function GitHubIcon({ className }: { className?: string | undefined }) {
return (
<svg
width="24"
height="24"
fill="currentColor"
viewBox="3 3 18 18"
className={className}
>
<path d="M12 3C7.0275 3 3 7.12937 3 12.2276C3 16.3109 5.57625 19.7597 9.15374 20.9824C9.60374 21.0631 9.77249 20.7863 9.77249 20.5441C9.77249 20.3249 9.76125 19.5982 9.76125 18.8254C7.5 19.2522 6.915 18.2602 6.735 17.7412C6.63375 17.4759 6.19499 16.6569 5.8125 16.4378C5.4975 16.2647 5.0475 15.838 5.80124 15.8264C6.51 15.8149 7.01625 16.4954 7.18499 16.7723C7.99499 18.1679 9.28875 17.7758 9.80625 17.5335C9.885 16.9337 10.1212 16.53 10.38 16.2993C8.3775 16.0687 6.285 15.2728 6.285 11.7432C6.285 10.7397 6.63375 9.9092 7.20749 9.26326C7.1175 9.03257 6.8025 8.08674 7.2975 6.81794C7.2975 6.81794 8.05125 6.57571 9.77249 7.76377C10.4925 7.55615 11.2575 7.45234 12.0225 7.45234C12.7875 7.45234 13.5525 7.55615 14.2725 7.76377C15.9937 6.56418 16.7475 6.81794 16.7475 6.81794C17.2424 8.08674 16.9275 9.03257 16.8375 9.26326C17.4113 9.9092 17.76 10.7281 17.76 11.7432C17.76 15.2843 15.6563 16.0687 13.6537 16.2993C13.98 16.5877 14.2613 17.1414 14.2613 18.0065C14.2613 19.2407 14.25 20.2326 14.25 20.5441C14.25 20.7863 14.4188 21.0746 14.8688 20.9824C16.6554 20.364 18.2079 19.1866 19.3078 17.6162C20.4077 16.0457 20.9995 14.1611 21 12.2276C21 7.12937 16.9725 3 12 3Z"></path>
</svg>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 233 KiB

View File

@@ -10,12 +10,12 @@ export function Card({
description: string;
}) {
return (
<div className="p-4 bg-white dark:bg-black group">
<Icon className="text-black size-7 group-hover:size-9 dark:text-white inline mr-2 transition-[width,height] duration-200" />
<div>
<Icon className="text-black size-7 dark:text-white inline mr-2" />
<span className="align-middle text-2xl font-bold text-black dark:text-white">
{name}
</span>
<p className="text-xl font-semibold text-neutral-500 dark:text-neutral-500">
<p className="text-2xl font-semibold text-neutral-500 dark:text-neutral-500">
{description}
</p>
</div>

View File

@@ -0,0 +1,28 @@
"use client";
import { IconCopy, IconCopyCheck } from "@tabler/icons-react";
import { useState } from "react";
export default function CopyableCode({ children }: { children: string }) {
const [success, setSuccess] = useState(false);
const handleClick = () => {
navigator.clipboard.writeText(children);
setSuccess(true);
setTimeout(() => setSuccess(false), 3000);
};
return (
<div className="relative rounded-md xl:w-auto">
<button
className="hover:bg-black/10 dark:hover:bg-black/60 flex w-full items-center justify-center gap-4 rounded-md border border-black/10 bg-black/5 px-8 py-3 font-mono text-sm font-medium text-black/70 transition-colors duration-200 md:px-10 md:py-3 md:text-base md:leading-6 dark:border-white/15 dark:bg-black dark:text-gray-300 backdrop-blur-md"
onClick={handleClick}
>
{children}
{success ? (
<IconCopyCheck className="stroke-black/40 dark:stroke-white/50" />
) : (
<IconCopy className="stroke-black/40 dark:stroke-white/50" />
)}
</button>
</div>
);
}

View File

@@ -8,7 +8,7 @@ export function GridPattern() {
return (
<svg
aria-hidden="true"
className="pointer-events-none absolute inset-0 bottom-0 left-0 right-0 top-0 h-full w-full -z-10 bg-white stroke-neutral-200 dark:stroke-white/10 dark:bg-black"
className="pointer-events-none absolute inset-0 bottom-0 left-0 right-0 top-0 -z-10 h-full w-full stroke-neutral-200 dark:stroke-neutral-600/30"
>
<defs>
<pattern
@@ -22,6 +22,7 @@ export function GridPattern() {
<path
d={`M.5 ${SIZE}V.5H${SIZE}`}
fill="none"
strokeDasharray={"4 2"}
/>
</pattern>
</defs>

View File

@@ -0,0 +1,28 @@
import { ReactNode } from "react";
import { GradientText } from "./GradientText";
export function Section({
title,
className,
children,
}: {
title: string;
className: string;
children: ReactNode;
}) {
return (
<div className="border-t border-t-neutral-300 bg-neutral-50 py-32 dark:border-t-neutral-600/30 dark:bg-neutral-950">
<div className="mx-auto w-full max-w-screen-xl">
<GradientText
text={title}
className="mx-auto mb-20 w-fit text-center text-4xl font-bold tracking-tighter"
innerClassName={className}
blur={12}
/>
<div className="m-2 grid w-full auto-cols-fr gap-20 lg:grid-cols-3">
{children}
</div>
</div>
</div>
);
}

View File

@@ -1,28 +1,29 @@
import React from "react";
import "./styles.css";
import "./styles.css"
import CopyableCode from "../CopyableCode";
import { Browser } from "../Browser";
import { Card } from "../Card";
import {
IconAdjustments,
IconArrowRight,
IconBarrierBlockOff,
IconBolt,
IconBraces,
IconDevices,
IconFeather,
IconGitMerge,
IconPuzzle,
IconServer,
IconTerminal,
IconLockCheck,
} from "@tabler/icons-react";
import { GitHubIcon } from "nextra/icons";
import { GridPattern } from "../GridPattern";
import { GradientText } from "../GradientText";
import { Section } from "../Section";
import { Steps } from "nextra/components";
import Link from "next/link";
export default async function Home() {
return (
<>
<div className="relative home bg-radial-[ellipse_at_center] from-transparent from-20% to-white dark:to-black">
<div className="relative home">
<GridPattern />
<div className="px-4 pt-16 pb-8 sm:pt-24 lg:px-8">
<div className="flex w-full flex-col items-center justify-between">
@@ -36,7 +37,7 @@ export default async function Home() {
blur={30}
/>
</h1>
<h3 className="mx-auto mt-6 max-w-3xl text-center text-xl leading-tight font-medium text-neutral-500 dark:text-neutral-400">
<h3 className="mx-auto mt-6 max-w-3xl text-center text-xl leading-tight font-medium text-gray-400">
Cup is a small utility with a big impact. Simplify your
container management workflow with fast and efficient update
checking, a full-featured CLI and web interface, and more.
@@ -53,7 +54,7 @@ export default async function Home() {
<a
href="https://github.com/sergi0g/cup"
target="_blank"
className="hide-focus h-full bg-white dark:bg-black text-nowrap border border-black/15 transition-colors duration-200 ease-in-out hover:border-black/40 dark:border-white/15 hover:dark:border-white/40 hover:dark:shadow-sm focus:dark:border-white/30"
className="hide-focus h-full text-nowrap border border-neutral-400 transition-colors duration-200 ease-in-out hover:border-neutral-600 focus:border-neutral-600 dark:border-neutral-600 hover:dark:border-neutral-400 hover:dark:shadow-sm hover:dark:shadow-neutral-600 focus:dark:border-neutral-400"
>
Star on GitHub
<GitHubIcon className="ml-auto size-4 md:size-5" />
@@ -65,49 +66,68 @@ export default async function Home() {
<Browser />
</div>
</div>
<div className="bg-white dark:bg-black py-12 px-8 w-full">
<div className="flex h-full w-full items-center justify-center">
<div className="grid md:grid-cols-2 md:grid-rows-4 lg:grid-cols-4 lg:grid-rows-2 w-full max-w-7xl gap-px border border-transparent bg-black/10 dark:bg-white/10">
<Card
name="Built for speed."
icon={IconBolt}
description="Cup is written in Rust and every release goes through extensive profiling to squeeze out every last drop of performance."
/>
<Card
name="Configurable."
icon={IconAdjustments}
description="Make Cup yours with the extensive configuration options available. Customize and tailor it to your needs."
/>
<Card
name="Extend it."
icon={IconPuzzle}
description="JSON output enables you to connect Cup with your favorite integrations, build automations and more."
/>
<Card
name="CLI available."
icon={IconTerminal}
description="Do you like terminals? Cup has a CLI. Check for updates quickly without spinning up a server."
/>
<Card
name="Multiple servers."
icon={IconServer}
description="Run multiple Cup instances and effortlessly check on them through one web interface."
/>
<Card
name="Unstoppable."
icon={IconBarrierBlockOff}
description="Cup is designed to check for updates without using up any rate limits. 10 images per hour won't be a problem, even with 100 images."
/>
<Card
name="Lightweight."
icon={IconFeather}
description="No need for a powerful server and endless storage. The tiny 5.4 MB binary won't hog your CPU and memory."
/>
<Card
name="Open source."
icon={IconGitMerge}
description="All source code is publicly available in our GitHub repository. We're looking for contributors!"
/>
<Section
title="Powerful at its core."
className="bg-gradient-to-r from-red-500 to-amber-500"
>
<Card
name="100% Safe Code"
icon={IconLockCheck}
description="Built with safe Rust and Typescript to ensure security and reliability."
/>
<Card
name="Lightning Fast Performance"
icon={IconBolt}
description="Heavily optimized to squeeze out every last drop of performance. Each release is extensively benchmarked and profiled so that you'll never have to stare at a loading spinner for long."
/>
<Card
name="Lightweight"
icon={IconFeather}
description="No runtimes or libraries are needed. All you need is the 5.1 MB static binary that works out of the box on any system."
/>
</Section>
<Section
title="Efficient, yet flexible."
className="bg-gradient-to-r from-blue-500 to-indigo-500"
>
<Card
name="JSON output"
description="Connect Cup to your favorite intergrations with JSON output for the CLI and an API for the server. Now go make that cool dashboard you've been dreaming of!"
icon={IconBraces}
/>
<Card
name="Both CLI and web interface"
description="Whether you prefer the command line or the web, Cup runs wherever you choose."
icon={IconDevices}
/>
<Card
name="Configurable"
description="The simple configuration file provides you with all the tools you need to specify a custom Docker socket, manage registry connection options, choose a theme for the web interface and more."
icon={IconAdjustments}
/>
</Section>
<div className="relative py-24 border-t border-t-neutral-300 dark:border-t-neutral-600/30 text-black dark:text-white">
<GridPattern />
<div className="mx-auto flex w-full max-w-screen-xl flex-col items-center">
<p className="mb-8 text-center text-3xl font-bold">
Still not convinced? Try it out now!
</p>
<div>
<Steps>
<h3 className="mb-2">Open a terminal and run</h3>
<CopyableCode>
docker run --rm -t -v /var/run/docker.sock:/var/run/docker.sock
-p 8000:8000 ghcr.io/sergi0g/cup serve
</CopyableCode>
<h3 className="mb-2">Open the dashboard in your browser</h3>
<p>
Visit{" "}
<a href="http://localhost:8000" className="underline">
http://localhost:8000
</a>{" "}
in your browser to try it out!
</p>
</Steps>
</div>
</div>
</div>

View File

@@ -20,7 +20,7 @@ const logo = (
);
const navbar = (
<Navbar logo={logo} projectLink="https://github.com/sergi0g/cup" chatLink="https://discord.gg/jmh5ctzwNG">
<Navbar logo={logo} projectLink="https://github.com/sergi0g/cup">
<ThemeSwitch lite className="cursor-pointer" />
</Navbar>
);

View File

@@ -35,9 +35,9 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
```
Cup can run with a non-root user, but needs to be in a docker group. Assuming user id of 1000 and `docker` group id of 999 you can add this to the `services.cup` key in the docker compose:
This can be customized further of course, if you choose to use a different port, another config location, or would like to change something else.
Cup can run with a non-root user, but needs to be in a docker group. Assuming user id of 1000 and `docker` group id of 999 you can add this to the docker compose:
```yaml
user: "1000:999"
```
The compose can be customized further of course, if you choose to use a different port, another config location, or would like to change something else. Have fun!

View File

@@ -36,7 +36,7 @@ services:
homepage.widget.mappings[1].field.metrics: up_to_date
homepage.widget.mappings[1].format: number
homepage.widget.mappings[2].label: Updates
homepage.widget.mappings[2].field.metrics: updates_available
homepage.widget.mappings[2].field.metrics: update_available
homepage.widget.mappings[2].format: number
```
@@ -64,7 +64,7 @@ widget:
label: Up to date
format: number
- field:
metrics: updates_available
metrics: update_available
label: Available updates
format: number
- field:

View File

@@ -1,12 +0,0 @@
# Agent mode
If you'd like to have only the server API exposed without the dashboard, you can run Cup in agent mode.
Modify your config like this:
```jsonc
{
"agent": true
// Other options
}
```

View File

@@ -4,7 +4,7 @@ import { Callout } from "nextra/components";
Some registries (or specific images) may require you to be authenticated. For those, you can modify `cup.json` like this:
```jsonc
```json
{
"registries": {
"<YOUR_REGISTRY_DOMAIN_1>": {

View File

@@ -1,12 +0,0 @@
# Automatic refresh
Cup can automatically refresh the results when running in server mode. Simply add this to your config:
```jsonc
{
"refresh_interval": "0 0,30 * 0 0" // Check twice an hour
// Other options
}
```
You can use a cron expression to specify the refresh interval. The reference is [here](https://github.com/Hexagon/croner-rust#pattern)

View File

@@ -1,22 +0,0 @@
# Ignored registries
If you want to skip checking images from some registries, you can modify your config like this:
```jsonc
{
"registries": {
"<SOME_REGISTRY_DOMAIN_1>": {
"ignore": true
// Other options
},
"<SOME_REGISTRY_DOMAIN_2>" {
"ignore": false
// Other options
},
// ...
}
// Other options
}
```
This configuration option is a bit redundant, since you can achieve the same with [this option](/docs/configuration/include-exclude-images). It's recommended to use that.

View File

@@ -1,35 +0,0 @@
# Include/Exclude images
If you want to exclude some images (e.g. because they have too many tags and take too long to check), you can add the following to your config:
```jsonc
{
"images": {
"exclude": [
"ghcr.io/immich-app/immich-machine-learning",
"postgres:15"
]
// ...
}
// Other options
}
```
For an image to be excluded, it must start with one of the strings you specify above. That means you could use `ghcr.io` to exclude all images from ghcr.io or `ghcr.io/sergi0g` to exclude all my images (why would you do that?).
If you want Cup to always check some extra images that aren't available locally, you can modify your config like this:
```jsonc
{
"images": {
"extra": [
"mysql:8.0",
"nextcloud:30"
]
// ...
}
// Other options
}
```
Note that you must specify images with version tags, otherwise Cup will exit with an error!

View File

@@ -8,7 +8,7 @@ import {
IconLockOpen,
IconKey,
IconPlug,
IconServer,
IconServer
} from "@tabler/icons-react";
# Configuration
@@ -71,21 +71,12 @@ Here's a full example:
```json
{
"$schema": "https://raw.githubusercontent.com/sergi0g/cup/main/cup.schema.json",
"version": 3,
"images": {
"exclude": ["ghcr.io/immich-app/immich-machine-learning"],
"extra": ["ghcr.io/sergi0g/cup:v3.0.0"]
"authentication": {
"ghcr.io": "<YOUR_TOKEN_HERE>",
"registry-1.docker.io": "<YOUR_TOKEN_HERE>"
},
"registries": {
"myregistry.com": {
"authentication": "<YOUR_TOKEN_HERE>"
}
},
"servers": {
"Raspberry Pi": "https://server.local:8000"
},
"theme": "blue"
"theme": "blue",
"insecure_registries": ["localhost:5000", "my-insecure-registry.example.com"]
}
```

View File

@@ -8,7 +8,7 @@ To solve this problem, you can specify exceptions in your `cup.json`.
Here's what it looks like:
```jsonc
```json
{
"registries": {
"<INSECURE_REGISTRY_1>": {

View File

@@ -4,7 +4,7 @@ Besides checking for local image updates, you might want to be able to view upda
Just add something like this to your config:
```jsonc
```json
{
"servers": {
"Cool server 1": "http://your-other-server-running-cup:8000",

View File

@@ -1,8 +1,8 @@
# Custom socket
# Socket
If you need to specify a custom Docker socket (e.g. because you're using Podman), you can use the `socket` option. Here's an example:
```jsonc
```json
{
"socket": "/run/user/1000/podman/podman.sock"
// Other options
@@ -11,7 +11,7 @@ If you need to specify a custom Docker socket (e.g. because you're using Podman)
You can also specify a TCP socket if you're using a remote Docker host or a [proxy](https://github.com/Tecnativa/docker-socket-proxy):
```jsonc
```json
{
"socket": "tcp://localhost:2375"
// Other options

View File

@@ -21,7 +21,7 @@ Available options are `default` and `blue`.
Here's an example:
```jsonc
```json
{
"theme": "blue"
// Other options

View File

@@ -15,7 +15,7 @@ Cup is a lightweight alternative to [What's up Docker?](https://github.com/getwu
- Supports most registries, including Docker Hub, ghcr.io, Quay, lscr.io and even Gitea (or derivatives)
- Doesn't exhaust any rate limits. This is the original reason I created Cup. It was inspired by [What's up docker?](https://github.com/getwud/wud) which would always use it up.
- Beautiful CLI and web interface for checking on your containers any time.
- The binary is tiny! At the time of writing it's just 5.4 MB. No more pulling 100+ MB docker images for a such a simple program.
- The binary is tiny! At the time of writing it's just 5.1 MB. No more pulling 100+ MB docker images for a such a simple program.
- JSON output for both the CLI and web interface so you can connect Cup to integrations. It's easy to parse and makes webhooks and pretty dashboards simple to set up!
# Installation

View File

@@ -12,52 +12,36 @@ Cup's CLI provides the `cup check` command.
```ansi
$ cup check
✓ Done!
~ Local images
╭─────────────────────────────────────────┬──────────────────────────────────┬─────────╮
│Reference │Status │Time (ms)│
├─────────────────────────────────────────┼──────────────────────────────────┼─────────┤
│postgres:15-alpine │Major update (15 → 17) │788 │
│ghcr.io/immich-app/immich-server:v1.118.2│Minor update (1.118.2 → 1.127.0) │2294 │
│ollama/ollama:0.4.1 │Minor update (0.4.1 → 0.5.12) │533 │
│adguard/adguardhome:v0.107.52 │Patch update (0.107.52 → 0.107.57)│1738 │
│jc21/nginx-proxy-manager:latest │Up to date │583 │
│louislam/uptime-kuma:1 │Up to date │793 │
│moby/buildkit:buildx-stable-1 │Up to date │600 │
│tecnativa/docker-socket-proxy:latest │Up to date │564 │
ubuntu:latest │Up to date │585 │
│wagoodman/dive:latest │Up to date │585 │
│rolebot:latest │Unknown │174 │
╰─────────────────────────────────────────┴──────────────────────────────────┴─────────╯
 INFO ✨ Checked 11 images in 8312ms

mysql:8.0 Major update
node:20 Major update
postgres:16-alpine Major update
rust:1.80.1-alpine Minor update
redis:7.4.0 Patch update
nginx:alpine Update available
redis:alpine Update available
ubuntu:latest Update available
node:iron Up to date
2fauth/2fauth:latest Up to date
c1982/sdns:latest Up to date
registry.acme.com/acme-server:latest Unknown
INFO ✨ Checked 58 images in 3772ms
```
### Check for updates to specific images
```ansi
$ cup check node:latest
✓ Done!
~ Local images
╭───────────┬────────────────┬─────────╮
│Reference │Status │Time (ms)│
├───────────┼────────────────┼─────────┤
│node:latest│Update available│788 │
╰───────────┴────────────────┴─────────╯
 INFO ✨ Checked 1 images in 310ms
$ cup check node:latest
node:latest Update available
INFO ✨ Checked 1 images in 1310ms
```
```ansi
$ cup check nextcloud:30 postgres:14 mysql:8.0
✓ Done!
~ Local images
╭────────────┬────────────────────────┬─────────╮
│Reference │Status │Time (ms)│
├────────────┼────────────────────────┼─────────┤
│postgres:14 │Major update (14 → 17) │195 │
│mysql:8.0 │Major update (8.0 → 9.2)│382 │
│nextcloud:30│Up to date │585 │
╰────────────┴────────────────────────┴─────────╯
 INFO ✨ Checked 3 images in 769ms
nextcloud:30 Update available
postgres:14 Update available
mysql:8.0 Up to date
INFO ✨ Checked 3 images in 1769ms
```
## Enable icons
@@ -78,8 +62,7 @@ $ cup check -r
```
<Callout emoji="⚠️">
When parsing Cup's output, capture only `stdout`, otherwise you might not get
valid JSON (if there are warnings)
When parsing Cup's output, capture only `stdout`, otherwise you might not get valid JSON (if there are warnings)
</Callout>
## Usage with Docker

View File

@@ -7,7 +7,7 @@ import { Cards } from "nextra/components";
# Usage
You can use Cup in 2 different ways. As a CLI or as a server. You can learn more about each mode on its corresponding page
You can use Cup in 2 different ways. As a CLI or as a server. You can learn more about each mode in its corresponding page
<Cards>
<Cards.Card icon={<IconTerminal />} title="CLI" href="/docs/usage/cli" />

View File

@@ -8,13 +8,13 @@ The server provides the `cup serve` command.
```ansi
$ cup serve
 INFO Starting server, please wait...
 INFO ✨ Checked 8 images in 8862ms
 INFO Ready to start!
 HTTP GET / 200 in 0ms
 HTTP GET /assets/index.js 200 in 3ms
 HTTP GET /assets/index.css 200 in 0ms
 HTTP GET /api/v3/json 200 in 0ms
INFO Starting server, please wait...
INFO ✨ Checked 8 images in 8862ms
INFO Ready to start!
HTTP GET / 200 in 0ms
HTTP GET /assets/index.js 200 in 3ms
HTTP GET /assets/index.css 200 in 0ms
HTTP GET /api/v3/json 200 in 0ms
```
This will launch the server on port `8000`. To access it, visit `http://<YOUR_IP>:8000` (replace `<YOUR_IP>` with the IP address of the machine running Cup.)
@@ -29,13 +29,13 @@ Pass the `-p` argument with the port you want to use
```ansi
$ cup serve -p 9000
 INFO Starting server, please wait...
 INFO ✨ Checked 8 images in 8862ms
 INFO Ready to start!
 HTTP GET / 200 in 0ms
 HTTP GET /assets/index.js 200 in 3ms
 HTTP GET /assets/index.css 200 in 0ms
 HTTP GET /api/v3/json 200 in 0ms
INFO Starting server, please wait...
INFO ✨ Checked 8 images in 8862ms
INFO Ready to start!
HTTP GET / 200 in 0ms
HTTP GET /assets/index.js 200 in 3ms
HTTP GET /assets/index.css 200 in 0ms
HTTP GET /api/v3/json 200 in 0ms
```
## Usage with Docker

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -17,7 +17,7 @@ async fn get_remote_updates(ctx: &Context, client: &Client, refresh: bool) -> Ve
let handles: Vec<_> = ctx.config.servers
.iter()
.map(|(name, url)| async move {
.map(|(name, url)| async {
let base_url = if url.starts_with("http://") || url.starts_with("https://") {
format!("{}/api/v3/", url.trim_end_matches('/'))
} else {
@@ -47,7 +47,6 @@ async fn get_remote_updates(ctx: &Context, client: &Client, refresh: bool) -> Ve
return Vec::new();
}
let json = parse_json(&get_response_body(response).await);
ctx.logger.debug(format!("JSON response for {}: {}", name, json));
if let Some(updates) = json["images"].as_array() {
let mut server_updates: Vec<Update> = updates
.iter()
@@ -58,7 +57,6 @@ async fn get_remote_updates(ctx: &Context, client: &Client, refresh: bool) -> Ve
update.server = Some(name.clone());
update.status = update.get_status();
}
ctx.logger.debug(format!("Updates for {}: {:#?}", name, server_updates));
return server_updates;
}
@@ -120,16 +118,6 @@ pub async fn get_updates(
.iter()
.map(|image| &image.parts.registry)
.unique()
.filter(|&registry| match ctx.config.registries.get(registry) {
Some(config) => {
if config.ignore {
false
} else {
true
}
}
None => true,
})
.collect::<Vec<&String>>();
// Create request client. All network requests share the same client for better performance.
@@ -148,7 +136,7 @@ pub async fn get_updates(
// Retrieve an authentication token (if required) for each registry.
let mut tokens: FxHashMap<&str, Option<String>> = FxHashMap::default();
for registry in registries.clone() {
for registry in registries {
let credentials = if let Some(registry_config) = ctx.config.registries.get(registry) {
&registry_config.authentication
} else {
@@ -173,11 +161,24 @@ pub async fn get_updates(
ctx.logger.debug(format!("Tokens: {:?}", tokens));
let ignored_registries = ctx
.config
.registries
.iter()
.filter_map(|(registry, registry_config)| {
if registry_config.ignore {
Some(registry)
} else {
None
}
})
.collect::<Vec<&String>>();
let mut handles = Vec::with_capacity(images.len());
// Loop through images check for updates
for image in &images {
let is_ignored = !registries.contains(&&image.parts.registry)
let is_ignored = ignored_registries.contains(&&image.parts.registry)
|| ctx
.config
.images

View File

@@ -95,10 +95,6 @@ impl Client {
let message = format!("{} {}: Connection timed out!", method, url);
self.ctx.logger.warn(&message);
Err(message)
} else if error.is_middleware() {
let message = format!("{} {}: Connection failed after 3 retries!", method, url);
self.ctx.logger.warn(&message);
Err(message)
} else {
error!(
"{} {}: Unexpected error: {}",

View File

@@ -1,7 +1,8 @@
use std::fmt::Display;
/// Enum for image status
#[derive(Ord, Eq, PartialEq, PartialOrd, Clone, Debug)]
#[derive(Ord, Eq, PartialEq, PartialOrd, Clone)]
#[cfg_attr(test, derive(Debug))]
pub enum Status {
UpdateMajor,
UpdateMinor,

View File

@@ -2,8 +2,8 @@ use serde::{ser::SerializeStruct, Deserialize, Serialize};
use super::{parts::Parts, status::Status};
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Default))]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(PartialEq, Debug, Default))]
pub struct Update {
pub reference: String,
pub parts: Parts,
@@ -14,16 +14,16 @@ pub struct Update {
pub status: Status,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Default))]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(PartialEq, Debug, Default))]
pub struct UpdateResult {
pub has_update: Option<bool>,
pub info: UpdateInfo,
pub error: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Default))]
#[derive(Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(PartialEq, Debug, Default))]
#[serde(untagged)]
pub enum UpdateInfo {
#[cfg_attr(test, default)]
@@ -32,8 +32,8 @@ pub enum UpdateInfo {
Digest(DigestUpdateInfo),
}
#[derive(Deserialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Deserialize, Clone)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct VersionUpdateInfo {
pub version_update_type: String,
pub new_tag: String,
@@ -41,8 +41,8 @@ pub struct VersionUpdateInfo {
pub new_version: String,
}
#[derive(Deserialize, Clone, Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Deserialize, Clone)]
#[cfg_attr(test, derive(PartialEq, Debug))]
pub struct DigestUpdateInfo {
pub local_digests: Vec<String>,
pub remote_digest: Option<String>,
@@ -53,12 +53,10 @@ impl Serialize for VersionUpdateInfo {
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("VersionUpdateInfo", 5)?;
let mut state = serializer.serialize_struct("VersionUpdateInfo", 3)?;
let _ = state.serialize_field("type", "version");
let _ = state.serialize_field("version_update_type", &self.version_update_type);
let _ = state.serialize_field("new_tag", &self.new_tag);
let _ = state.serialize_field("current_version", &self.current_version);
let _ = state.serialize_field("new_version", &self.new_version);
state.end()
}
}

Binary file not shown.

View File

@@ -225,97 +225,182 @@
</div>
<ul>
{% for server in server_ids %}
<li class="mb-4 last:mb-0">
<p
class="my-4 text-lg font-semibold text-{{ theme }}-600 dark:text-{{ theme }}-400 px-6"
>
{% if server == '' %}
Local images
{% else %}
{{ server }}
{% endif %}
</p>
<ul
class="dark:divide-{{theme}}-900 divide-y dark:text-white"
>
{% for image in servers[server] %}
<li
class="flex items-center gap-4 break-all px-6 py-4 text-start hover:bg-{{theme}}-100 hover:dark:bg-{{theme}}-900/50 transition-colors duration-200"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-6 shrink-0 text-{{ theme }}-500"
{% if server == '' %}
<li class="mb-8 last:mb-0">
<ul
class="dark:divide-{{theme}}-900 divide-y dark:text-white"
>
{% for image in servers[server] %}
<li
class="flex items-center gap-4 break-all px-6 py-4 text-start hover:bg-{{theme}}-100 hover:dark:bg-{{theme}}-900/50 transition-colors duration-200"
>
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>
</svg>
<span class="font-mono">{{ image.name }}</span>
{% case image.status %}
{% when 'Up to date' %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="ml-auto text-green-500"
>
<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>
</svg>
{% when 'Unknown' %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-{{ theme }}-500 ml-auto"
>
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>
</svg>
{% else %}
{% case image.status %}
{% when 'Major update' %}
{% assign color = 'text-red-500' %}
{% when 'Minor update' %}
{% assign color = 'text-yellow-500' %}
{% else %}
{% assign color = 'text-blue-500' %}
{% endcase %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="ml-auto {{ color }}"
>
<circle cx="12" cy="12" r="10"/><path d="m16 12-4-4-4 4"/><path d="M12 16V8"/>
</svg>
{% endcase %}
</li>
{% endfor %}
</ul>
</li>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-6 shrink-0 text-{{ theme }}-500"
>
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>
</svg>
<span class="font-mono">{{ image.name }}</span>
{% case image.status %}
{% when 'Up to
date' %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="ml-auto text-green-500"
>
<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>
</svg>
{% when 'Unknown' %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-{{ theme }}-500 ml-auto"
>
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>
</svg>
{% else %}
{% case image.status %}
{% when 'Major update' %}
{% assign color = 'text-red-500' %}
{% when 'Minor
update' %}
{% assign color = 'text-yellow-500' %}
{% else %}
{% assign color = 'text-blue-500' %}
{% endcase %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="ml-auto {{ color }}"
>
<circle cx="12" cy="12" r="10"/><path d="m16 12-4-4-4 4"/><path d="M12 16V8"/>
</svg>
{% endcase %}
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li class="mb-4 last:mb-0">
<p
class="my-4 text-lg font-semibold text-{{ theme }}-600 dark:text-{{ theme }}-400 px-6"
>
{{ server }}
</p>
<ul
class="dark:divide-{{ theme }}-900 divide-y dark:text-white"
>
{% for image in servers[server] %}
<li
class="flex items-center gap-4 break-all px-6 py-4 text-start hover:bg-{{ theme }}-100 hover:dark:bg-{{ theme }}-900/50 transition-colors duration-200"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-6 shrink-0"
>
<path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/>
</svg>
{{ image.name }}
{% case image.status %}
{% when 'Up to
date' %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="ml-auto text-green-500"
>
<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>
</svg>
{% when 'Unknown' %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-{{ theme }}-500 ml-auto"
>
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>
</svg>
{% else %}
{% case image.status %}
{% when 'Major update' %}
{% assign color = 'text-red-500' %}
{% when 'Minor
update' %}
{% assign color = 'text-yellow-500' %}
{% else %}
{% assign color = 'text-blue-500' %}
{% endcase %}
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="ml-auto {{ color }}"
>
<circle cx="12" cy="12" r="10"/><path d="m16 12-4-4-4 4"/><path d="M12 16V8"/>
</svg>
{% endcase %}
</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endfor %}
</ul>
</div>

View File

@@ -13,6 +13,7 @@
"dependencies": {
"@headlessui/react": "^2.1.10",
"@radix-ui/react-tooltip": "^1.1.2",
"caniuse-lite": "^1.0.30001698",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"lucide-react": "^0.475.0",

View File

@@ -38,7 +38,6 @@ export default function Image({ data }: { data: Image }) {
data.result.info?.type == "version"
? data.reference.split(":")[0] + ":" + data.result.info.new_tag
: data.reference;
const info = getInfo(data)!;
let url: string | null = null;
if (clickable_registries.includes(data.parts.registry)) {
switch (data.parts.registry) {
@@ -58,12 +57,7 @@ export default function Image({ data }: { data: Image }) {
>
<Box className={`size-6 shrink-0 text-${theme}-500`} />
<span className="font-mono">{data.reference}</span>
<WithTooltip
text={info.description}
className={`ml-auto size-6 shrink-0 ${info.color}`}
>
<info.icon />
</WithTooltip>
<Icon data={data} />
</li>
</button>
<Dialog open={open} onClose={setOpen} className="relative z-10">
@@ -119,8 +113,7 @@ export default function Image({ data }: { data: Image }) {
</button>
</div>
<div className="flex items-center gap-3">
<info.icon className={`size-6 shrink-0 ${info.color}`} />
{info.description}
<DialogIcon data={data} />
</div>
<div className="flex items-center gap-3">
<Timer className="size-6 shrink-0 text-gray-500" />
@@ -171,54 +164,118 @@ export default function Image({ data }: { data: Image }) {
);
}
function getInfo(data: Image):
| {
color: string;
icon: typeof HelpCircle;
description: string;
}
| undefined {
function Icon({ data }: { data: Image }) {
switch (data.result.has_update) {
case null:
return {
color: "text-gray-500",
icon: HelpCircle,
description: "Unknown",
};
return (
<WithTooltip
text="Unknown"
className="ml-auto size-6 shrink-0 text-gray-500"
>
<HelpCircle />
</WithTooltip>
);
case false:
return {
color: "text-green-500",
icon: CircleCheck,
description: "Up to date",
};
return (
<WithTooltip
text="Up to date"
className="ml-auto size-6 shrink-0 text-green-500"
>
<CircleCheck />
</WithTooltip>
);
case true:
if (data.result.info?.type === "version") {
switch (data.result.info.version_update_type) {
case "major":
return {
color: "text-red-500",
icon: CircleArrowUp,
description: "Major update",
};
return (
<WithTooltip
text="Major Update"
className="ml-auto size-6 shrink-0 text-red-500"
>
<CircleArrowUp />
</WithTooltip>
);
case "minor":
return {
color: "text-yellow-500",
icon: CircleArrowUp,
description: "Minor update",
};
return (
<WithTooltip
text="Minor Update"
className="ml-auto size-6 shrink-0 text-yellow-500"
>
<CircleArrowUp />
</WithTooltip>
);
case "patch":
return {
color: "text-blue-500",
icon: CircleArrowUp,
description: "Patch update",
};
return (
<WithTooltip
text="Patch Update"
className="ml-auto size-6 shrink-0 text-blue-500"
>
<CircleArrowUp />
</WithTooltip>
);
}
} else if (data.result.info?.type === "digest") {
return {
color: "text-blue-500",
icon: CircleArrowUp,
description: "Update available",
};
return (
<WithTooltip
text="Update available"
className="ml-auto size-6 shrink-0 text-blue-500"
>
<CircleArrowUp />
</WithTooltip>
);
}
}
}
function DialogIcon({ data }: { data: Image }) {
switch (data.result.has_update) {
case null:
return (
<>
<HelpCircle className="size-6 shrink-0 text-gray-500" />
Unknown
</>
);
case false:
return (
<>
<CircleCheck className="size-6 shrink-0 text-green-500" />
Up to date
</>
);
case true:
if (data.result.info?.type === "version") {
switch (data.result.info.version_update_type) {
case "major":
return (
<>
<CircleArrowUp className="size-6 shrink-0 text-red-500" />
Major update
</>
);
case "minor":
return (
<>
<CircleArrowUp className="size-6 shrink-0 text-yellow-500" />
Minor update
</>
);
case "patch":
return (
<>
<CircleArrowUp className="size-6 shrink-0 text-blue-500" />
Patch update
</>
);
}
} else if (data.result.info?.type === "digest") {
return (
<>
<CircleArrowUp className="size-6 shrink-0 text-blue-500" />
Update available
</>
);
}
}
}

View File

@@ -1,7 +1,7 @@
import { Data } from "../types";
import Logo from "./Logo";
import { theme } from "../theme";
import { LoaderCircle } from "lucide-react";
import { RefreshCw } from "lucide-react";
export default function Loading({ onLoad }: { onLoad: (data: Data) => void }) {
fetch(
@@ -26,16 +26,9 @@ export default function Loading({ onLoad }: { onLoad: (data: Data) => void }) {
<Logo />
</div>
<div
className={`flex flex-col h-full items-center justify-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`}
>
<div className="flex gap-1 mb-8">
Loading <LoaderCircle className="animate-spin" />
</div>
<p>
If this takes more than a few seconds, there was probably a
problem fetching the data. Please try reloading the page and
report a bug if the problem persists.
</p>
Loading <RefreshCw className="animate-spin" />
</div>
</div>
</div>

View File

@@ -21,7 +21,7 @@ export default function RefreshButton() {
};
return (
<WithTooltip text="Reload">
<button className="group shrink-0" onClick={refresh} disabled={disabled}>
<button className="group" onClick={refresh} disabled={disabled}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
@@ -32,7 +32,7 @@ export default function RefreshButton() {
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="size-6 group-disabled:animate-spin"
className="shrink-0 group-disabled:animate-spin"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M4,11A8.1,8.1 0 0 1 19.5,9M20,5v4h-4" />