Compare commits
98 Commits
v3.0.2
...
v3-nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cfa4771eb | ||
|
|
14f3f1d19b | ||
|
|
94a65f204d | ||
|
|
8d0da37e36 | ||
|
|
780d7a088d | ||
|
|
bcb9f63735 | ||
|
|
4d691dd5fa | ||
|
|
685219ea62 | ||
|
|
756462cd7c | ||
|
|
f020ac0906 | ||
|
|
4b03a48d88 | ||
|
|
ba1cfac64b | ||
|
|
05d4c7c630 | ||
|
|
cf22ec300f | ||
|
|
5b428dbf67 | ||
|
|
787a730ab5 | ||
|
|
925989fd80 | ||
|
|
5656003058 | ||
|
|
f79d7ff03a | ||
|
|
550fb955a3 | ||
|
|
6ae95bf83b | ||
|
|
2262df0355 | ||
|
|
1beb7dc020 | ||
|
|
a0de565367 | ||
|
|
0314ef2f05 | ||
|
|
f1c8a45122 | ||
|
|
ce3f8176f1 | ||
|
|
8b520182ed | ||
|
|
e8fee79d20 | ||
|
|
24f160803a | ||
|
|
2ef77c9a55 | ||
|
|
a5bbdd0e33 | ||
|
|
b5aa0309ee | ||
|
|
4bbb53cd67 | ||
|
|
3ac6fb57e9 | ||
|
|
ead74dadd6 | ||
|
|
6e6afdb757 | ||
|
|
0c10134829 | ||
|
|
c0c7f7c0e9 | ||
|
|
aeeffaccba | ||
|
|
a1711b7ac8 | ||
|
|
9d628e3ab2 | ||
|
|
d3b18a6587 | ||
|
|
76a812f52f | ||
|
|
fe779c9c4e | ||
|
|
84609d5189 | ||
|
|
ded441cf75 | ||
|
|
0a8295fff4 | ||
|
|
9c8e6ccdea | ||
|
|
f1e1bcbf1c | ||
|
|
31f7bfbbcb | ||
|
|
15eb553e50 | ||
|
|
359147770f | ||
|
|
0a4e302322 | ||
|
|
5ed64c92fd | ||
|
|
6d08d75ac3 | ||
|
|
dc38b84e87 | ||
|
|
09b6880295 | ||
|
|
4f1075b2b2 | ||
|
|
c84270603f | ||
|
|
4aa28f2cc5 | ||
|
|
eadda5f776 | ||
|
|
622b156eed | ||
|
|
dca19b5ae2 | ||
|
|
f6ac43aac0 | ||
|
|
e5e60c4abc | ||
|
|
33a72c8c0d | ||
|
|
e544ef6ca5 | ||
|
|
afc34a0847 | ||
|
|
ce08e00bb4 | ||
|
|
6a77b85141 | ||
|
|
215e88ae0f | ||
|
|
178acfb2f6 | ||
|
|
59894343de | ||
|
|
61bc60493f | ||
|
|
be7d55d126 | ||
|
|
36a3a13c04 | ||
|
|
d85fadfb39 | ||
|
|
0f95be26dc | ||
|
|
0b7e064980 | ||
|
|
9e9bb78db7 | ||
|
|
88d346b480 | ||
|
|
4519c534a1 | ||
|
|
6b83f51749 | ||
|
|
0c3f293fa8 | ||
|
|
d94abecf35 | ||
|
|
c11b5e6432 | ||
|
|
022dc0b2cb | ||
|
|
51609da4ff | ||
|
|
3ed79e69bd | ||
|
|
078a51c4fa | ||
|
|
8d70d7ae4d | ||
|
|
6d45409928 | ||
|
|
bcfb9ef27a | ||
|
|
5c4de36052 | ||
|
|
eda30229e2 | ||
|
|
8fd012efbe | ||
|
|
8ab073d562 |
12
.github/actions/build-image/Dockerfile
vendored
@@ -1,12 +0,0 @@
|
|||||||
FROM --platform=$BUILDPLATFORM alpine AS builder
|
|
||||||
|
|
||||||
ARG TARGETARCH
|
|
||||||
ARG TARGETOS
|
|
||||||
|
|
||||||
COPY binaries/* /
|
|
||||||
RUN mv cup-$TARGETOS-$TARGETARCH cup
|
|
||||||
RUN chmod +x cup
|
|
||||||
|
|
||||||
FROM scratch
|
|
||||||
COPY --from=builder /cup /cup
|
|
||||||
ENTRYPOINT ["/cup"]
|
|
||||||
51
.github/actions/build-image/action.yml
vendored
@@ -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
|
|
||||||
34
.github/workflows/nightly.yml
vendored
@@ -62,24 +62,38 @@ jobs:
|
|||||||
cup-linux-arm64
|
cup-linux-arm64
|
||||||
|
|
||||||
build-image:
|
build-image:
|
||||||
needs:
|
needs: get-tag
|
||||||
- get-tag
|
|
||||||
- build-binaries
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
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:
|
with:
|
||||||
tags: |
|
registry: ghcr.io
|
||||||
${{ needs.get-tag.outputs.tag }}
|
username: ${{ github.repository_owner }}
|
||||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
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:
|
nightly-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs: [build-binaries, get-tag]
|
||||||
- build-binaries
|
|
||||||
- build-image
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download binaries
|
- name: Download binaries
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
|
|||||||
31
.github/workflows/release.yml
vendored
@@ -60,19 +60,34 @@ jobs:
|
|||||||
cup-linux-arm64
|
cup-linux-arm64
|
||||||
|
|
||||||
build-image:
|
build-image:
|
||||||
needs:
|
needs: get-tag
|
||||||
- get-tag
|
|
||||||
- build-binaries
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
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:
|
with:
|
||||||
tags: |
|
registry: ghcr.io
|
||||||
${{ needs.get-tag.outputs.tag }}
|
username: ${{ github.repository_owner }}
|
||||||
latest
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
gh-token: ${{ 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:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
1157
Cargo.lock
generated
10
Cargo.toml
@@ -1,15 +1,15 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cup"
|
name = "cup"
|
||||||
version = "3.0.2"
|
version = "3.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.7", features = ["derive"] }
|
clap = { version = "4.5.7", features = ["derive"] }
|
||||||
indicatif = { version = "0.17.8", optional = true }
|
indicatif = { version = "0.17.8", optional = true }
|
||||||
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
|
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 }
|
liquid = { version = "0.26.6", optional = true }
|
||||||
bollard = "0.18.1"
|
bollard = "0.16.1"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
http-auth = { version = "0.1.9", default-features = false }
|
http-auth = { version = "0.1.9", default-features = false }
|
||||||
termsize = { version = "0.1.8", optional = true }
|
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 }
|
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"] }
|
reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls"] }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
reqwest-retry = "0.7.0"
|
reqwest-retry = "0.6.1"
|
||||||
reqwest-middleware = "0.3.3"
|
reqwest-middleware = "0.3.3"
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
http-link = "1.0.1"
|
http-link = "1.0.1"
|
||||||
itertools = "0.14.0"
|
itertools = "0.13.0"
|
||||||
serde_json = "1.0.133"
|
serde_json = "1.0.133"
|
||||||
serde = "1.0.215"
|
serde = "1.0.215"
|
||||||
tokio-cron-scheduler = { version = "0.13.0", default-features = false, optional = true }
|
tokio-cron-scheduler = { version = "0.13.0", default-features = false, optional = true }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN ~/.bun/bin/bun install
|
|||||||
RUN ~/.bun/bin/bun run build
|
RUN ~/.bun/bin/bun run build
|
||||||
|
|
||||||
### Build Cup ###
|
### Build Cup ###
|
||||||
FROM rust:1-alpine AS build
|
FROM rust:1.80.1-alpine AS build
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
RUN apk add musl-dev
|
RUN apk add musl-dev
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ _If you like this project and/or use Cup, please consider starring the project
|
|||||||
|
|
||||||
## Documentation 📘
|
## Documentation 📘
|
||||||
|
|
||||||
Take a look at https://cup.sergi0g.dev/docs!
|
Take a look at https://sergi0g.github.io/cup/docs!
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ Here are some ideas to get you started:
|
|||||||
- Help optimize Cup and make it even better!
|
- Help optimize Cup and make it even better!
|
||||||
- Add more features to the web UI
|
- 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
|
## Support
|
||||||
|
|
||||||
|
|||||||
BIN
docs/bun.lockb
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build && pagefind --site out --output-path out/_pagefind",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"fmt": "bun prettier --write ."
|
"fmt": "bun prettier --write ."
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-config-next": "15.1.5",
|
"eslint-config-next": "15.1.5",
|
||||||
"pagefind": "^1.3.0",
|
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 136 KiB |
15
docs/src/app/assets/GitHubIcon.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 233 KiB |
@@ -35,9 +35,9 @@ services:
|
|||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /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
|
```yaml
|
||||||
user: "1000:999"
|
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!
|
|
||||||
@@ -36,7 +36,7 @@ services:
|
|||||||
homepage.widget.mappings[1].field.metrics: up_to_date
|
homepage.widget.mappings[1].field.metrics: up_to_date
|
||||||
homepage.widget.mappings[1].format: number
|
homepage.widget.mappings[1].format: number
|
||||||
homepage.widget.mappings[2].label: Updates
|
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
|
homepage.widget.mappings[2].format: number
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ widget:
|
|||||||
label: Up to date
|
label: Up to date
|
||||||
format: number
|
format: number
|
||||||
- field:
|
- field:
|
||||||
metrics: updates_available
|
metrics: update_available
|
||||||
label: Available updates
|
label: Available updates
|
||||||
format: number
|
format: number
|
||||||
- field:
|
- field:
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -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:
|
Some registries (or specific images) may require you to be authenticated. For those, you can modify `cup.json` like this:
|
||||||
|
|
||||||
```jsonc
|
```json
|
||||||
{
|
{
|
||||||
"registries": {
|
"registries": {
|
||||||
"<YOUR_REGISTRY_DOMAIN_1>": {
|
"<YOUR_REGISTRY_DOMAIN_1>": {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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.
|
|
||||||
@@ -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!
|
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
IconLockOpen,
|
IconLockOpen,
|
||||||
IconKey,
|
IconKey,
|
||||||
IconPlug,
|
IconPlug,
|
||||||
IconServer,
|
IconServer
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@@ -71,21 +71,12 @@ Here's a full example:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/sergi0g/cup/main/cup.schema.json",
|
"authentication": {
|
||||||
"version": 3,
|
"ghcr.io": "<YOUR_TOKEN_HERE>",
|
||||||
"images": {
|
"registry-1.docker.io": "<YOUR_TOKEN_HERE>"
|
||||||
"exclude": ["ghcr.io/immich-app/immich-machine-learning"],
|
|
||||||
"extra": ["ghcr.io/sergi0g/cup:v3.0.0"]
|
|
||||||
},
|
},
|
||||||
"registries": {
|
"theme": "blue",
|
||||||
"myregistry.com": {
|
"insecure_registries": ["localhost:5000", "my-insecure-registry.example.com"]
|
||||||
"authentication": "<YOUR_TOKEN_HERE>"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"servers": {
|
|
||||||
"Raspberry Pi": "https://server.local:8000"
|
|
||||||
},
|
|
||||||
"theme": "blue"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ To solve this problem, you can specify exceptions in your `cup.json`.
|
|||||||
|
|
||||||
Here's what it looks like:
|
Here's what it looks like:
|
||||||
|
|
||||||
```jsonc
|
```json
|
||||||
{
|
{
|
||||||
"registries": {
|
"registries": {
|
||||||
"<INSECURE_REGISTRY_1>": {
|
"<INSECURE_REGISTRY_1>": {
|
||||||
|
|||||||
@@ -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:
|
Just add something like this to your config:
|
||||||
|
|
||||||
```jsonc
|
```json
|
||||||
{
|
{
|
||||||
"servers": {
|
"servers": {
|
||||||
"Cool server 1": "http://your-other-server-running-cup:8000",
|
"Cool server 1": "http://your-other-server-running-cup:8000",
|
||||||
|
|||||||
@@ -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:
|
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"
|
"socket": "/run/user/1000/podman/podman.sock"
|
||||||
// Other options
|
// 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):
|
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"
|
"socket": "tcp://localhost:2375"
|
||||||
// Other options
|
// Other options
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Available options are `default` and `blue`.
|
|||||||
|
|
||||||
Here's an example:
|
Here's an example:
|
||||||
|
|
||||||
```jsonc
|
```json
|
||||||
{
|
{
|
||||||
"theme": "blue"
|
"theme": "blue"
|
||||||
// Other options
|
// Other options
|
||||||
|
|||||||
@@ -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)
|
- 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.
|
- 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.
|
- 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!
|
- 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
|
# Installation
|
||||||
|
|||||||
@@ -12,52 +12,36 @@ Cup's CLI provides the `cup check` command.
|
|||||||
|
|
||||||
```ansi
|
```ansi
|
||||||
$ cup check
|
$ cup check
|
||||||
[32;1m✓[0m Done!
|
[38;5;1m
|
||||||
[90;1m~ Local images[0m
|
mysql:8.0 Major update
|
||||||
[90;1m╭─────────────────────────────────────────┬──────────────────────────────────┬─────────╮[0m
|
node:20 Major update
|
||||||
[90;1m│[36;1mReference [90;1m│[36;1mStatus [90;1m│[36;1mTime (ms)[90;1m│[0m
|
postgres:16-alpine Major update[0m[38;5;3m
|
||||||
[90;1m├─────────────────────────────────────────┼──────────────────────────────────┼─────────┤[0m
|
rust:1.80.1-alpine Minor update[0m[38;5;12m
|
||||||
[90;1m│[0mpostgres:15-alpine [90;1m│[0m[31mMajor update (15 → 17) [0m[90;1m│[0m788 [90;1m│[0m
|
redis:7.4.0 Patch update
|
||||||
[90;1m│[0mghcr.io/immich-app/immich-server:v1.118.2[90;1m│[0m[33mMinor update (1.118.2 → 1.127.0) [0m[90;1m│[0m2294 [90;1m│[0m
|
nginx:alpine Update available
|
||||||
[90;1m│[0mollama/ollama:0.4.1 [90;1m│[0m[33mMinor update (0.4.1 → 0.5.12) [0m[90;1m│[0m533 [90;1m│[0m
|
redis:alpine Update available
|
||||||
[90;1m│[0madguard/adguardhome:v0.107.52 [90;1m│[0m[34mPatch update (0.107.52 → 0.107.57)[0m[90;1m│[0m1738 [90;1m│[0m
|
ubuntu:latest Update available[0m[38;5;2m
|
||||||
[90;1m│[0mjc21/nginx-proxy-manager:latest [90;1m│[0m[32mUp to date [0m[90;1m│[0m583 [90;1m│[0m
|
node:iron Up to date
|
||||||
[90;1m│[0mlouislam/uptime-kuma:1 [90;1m│[0m[32mUp to date [0m[90;1m│[0m793 [90;1m│[0m
|
2fauth/2fauth:latest Up to date
|
||||||
[90;1m│[0mmoby/buildkit:buildx-stable-1 [90;1m│[0m[32mUp to date [0m[90;1m│[0m600 [90;1m│[0m
|
c1982/sdns:latest Up to date[0m[38;5;8m
|
||||||
[90;1m│[0mtecnativa/docker-socket-proxy:latest [90;1m│[0m[32mUp to date [0m[90;1m│[0m564 [90;1m│[0m
|
registry.acme.com/acme-server:latest Unknown
|
||||||
[90;1m│[0mubuntu:latest [90;1m│[0m[32mUp to date [0m[90;1m│[0m585 [90;1m│[0m
|
[36;1mINFO [0m✨ Checked 58 images in 3772ms
|
||||||
[90;1m│[0mwagoodman/dive:latest [90;1m│[0m[32mUp to date [0m[90;1m│[0m585 [90;1m│[0m
|
|
||||||
[90;1m│[0mrolebot:latest [90;1m│[0m[90mUnknown [0m[90;1m│[0m174 [90;1m│[0m
|
|
||||||
[90;1m╰─────────────────────────────────────────┴──────────────────────────────────┴─────────╯[0m
|
|
||||||
[36;1m INFO[0m ✨ Checked 11 images in 8312ms
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Check for updates to specific images
|
### Check for updates to specific images
|
||||||
|
|
||||||
```ansi
|
```ansi
|
||||||
$ cup check node:latest
|
$ cup check node:latest[38;5;12m
|
||||||
[32;1m✓[0m Done!
|
node:latest Update available
|
||||||
[90;1m~ Local images[0m
|
[36;1mINFO [0m✨ Checked 1 images in 1310ms
|
||||||
[90;1m╭───────────┬────────────────┬─────────╮[0m
|
|
||||||
[90;1m│[36;1mReference [90;1m│[36;1mStatus [90;1m│[36;1mTime (ms)[90;1m│[0m
|
|
||||||
[90;1m├───────────┼────────────────┼─────────┤[0m
|
|
||||||
[90;1m│[0mnode:latest[90;1m│[0m[34mUpdate available[0m[90;1m│[0m788 [90;1m│[0m
|
|
||||||
[90;1m╰───────────┴────────────────┴─────────╯[0m
|
|
||||||
[36;1m INFO[0m ✨ Checked 1 images in 310ms
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```ansi
|
```ansi
|
||||||
$ cup check nextcloud:30 postgres:14 mysql:8.0[38;5;12m
|
$ cup check nextcloud:30 postgres:14 mysql:8.0[38;5;12m
|
||||||
[32;1m✓[0m Done!
|
nextcloud:30 Update available
|
||||||
[90;1m~ Local images[0m
|
postgres:14 Update available[38;5;2m
|
||||||
[90;1m╭────────────┬────────────────────────┬─────────╮[0m
|
mysql:8.0 Up to date
|
||||||
[90;1m│[36;1mReference [90;1m│[36;1mStatus [90;1m│[36;1mTime (ms)[90;1m│[0m
|
[36;1mINFO [0m✨ Checked 3 images in 1769ms
|
||||||
[90;1m├────────────┼────────────────────────┼─────────┤[0m
|
|
||||||
[90;1m│[0mpostgres:14 [90;1m│[0m[31mMajor update (14 → 17) [0m[90;1m│[0m195 [90;1m│[0m
|
|
||||||
[90;1m│[0mmysql:8.0 [90;1m│[0m[31mMajor update (8.0 → 9.2)[0m[90;1m│[0m382 [90;1m│[0m
|
|
||||||
[90;1m│[0mnextcloud:30[90;1m│[0m[32mUp to date [0m[90;1m│[0m585 [90;1m│[0m
|
|
||||||
[90;1m╰────────────┴────────────────────────┴─────────╯[0m
|
|
||||||
[36;1m INFO[0m ✨ Checked 3 images in 769ms
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Enable icons
|
## Enable icons
|
||||||
@@ -78,8 +62,7 @@ $ cup check -r
|
|||||||
```
|
```
|
||||||
|
|
||||||
<Callout emoji="⚠️">
|
<Callout emoji="⚠️">
|
||||||
When parsing Cup's output, capture only `stdout`, otherwise you might not get
|
When parsing Cup's output, capture only `stdout`, otherwise you might not get valid JSON (if there are warnings)
|
||||||
valid JSON (if there are warnings)
|
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
## Usage with Docker
|
## Usage with Docker
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Cards } from "nextra/components";
|
|||||||
|
|
||||||
# Usage
|
# 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>
|
||||||
<Cards.Card icon={<IconTerminal />} title="CLI" href="/docs/usage/cli" />
|
<Cards.Card icon={<IconTerminal />} title="CLI" href="/docs/usage/cli" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 90 KiB |
32
src/check.rs
@@ -51,7 +51,10 @@ async fn get_remote_updates(ctx: &Context, client: &Client, refresh: bool) -> Ve
|
|||||||
if let Some(updates) = json["images"].as_array() {
|
if let Some(updates) = json["images"].as_array() {
|
||||||
let mut server_updates: Vec<Update> = updates
|
let mut server_updates: Vec<Update> = updates
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|img| serde_json::from_value(img.clone()).ok())
|
.filter_map(|img| match serde_json::from_value(img.clone()) {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {dbg!(e);None}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
// Add server origin to each image
|
// Add server origin to each image
|
||||||
for update in &mut server_updates {
|
for update in &mut server_updates {
|
||||||
@@ -120,16 +123,6 @@ pub async fn get_updates(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|image| &image.parts.registry)
|
.map(|image| &image.parts.registry)
|
||||||
.unique()
|
.unique()
|
||||||
.filter(|®istry| match ctx.config.registries.get(registry) {
|
|
||||||
Some(config) => {
|
|
||||||
if config.ignore {
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => true,
|
|
||||||
})
|
|
||||||
.collect::<Vec<&String>>();
|
.collect::<Vec<&String>>();
|
||||||
|
|
||||||
// Create request client. All network requests share the same client for better performance.
|
// Create request client. All network requests share the same client for better performance.
|
||||||
@@ -148,7 +141,7 @@ pub async fn get_updates(
|
|||||||
|
|
||||||
// Retrieve an authentication token (if required) for each registry.
|
// Retrieve an authentication token (if required) for each registry.
|
||||||
let mut tokens: FxHashMap<&str, Option<String>> = FxHashMap::default();
|
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) {
|
let credentials = if let Some(registry_config) = ctx.config.registries.get(registry) {
|
||||||
®istry_config.authentication
|
®istry_config.authentication
|
||||||
} else {
|
} else {
|
||||||
@@ -173,11 +166,24 @@ pub async fn get_updates(
|
|||||||
|
|
||||||
ctx.logger.debug(format!("Tokens: {:?}", tokens));
|
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());
|
let mut handles = Vec::with_capacity(images.len());
|
||||||
|
|
||||||
// Loop through images check for updates
|
// Loop through images check for updates
|
||||||
for image in &images {
|
for image in &images {
|
||||||
let is_ignored = !registries.contains(&&image.parts.registry)
|
let is_ignored = ignored_registries.contains(&&image.parts.registry)
|
||||||
|| ctx
|
|| ctx
|
||||||
.config
|
.config
|
||||||
.images
|
.images
|
||||||
|
|||||||
@@ -95,10 +95,6 @@ impl Client {
|
|||||||
let message = format!("{} {}: Connection timed out!", method, url);
|
let message = format!("{} {}: Connection timed out!", method, url);
|
||||||
self.ctx.logger.warn(&message);
|
self.ctx.logger.warn(&message);
|
||||||
Err(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 {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
"{} {}: Unexpected error: {}",
|
"{} {}: Unexpected error: {}",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub fn split(reference: &str) -> (String, String, String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let splits = repository_and_tag.split('@').next().unwrap().split(':').collect::<Vec<&str>>();
|
let splits = repository_and_tag.split(':').collect::<Vec<&str>>();
|
||||||
let (repository, tag) = match splits.len() {
|
let (repository, tag) = match splits.len() {
|
||||||
1 | 2 => {
|
1 | 2 => {
|
||||||
let repository_components = splits[0].split('/').collect::<Vec<&str>>();
|
let repository_components = splits[0].split('/').collect::<Vec<&str>>();
|
||||||
@@ -38,7 +38,7 @@ pub fn split(reference: &str) -> (String, String, String) {
|
|||||||
};
|
};
|
||||||
(repository, tag)
|
(repository, tag)
|
||||||
}
|
}
|
||||||
_ => {dbg!(splits); panic!()},
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
(registry.to_string(), repository, tag.to_string())
|
(registry.to_string(), repository, tag.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
web/bun.lockb
107
web/index.html
@@ -225,16 +225,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{% for server in server_ids %}
|
{% 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 == '' %}
|
{% if server == '' %}
|
||||||
Local images
|
<li class="mb-8 last:mb-0">
|
||||||
{% else %}
|
|
||||||
{{ server }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<ul
|
<ul
|
||||||
class="dark:divide-{{theme}}-900 divide-y dark:text-white"
|
class="dark:divide-{{theme}}-900 divide-y dark:text-white"
|
||||||
>
|
>
|
||||||
@@ -258,7 +250,8 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="font-mono">{{ image.name }}</span>
|
<span class="font-mono">{{ image.name }}</span>
|
||||||
{% case image.status %}
|
{% case image.status %}
|
||||||
{% when 'Up to date' %}
|
{% when 'Up to
|
||||||
|
date' %}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="24"
|
||||||
@@ -292,7 +285,8 @@
|
|||||||
{% case image.status %}
|
{% case image.status %}
|
||||||
{% when 'Major update' %}
|
{% when 'Major update' %}
|
||||||
{% assign color = 'text-red-500' %}
|
{% assign color = 'text-red-500' %}
|
||||||
{% when 'Minor update' %}
|
{% when 'Minor
|
||||||
|
update' %}
|
||||||
{% assign color = 'text-yellow-500' %}
|
{% assign color = 'text-yellow-500' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% assign color = 'text-blue-500' %}
|
{% assign color = 'text-blue-500' %}
|
||||||
@@ -316,6 +310,97 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</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 %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^2.1.10",
|
"@headlessui/react": "^2.1.10",
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
|
"caniuse-lite": "^1.0.30001698",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"lucide-react": "^0.475.0",
|
"lucide-react": "^0.475.0",
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export default function Image({ data }: { data: Image }) {
|
|||||||
data.result.info?.type == "version"
|
data.result.info?.type == "version"
|
||||||
? data.reference.split(":")[0] + ":" + data.result.info.new_tag
|
? data.reference.split(":")[0] + ":" + data.result.info.new_tag
|
||||||
: data.reference;
|
: data.reference;
|
||||||
const info = getInfo(data)!;
|
|
||||||
let url: string | null = null;
|
let url: string | null = null;
|
||||||
if (clickable_registries.includes(data.parts.registry)) {
|
if (clickable_registries.includes(data.parts.registry)) {
|
||||||
switch (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`} />
|
<Box className={`size-6 shrink-0 text-${theme}-500`} />
|
||||||
<span className="font-mono">{data.reference}</span>
|
<span className="font-mono">{data.reference}</span>
|
||||||
<WithTooltip
|
<Icon data={data} />
|
||||||
text={info.description}
|
|
||||||
className={`ml-auto size-6 shrink-0 ${info.color}`}
|
|
||||||
>
|
|
||||||
<info.icon />
|
|
||||||
</WithTooltip>
|
|
||||||
</li>
|
</li>
|
||||||
</button>
|
</button>
|
||||||
<Dialog open={open} onClose={setOpen} className="relative z-10">
|
<Dialog open={open} onClose={setOpen} className="relative z-10">
|
||||||
@@ -119,8 +113,7 @@ export default function Image({ data }: { data: Image }) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<info.icon className={`size-6 shrink-0 ${info.color}`} />
|
<DialogIcon data={data} />
|
||||||
{info.description}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Timer className="size-6 shrink-0 text-gray-500" />
|
<Timer className="size-6 shrink-0 text-gray-500" />
|
||||||
@@ -171,54 +164,118 @@ export default function Image({ data }: { data: Image }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInfo(data: Image):
|
function Icon({ data }: { data: Image }) {
|
||||||
| {
|
|
||||||
color: string;
|
|
||||||
icon: typeof HelpCircle;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
| undefined {
|
|
||||||
switch (data.result.has_update) {
|
switch (data.result.has_update) {
|
||||||
case null:
|
case null:
|
||||||
return {
|
return (
|
||||||
color: "text-gray-500",
|
<WithTooltip
|
||||||
icon: HelpCircle,
|
text="Unknown"
|
||||||
description: "Unknown",
|
className="ml-auto size-6 shrink-0 text-gray-500"
|
||||||
};
|
>
|
||||||
|
<HelpCircle />
|
||||||
|
</WithTooltip>
|
||||||
|
);
|
||||||
case false:
|
case false:
|
||||||
return {
|
return (
|
||||||
color: "text-green-500",
|
<WithTooltip
|
||||||
icon: CircleCheck,
|
text="Up to date"
|
||||||
description: "Up to date",
|
className="ml-auto size-6 shrink-0 text-green-500"
|
||||||
};
|
>
|
||||||
|
<CircleCheck />
|
||||||
|
</WithTooltip>
|
||||||
|
);
|
||||||
case true:
|
case true:
|
||||||
if (data.result.info?.type === "version") {
|
if (data.result.info?.type === "version") {
|
||||||
switch (data.result.info.version_update_type) {
|
switch (data.result.info.version_update_type) {
|
||||||
case "major":
|
case "major":
|
||||||
return {
|
return (
|
||||||
color: "text-red-500",
|
<WithTooltip
|
||||||
icon: CircleArrowUp,
|
text="Major Update"
|
||||||
description: "Major update",
|
className="ml-auto size-6 shrink-0 text-red-500"
|
||||||
};
|
>
|
||||||
|
<CircleArrowUp />
|
||||||
|
</WithTooltip>
|
||||||
|
);
|
||||||
case "minor":
|
case "minor":
|
||||||
return {
|
return (
|
||||||
color: "text-yellow-500",
|
<WithTooltip
|
||||||
icon: CircleArrowUp,
|
text="Minor Update"
|
||||||
description: "Minor update",
|
className="ml-auto size-6 shrink-0 text-yellow-500"
|
||||||
};
|
>
|
||||||
|
<CircleArrowUp />
|
||||||
|
</WithTooltip>
|
||||||
|
);
|
||||||
case "patch":
|
case "patch":
|
||||||
return {
|
return (
|
||||||
color: "text-blue-500",
|
<WithTooltip
|
||||||
icon: CircleArrowUp,
|
text="Patch Update"
|
||||||
description: "Patch update",
|
className="ml-auto size-6 shrink-0 text-blue-500"
|
||||||
};
|
>
|
||||||
|
<CircleArrowUp />
|
||||||
|
</WithTooltip>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (data.result.info?.type === "digest") {
|
} else if (data.result.info?.type === "digest") {
|
||||||
return {
|
return (
|
||||||
color: "text-blue-500",
|
<WithTooltip
|
||||||
icon: CircleArrowUp,
|
text="Update available"
|
||||||
description: "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
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Data } from "../types";
|
import { Data } from "../types";
|
||||||
import Logo from "./Logo";
|
import Logo from "./Logo";
|
||||||
import { theme } from "../theme";
|
import { theme } from "../theme";
|
||||||
import { LoaderCircle } from "lucide-react";
|
import { RefreshCw } from "lucide-react";
|
||||||
|
|
||||||
export default function Loading({ onLoad }: { onLoad: (data: Data) => void }) {
|
export default function Loading({ onLoad }: { onLoad: (data: Data) => void }) {
|
||||||
fetch(
|
fetch(
|
||||||
@@ -26,16 +26,9 @@ export default function Loading({ onLoad }: { onLoad: (data: Data) => void }) {
|
|||||||
<Logo />
|
<Logo />
|
||||||
</div>
|
</div>
|
||||||
<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 <RefreshCw className="animate-spin" />
|
||||||
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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function RefreshButton() {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<WithTooltip text="Reload">
|
<WithTooltip text="Reload">
|
||||||
<button className="group shrink-0" onClick={refresh} disabled={disabled}>
|
<button className="group" onClick={refresh} disabled={disabled}>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="24"
|
||||||
@@ -32,7 +32,7 @@ export default function RefreshButton() {
|
|||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="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 stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M4,11A8.1,8.1 0 0 1 19.5,9M20,5v4h-4" />
|
<path d="M4,11A8.1,8.1 0 0 1 19.5,9M20,5v4h-4" />
|
||||||
|
|||||||