mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-12 07:03:48 -05:00
Compare commits
3 Commits
v3.0.0-bet
...
v3-nightly
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3cfa4771eb | ||
|
|
14f3f1d19b | ||
|
|
94a65f204d |
@@ -17,7 +17,7 @@ _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)
|
- 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.1 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.4 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!
|
||||||
|
|
||||||
## Documentation 📘
|
## Documentation 📘
|
||||||
@@ -28,7 +28,7 @@ Take a look at https://sergi0g.github.io/cup/docs!
|
|||||||
|
|
||||||
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 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 `/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 `/api/v3/json` url from the server).
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
Take a sneak peek at what's coming up in future releases on the [roadmap](https://github.com/users/sergi0g/projects/2)!
|
Take a sneak peek at what's coming up in future releases on the [roadmap](https://github.com/users/sergi0g/projects/2)!
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ export function Card({
|
|||||||
description: string;
|
description: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="p-4 bg-white dark:bg-black group">
|
||||||
<Icon className="text-black size-7 dark:text-white inline mr-2" />
|
<Icon className="text-black size-7 group-hover:size-9 dark:text-white inline mr-2 transition-[width,height] duration-200" />
|
||||||
<span className="align-middle text-2xl font-bold text-black dark:text-white">
|
<span className="align-middle text-2xl font-bold text-black dark:text-white">
|
||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
<p className="text-2xl font-semibold text-neutral-500 dark:text-neutral-500">
|
<p className="text-xl font-semibold text-neutral-500 dark:text-neutral-500">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
"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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ export function GridPattern() {
|
|||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<pattern
|
<pattern
|
||||||
@@ -22,7 +22,6 @@ export function GridPattern() {
|
|||||||
<path
|
<path
|
||||||
d={`M.5 ${SIZE}V.5H${SIZE}`}
|
d={`M.5 ${SIZE}V.5H${SIZE}`}
|
||||||
fill="none"
|
fill="none"
|
||||||
strokeDasharray={"4 2"}
|
|
||||||
/>
|
/>
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,28 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import "./styles.css"
|
import "./styles.css";
|
||||||
|
|
||||||
import CopyableCode from "../CopyableCode";
|
|
||||||
import { Browser } from "../Browser";
|
import { Browser } from "../Browser";
|
||||||
import { Card } from "../Card";
|
import { Card } from "../Card";
|
||||||
import {
|
import {
|
||||||
IconAdjustments,
|
IconAdjustments,
|
||||||
IconArrowRight,
|
IconArrowRight,
|
||||||
|
IconBarrierBlockOff,
|
||||||
IconBolt,
|
IconBolt,
|
||||||
IconBraces,
|
|
||||||
IconDevices,
|
|
||||||
IconFeather,
|
IconFeather,
|
||||||
IconLockCheck,
|
IconGitMerge,
|
||||||
|
IconPuzzle,
|
||||||
|
IconServer,
|
||||||
|
IconTerminal,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { GitHubIcon } from "nextra/icons";
|
import { GitHubIcon } from "nextra/icons";
|
||||||
import { GridPattern } from "../GridPattern";
|
import { GridPattern } from "../GridPattern";
|
||||||
import { GradientText } from "../GradientText";
|
import { GradientText } from "../GradientText";
|
||||||
import { Section } from "../Section";
|
|
||||||
import { Steps } from "nextra/components";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative home">
|
<div className="relative home bg-radial-[ellipse_at_center] from-transparent from-20% to-white dark:to-black">
|
||||||
<GridPattern />
|
<GridPattern />
|
||||||
<div className="px-4 pt-16 pb-8 sm:pt-24 lg:px-8">
|
<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">
|
<div className="flex w-full flex-col items-center justify-between">
|
||||||
@@ -37,7 +36,7 @@ export default async function Home() {
|
|||||||
blur={30}
|
blur={30}
|
||||||
/>
|
/>
|
||||||
</h1>
|
</h1>
|
||||||
<h3 className="mx-auto mt-6 max-w-3xl text-center text-xl leading-tight font-medium text-gray-400">
|
<h3 className="mx-auto mt-6 max-w-3xl text-center text-xl leading-tight font-medium text-neutral-500 dark:text-neutral-400">
|
||||||
Cup is a small utility with a big impact. Simplify your
|
Cup is a small utility with a big impact. Simplify your
|
||||||
container management workflow with fast and efficient update
|
container management workflow with fast and efficient update
|
||||||
checking, a full-featured CLI and web interface, and more.
|
checking, a full-featured CLI and web interface, and more.
|
||||||
@@ -54,7 +53,7 @@ export default async function Home() {
|
|||||||
<a
|
<a
|
||||||
href="https://github.com/sergi0g/cup"
|
href="https://github.com/sergi0g/cup"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
Star on GitHub
|
Star on GitHub
|
||||||
<GitHubIcon className="ml-auto size-4 md:size-5" />
|
<GitHubIcon className="ml-auto size-4 md:size-5" />
|
||||||
@@ -66,68 +65,49 @@ export default async function Home() {
|
|||||||
<Browser />
|
<Browser />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Section
|
<div className="bg-white dark:bg-black py-12 px-8 w-full">
|
||||||
title="Powerful at its core."
|
<div className="flex h-full w-full items-center justify-center">
|
||||||
className="bg-gradient-to-r from-red-500 to-amber-500"
|
<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
|
||||||
<Card
|
name="Built for speed."
|
||||||
name="100% Safe Code"
|
icon={IconBolt}
|
||||||
icon={IconLockCheck}
|
description="Cup is written in Rust and every release goes through extensive profiling to squeeze out every last drop of performance."
|
||||||
description="Built with safe Rust and Typescript to ensure security and reliability."
|
/>
|
||||||
/>
|
<Card
|
||||||
<Card
|
name="Configurable."
|
||||||
name="Lightning Fast Performance"
|
icon={IconAdjustments}
|
||||||
icon={IconBolt}
|
description="Make Cup yours with the extensive configuration options available. Customize and tailor it to your needs."
|
||||||
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
|
||||||
<Card
|
name="Extend it."
|
||||||
name="Lightweight"
|
icon={IconPuzzle}
|
||||||
icon={IconFeather}
|
description="JSON output enables you to connect Cup with your favorite integrations, build automations and more."
|
||||||
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."
|
/>
|
||||||
/>
|
<Card
|
||||||
</Section>
|
name="CLI available."
|
||||||
<Section
|
icon={IconTerminal}
|
||||||
title="Efficient, yet flexible."
|
description="Do you like terminals? Cup has a CLI. Check for updates quickly without spinning up a server."
|
||||||
className="bg-gradient-to-r from-blue-500 to-indigo-500"
|
/>
|
||||||
>
|
<Card
|
||||||
<Card
|
name="Multiple servers."
|
||||||
name="JSON output"
|
icon={IconServer}
|
||||||
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!"
|
description="Run multiple Cup instances and effortlessly check on them through one web interface."
|
||||||
icon={IconBraces}
|
/>
|
||||||
/>
|
<Card
|
||||||
<Card
|
name="Unstoppable."
|
||||||
name="Both CLI and web interface"
|
icon={IconBarrierBlockOff}
|
||||||
description="Whether you prefer the command line or the web, Cup runs wherever you choose."
|
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."
|
||||||
icon={IconDevices}
|
/>
|
||||||
/>
|
<Card
|
||||||
<Card
|
name="Lightweight."
|
||||||
name="Configurable"
|
icon={IconFeather}
|
||||||
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."
|
description="No need for a powerful server and endless storage. The tiny 5.4 MB binary won't hog your CPU and memory."
|
||||||
icon={IconAdjustments}
|
/>
|
||||||
/>
|
<Card
|
||||||
</Section>
|
name="Open source."
|
||||||
<div className="relative py-24 border-t border-t-neutral-300 dark:border-t-neutral-600/30 text-black dark:text-white">
|
icon={IconGitMerge}
|
||||||
<GridPattern />
|
description="All source code is publicly available in our GitHub repository. We're looking for contributors!"
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const logo = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const navbar = (
|
const navbar = (
|
||||||
<Navbar logo={logo} projectLink="https://github.com/sergi0g/cup">
|
<Navbar logo={logo} projectLink="https://github.com/sergi0g/cup" chatLink="https://discord.gg/jmh5ctzwNG">
|
||||||
<ThemeSwitch lite className="cursor-pointer" />
|
<ThemeSwitch lite className="cursor-pointer" />
|
||||||
</Navbar>
|
</Navbar>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ async fn get_remote_updates(ctx: &Context, client: &Client, refresh: bool) -> Ve
|
|||||||
|
|
||||||
let handles: Vec<_> = ctx.config.servers
|
let handles: Vec<_> = ctx.config.servers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, url)| async {
|
.map(|(name, url)| async move {
|
||||||
let base_url = if url.starts_with("http://") || url.starts_with("https://") {
|
let base_url = if url.starts_with("http://") || url.starts_with("https://") {
|
||||||
format!("{}/api/v3/", url.trim_end_matches('/'))
|
format!("{}/api/v3/", url.trim_end_matches('/'))
|
||||||
} else {
|
} else {
|
||||||
@@ -47,16 +47,21 @@ async fn get_remote_updates(ctx: &Context, client: &Client, refresh: bool) -> Ve
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let json = parse_json(&get_response_body(response).await);
|
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() {
|
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 {
|
||||||
update.server = Some(name.clone());
|
update.server = Some(name.clone());
|
||||||
update.status = update.get_status();
|
update.status = update.get_status();
|
||||||
}
|
}
|
||||||
|
ctx.logger.debug(format!("Updates for {}: {:#?}", name, server_updates));
|
||||||
return server_updates;
|
return server_updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
/// Enum for image status
|
/// Enum for image status
|
||||||
#[derive(Ord, Eq, PartialEq, PartialOrd, Clone)]
|
#[derive(Ord, Eq, PartialEq, PartialOrd, Clone, Debug)]
|
||||||
#[cfg_attr(test, derive(Debug))]
|
|
||||||
pub enum Status {
|
pub enum Status {
|
||||||
UpdateMajor,
|
UpdateMajor,
|
||||||
UpdateMinor,
|
UpdateMinor,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
|||||||
|
|
||||||
use super::{parts::Parts, status::Status};
|
use super::{parts::Parts, status::Status};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Debug, Default))]
|
#[cfg_attr(test, derive(PartialEq, Default))]
|
||||||
pub struct Update {
|
pub struct Update {
|
||||||
pub reference: String,
|
pub reference: String,
|
||||||
pub parts: Parts,
|
pub parts: Parts,
|
||||||
@@ -14,16 +14,16 @@ pub struct Update {
|
|||||||
pub status: Status,
|
pub status: Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Debug, Default))]
|
#[cfg_attr(test, derive(PartialEq, Default))]
|
||||||
pub struct UpdateResult {
|
pub struct UpdateResult {
|
||||||
pub has_update: Option<bool>,
|
pub has_update: Option<bool>,
|
||||||
pub info: UpdateInfo,
|
pub info: UpdateInfo,
|
||||||
pub error: Option<String>,
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Debug, Default))]
|
#[cfg_attr(test, derive(PartialEq, Default))]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum UpdateInfo {
|
pub enum UpdateInfo {
|
||||||
#[cfg_attr(test, default)]
|
#[cfg_attr(test, default)]
|
||||||
@@ -32,8 +32,8 @@ pub enum UpdateInfo {
|
|||||||
Digest(DigestUpdateInfo),
|
Digest(DigestUpdateInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct VersionUpdateInfo {
|
pub struct VersionUpdateInfo {
|
||||||
pub version_update_type: String,
|
pub version_update_type: String,
|
||||||
pub new_tag: String,
|
pub new_tag: String,
|
||||||
@@ -41,8 +41,8 @@ pub struct VersionUpdateInfo {
|
|||||||
pub new_version: String,
|
pub new_version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq, Debug))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
pub struct DigestUpdateInfo {
|
pub struct DigestUpdateInfo {
|
||||||
pub local_digests: Vec<String>,
|
pub local_digests: Vec<String>,
|
||||||
pub remote_digest: Option<String>,
|
pub remote_digest: Option<String>,
|
||||||
@@ -53,10 +53,12 @@ impl Serialize for VersionUpdateInfo {
|
|||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let mut state = serializer.serialize_struct("VersionUpdateInfo", 3)?;
|
let mut state = serializer.serialize_struct("VersionUpdateInfo", 5)?;
|
||||||
let _ = state.serialize_field("type", "version");
|
let _ = state.serialize_field("type", "version");
|
||||||
let _ = state.serialize_field("version_update_type", &self.version_update_type);
|
let _ = state.serialize_field("version_update_type", &self.version_update_type);
|
||||||
let _ = state.serialize_field("new_tag", &self.new_tag);
|
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()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user