Fix Codacy issues

This commit is contained in:
Anders Roos 2022-11-15 13:34:40 +01:00
parent 5645cd23fe
commit 741f315f14
19 changed files with 163 additions and 135 deletions

View file

@ -23,6 +23,7 @@ func New(token string) *API {
} }
} }
// EnableCors is a middleware that enables CORS for the API
func (api *API) EnableCors(fn http.HandlerFunc) http.HandlerFunc { func (api *API) EnableCors(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Origin", "*")

View file

@ -10,20 +10,18 @@ import (
"github.com/containrrr/watchtower/pkg/types" "github.com/containrrr/watchtower/pkg/types"
) )
// Handler is an HTTP handle for serving list data // Handler is an HTTP handle for serving check data
type Handler struct { type Handler struct {
Path string Path string
Client container.Client Client container.Client
} }
// CheckRequest defines the type for the request data of the Check endpoint type checkRequest struct {
type CheckRequest struct { ContainerID string
ContainerId string
} }
// CheckResponse defines the type for the response data of the Check endpoint type checkResponse struct {
type CheckResponse struct { ContainerID string
ContainerId string
HasUpdate bool HasUpdate bool
NewVersion string NewVersion string
NewVersionCreated string NewVersionCreated string
@ -47,7 +45,7 @@ func (handle *Handler) HandlePost(w http.ResponseWriter, r *http.Request) {
log.Info("Check for update triggered by HTTP API request.") log.Info("Check for update triggered by HTTP API request.")
var request CheckRequest var request checkRequest
err := json.NewDecoder(r.Body).Decode(&request) err := json.NewDecoder(r.Body).Decode(&request)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
@ -57,7 +55,7 @@ func (handle *Handler) HandlePost(w http.ResponseWriter, r *http.Request) {
} }
client := handle.Client client := handle.Client
container, err := client.GetContainer(types.ContainerID(request.ContainerId)) container, err := client.GetContainer(types.ContainerID(request.ContainerID))
if err != nil { if err != nil {
log.Error(err) log.Error(err)
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
@ -74,8 +72,8 @@ func (handle *Handler) HandlePost(w http.ResponseWriter, r *http.Request) {
return return
} }
data := CheckResponse{ data := checkResponse{
ContainerId: request.ContainerId, ContainerID: request.ContainerID,
HasUpdate: stale, HasUpdate: stale,
NewVersion: newestImage.ShortID(), NewVersion: newestImage.ShortID(),
NewVersionCreated: created, NewVersionCreated: created,

View file

@ -17,9 +17,8 @@ type Handler struct {
Client container.Client Client container.Client
} }
// ContainerListEntry defines the type of each container in the response type containerListEntry struct {
type ContainerListEntry struct { ContainerID string
ContainerId string
ContainerName string ContainerName string
ImageName string ImageName string
ImageNameShort string ImageNameShort string
@ -27,9 +26,8 @@ type ContainerListEntry struct {
ImageCreatedDate string ImageCreatedDate string
} }
// ListResponse defines the return type of the List endpoint type listResponse struct {
type ListResponse struct { Containers []containerListEntry
Containers []ContainerListEntry
} }
// New is a factory function creating a new List instance // New is a factory function creating a new List instance
@ -59,11 +57,11 @@ func (handle *Handler) HandleGet(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
} }
data := ListResponse{Containers: []ContainerListEntry{}} data := listResponse{Containers: []containerListEntry{}}
for _, c := range containers { for _, c := range containers {
data.Containers = append(data.Containers, ContainerListEntry{ data.Containers = append(data.Containers, containerListEntry{
ContainerId: c.ID().ShortID(), ContainerID: c.ID().ShortID(),
ContainerName: c.Name()[1:], ContainerName: c.Name()[1:],
ImageName: c.ImageName(), ImageName: c.ImageName(),
ImageNameShort: strings.Split(c.ImageName(), ":")[0], ImageNameShort: strings.Split(c.ImageName(), ":")[0],

View file

@ -41,8 +41,8 @@ func (d *Dashboard) Start() error {
return nil return nil
} }
func (d *Dashboard) templatedHttpHandler(h http.Handler) http.HandlerFunc { func (d *Dashboard) templatedHTTPHandler(h http.Handler) http.HandlerFunc {
const apiUrlTemplate = "%s://%s:%s/%s/" const apiURLTemplate = "%s://%s:%s/%s/"
indexTemplate, err := template.ParseFiles(d.rootDir + "/index.html") indexTemplate, err := template.ParseFiles(d.rootDir + "/index.html")
if err != nil { if err != nil {
log.Error("Error when parsing index template") log.Error("Error when parsing index template")
@ -53,9 +53,9 @@ func (d *Dashboard) templatedHttpHandler(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" { if r.URL.Path == "/" {
hostName := strings.Split(r.Host, ":")[0] hostName := strings.Split(r.Host, ":")[0]
apiUrl := fmt.Sprintf(apiUrlTemplate, d.apiScheme, hostName, d.apiPort, d.apiVersion) apiURL := fmt.Sprintf(apiURLTemplate, d.apiScheme, hostName, d.apiPort, d.apiVersion)
err = indexTemplate.Execute(w, struct{ ApiUrl string }{ err = indexTemplate.Execute(w, struct{ APIURL string }{
ApiUrl: apiUrl, APIURL: apiURL,
}) })
if err != nil { if err != nil {
log.Error("Error when executing index template") log.Error("Error when executing index template")
@ -70,7 +70,7 @@ func (d *Dashboard) templatedHttpHandler(h http.Handler) http.HandlerFunc {
} }
func (d *Dashboard) getHandler() http.Handler { func (d *Dashboard) getHandler() http.Handler {
return d.templatedHttpHandler(http.FileServer(http.Dir(d.rootDir))) return d.templatedHTTPHandler(http.FileServer(http.Dir(d.rootDir)))
} }
func (d *Dashboard) runHTTPServer() { func (d *Dashboard) runHTTPServer() {

View file

@ -1 +1 @@
VITE_API_URL={{.ApiUrl}} VITE_API_URL={{.APIURL}}

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react" import { useEffect, useState } from "react";
import ContainerView from "./components/ContainerView"; import ContainerView from "./components/ContainerView";
import Header from "./components/Header"; import Header from "./components/Header";
import Login from "./components/Login"; import Login from "./components/Login";
@ -20,7 +20,7 @@ const App = () => {
const onLogIn = () => { const onLogIn = () => {
setLoading(false); setLoading(false);
setLoggedIn(true); setLoggedIn(true);
} };
const onLogOut = () => { const onLogOut = () => {
logOut(); logOut();
@ -44,4 +44,4 @@ const App = () => {
); );
}; };
export default App export default App;

View file

@ -1,14 +1,14 @@
import ContainerEntry from "../models/ContainerEntry"; import ContainerModel from "../models/ContainerModel";
import ContainerListEntry from "./ContainerListEntry"; import ContainerListEntry from "./ContainerListEntry";
interface ContainerListProps { interface ContainerListProps {
containers: ContainerEntry[]; containers: ContainerModel[];
onContainerClick: (container: ContainerEntry) => void; onContainerClick: (container: ContainerModel) => void;
} }
const ContainerList = (props: ContainerListProps) => ( const ContainerList = (props: ContainerListProps) => (
<ul className="list-group"> <ul className="list-group">
{props.containers.map(c => <ContainerListEntry {...c} onClick={() => props.onContainerClick(c)} />)} {props.containers.map((c) => <ContainerListEntry {...c} onClick={() => props.onContainerClick(c)} />)}
</ul > </ul >
); );

View file

@ -1,8 +1,12 @@
import ContainerEntry from "../models/ContainerEntry"; import ContainerModel from "../models/ContainerModel";
import ImageInfo from "./ImageInfo"; import ImageInfo from "./ImageInfo";
import SpinnerGrow from "./SpinnerGrow"; import SpinnerGrow from "./SpinnerGrow";
const ContainerListEntry = (props: (ContainerEntry & { onClick: () => void })) => ( interface ContainerListEntryProps extends ContainerModel {
onClick: () => void;
}
const ContainerListEntry = (props: ContainerListEntryProps) => (
<li className="list-group-item d-flex justify-content-between align-items-center container-list-entry" onClick={props.onClick} role="button"> <li className="list-group-item d-flex justify-content-between align-items-center container-list-entry" onClick={props.onClick} role="button">
<div className="ms-1 me-3 container-list-entry-icon"> <div className="ms-1 me-3 container-list-entry-icon">
{props.Selected {props.Selected

View file

@ -1,12 +1,13 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import ContainerEntry from "../models/ContainerEntry" import ContainerModel from "../models/ContainerModel";
import { check, list, update } from "../services/Api"; import { check, list, update } from "../services/Api";
import ContainerList from "./ContainerList"; import ContainerList from "./ContainerList";
import Spinner from "./Spinner"; import Spinner from "./Spinner";
import SpinnerModal from "./SpinnerModal"; import SpinnerModal from "./SpinnerModal";
import { UpdateSelected, UpdateAll, UpdateCheck } from "./UpdateButtons";
interface ViewModel { interface ViewModel {
Containers: ContainerEntry[]; Containers: ContainerModel[];
} }
const ContainerView = () => { const ContainerView = () => {
@ -16,32 +17,28 @@ const ContainerView = () => {
const [hasChecked, setHasChecked] = useState(false); const [hasChecked, setHasChecked] = useState(false);
const [viewModel, setViewModel] = useState<ViewModel>({ Containers: [] }); const [viewModel, setViewModel] = useState<ViewModel>({ Containers: [] });
useEffect(() => {
listContainers();
}, []);
const containers = viewModel.Containers; const containers = viewModel.Containers;
const containersWithUpdates = containers.filter(c => c.HasUpdate); const containersWithUpdates = containers.filter((c) => c.HasUpdate);
const containersWithoutUpdates = containers.filter(c => !c.HasUpdate); const containersWithoutUpdates = containers.filter((c) => !c.HasUpdate);
const hasSelectedContainers = containers.some(c => c.Selected); const hasSelectedContainers = containers.some((c) => c.Selected);
const hasUpdates = containersWithUpdates.length > 0; const hasUpdates = containersWithUpdates.length > 0;
const checkForUpdates = async () => { const checkForUpdates = async () => {
setChecking(true); setChecking(true);
setViewModel(m => ({ setViewModel((m) => ({
...m, ...m,
Containers: m.Containers.map(c => ({ Containers: m.Containers.map((c) => ({
...c, ...c,
IsChecking: true IsChecking: true
})) }))
})); }));
await Promise.all(containers.map(async (c1) => { await Promise.all(containers.map(async (c1) => {
const result = await check(c1.ContainerId); const result = await check(c1.ContainerID);
setViewModel(m => ({ setViewModel((m) => ({
...m, ...m,
Containers: m.Containers.map((c2, i) => (c1.ContainerId === c2.ContainerId ? { Containers: m.Containers.map((c2) => (c1.ContainerID === c2.ContainerID ? {
...c2, ...c2,
...result, ...result,
IsChecking: false IsChecking: false
@ -57,7 +54,17 @@ const ContainerView = () => {
const listContainers = async () => { const listContainers = async () => {
setLoading(true); setLoading(true);
const data = await list(); const data = await list();
setViewModel(data); setViewModel({
Containers: data.Containers.map((c) => ({
...c,
Selected: false,
IsChecking: false,
HasUpdate: false,
IsUpdating: false,
NewVersion: "",
NewVersionCreated: ""
}))
});
setLoading(false); setLoading(false);
setHasChecked(false); setHasChecked(false);
}; };
@ -75,14 +82,14 @@ const ContainerView = () => {
}; };
const updateSelected = async () => { const updateSelected = async () => {
const selectedImages = containers.filter(c => c.Selected === true).map(c => c.ImageNameShort); const selectedImages = containers.filter((c) => c.Selected === true).map((c) => c.ImageNameShort);
await updateImages(selectedImages); await updateImages(selectedImages);
}; };
const onContainerClick = (container: ContainerEntry) => { const onContainerClick = (container: ContainerModel) => {
setViewModel(m => ({ setViewModel((m) => ({
...m, ...m,
Containers: m.Containers.map(c2 => (container.ContainerId === c2.ContainerId ? { Containers: m.Containers.map((c2) => (container.ContainerID === c2.ContainerID ? {
...c2, ...c2,
Selected: !c2.Selected, Selected: !c2.Selected,
} : c2 } : c2
@ -90,6 +97,10 @@ const ContainerView = () => {
})); }));
}; };
useEffect(() => {
listContainers();
}, []);
return ( return (
<main className="mt-5 p-5 d-block"> <main className="mt-5 p-5 d-block">
<SpinnerModal visible={updating} title="Updating containers" message="Please wait..." /> <SpinnerModal visible={updating} title="Updating containers" message="Please wait..." />
@ -127,27 +138,4 @@ const ContainerView = () => {
); );
}; };
interface UpdateButtonProps {
disabled: boolean;
onClick: () => void;
};
const UpdateSelected = (props: UpdateButtonProps) => (
<button type="button" className="btn btn-primary me-2" disabled={props.disabled} onClick={props.onClick}>
<i className="bi bi-arrow-down-circle me-2"></i>Update selected
</button>
);
const UpdateAll = (props: UpdateButtonProps) => (
<button type="button" className="btn btn-primary me-2" disabled={props.disabled} onClick={props.onClick}>
<i className="bi bi-arrow-down-circle me-2"></i>Update all
</button>
);
const UpdateCheck = (props: UpdateButtonProps) => (
<button type="button" className="btn btn-outline-primary" disabled={props.disabled} onClick={props.onClick}>
<i className="bi bi-arrow-repeat me-2"></i>Check for updates
</button>
);
export default ContainerView; export default ContainerView;

View file

@ -1,4 +1,4 @@
import logo from "../assets/logo.png" import logo from "../assets/logo.png";
interface HeaderProps { interface HeaderProps {
onLogOut: () => void; onLogOut: () => void;

View file

@ -1,15 +1,15 @@
import { ChangeEvent, FormEvent, useState } from "react"; import { ChangeEvent, FormEvent, useState } from "react";
import { logIn } from "../services/Api"; import { logIn } from "../services/Api";
import logo from "../assets/logo.png" import logo from "../assets/logo.png";
interface LoginProps { interface LoginProps {
onLogin: () => void; onLogin: () => void;
} }
const Login = (props: LoginProps) => { const Login = (props: LoginProps) => {
const [password, setPassword] = useState<string | undefined>(undefined); const [password, setPassword] = useState("");
const [remember, setRemember] = useState(false); const [remember, setRemember] = useState(false);
const [error, setError] = useState<string | undefined>(undefined); const [error, setError] = useState("");
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.type === "checkbox") { if (event.target.type === "checkbox") {
@ -20,10 +20,10 @@ const Login = (props: LoginProps) => {
}; };
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => { const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
setError(undefined); setError("");
event.preventDefault(); event.preventDefault();
if (password === undefined) { if (password === "") {
return; return;
} }

View file

@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import logo from "../assets/logo.png" import logo from "../assets/logo.png";
interface SpinnerModalProps { interface SpinnerModalProps {
visible: boolean; visible: boolean;
@ -10,9 +10,11 @@ interface SpinnerModalProps {
const SpinnerModal = (props: SpinnerModalProps) => { const SpinnerModal = (props: SpinnerModalProps) => {
useEffect(() => { useEffect(() => {
document.body.classList.toggle("modal-open", props.visible === true); document.body.classList.toggle("modal-open", props.visible === true);
}, [props.visible]) }, [props.visible]);
if (props.visible !== true) return null; if (props.visible !== true) {
return null;
}
return ( return (
<div> <div>
@ -37,7 +39,7 @@ const SpinnerModal = (props: SpinnerModalProps) => {
</div> </div>
</div> </div>
</div> </div>
) );
}; };
export default SpinnerModal; export default SpinnerModal;

View file

@ -0,0 +1,23 @@
interface UpdateButtonProps {
disabled: boolean;
onClick: () => void;
}
export const UpdateSelected = (props: UpdateButtonProps) => (
<button type="button" className="btn btn-primary me-2" disabled={props.disabled} onClick={props.onClick}>
<i className="bi bi-arrow-down-circle me-2"></i>Update selected
</button>
);
export const UpdateAll = (props: UpdateButtonProps) => (
<button type="button" className="btn btn-primary me-2" disabled={props.disabled} onClick={props.onClick}>
<i className="bi bi-arrow-down-circle me-2"></i>Update all
</button>
);
export const UpdateCheck = (props: UpdateButtonProps) => (
<button type="button" className="btn btn-outline-primary" disabled={props.disabled} onClick={props.onClick}>
<i className="bi bi-arrow-repeat me-2"></i>Check for updates
</button>
);

View file

@ -1,45 +1,43 @@
[data-md-color-scheme="containrrr"] { [data-md-color-scheme="containrrr"] {
/* Primary and accent */ /* primary and accent */
--md-primary-fg-color: #406170; --md-primary-fg-color: #406170;
--md-primary-fg-color--light: #acbfc7; --md-primary-fg-color--light: #acbfc7;
--md-primary-fg-color--dark: #003343; --md-primary-fg-color--dark: #003343;
--md-accent-fg-color: #003343; --md-accent-fg-color: #003343;
--md-accent-fg-color--transparent: #00334310; --md-accent-fg-color--transparent: #00334310;
/* Typeset overrides */ /* typeset overrides */
--md-typeset-a-color: var(--md-primary-fg-color); --md-typeset-a-color: var(--md-primary-fg-color);
} }
:root { :root {
--bs-primary: #406170 !important; --bs-primary: #406170 !important;
--bs-primary-rgb: 3, 140, 127; --bs-primary-rgb: 3, 140, 127;
--bs-secondary: #acbfc7 !important; --bs-secondary: #acbfc7 !important;
--bs-secondary-rgb: 64, 97, 112; --bs-secondary-rgb: 64, 97, 112;
--bs-dark: #003343 !important; --bs-dark: #003343 !important;
} }
.btn-primary { .btn-primary {
--bs-btn-bg: #038C7F; --bs-btn-bg: #038c7f;
--bs-btn-border-color: ##038C7F; --bs-btn-border-color: ##038c7f;
--bs-btn-hover-bg: #02675d; --bs-btn-hover-bg: #02675d;
--bs-btn-hover-border-color: #025a52; --bs-btn-hover-border-color: #025a52;
--bs-btn-active-bg: #025a52; --bs-btn-active-bg: #025a52;
--bs-btn-active-border-color: #025a52; --bs-btn-active-border-color: #025a52;
--bs-btn-disabled-bg: #038C7F; --bs-btn-disabled-bg: #038c7f;
--bs-btn-disabled-border-color: #038C7F; --bs-btn-disabled-border-color: #038c7f;
} }
.btn-outline-primary { .btn-outline-primary {
--bs-btn-color: #038C7F; --bs-btn-color: #038c7f;
--bs-btn-disabled-color: #038C7F; --bs-btn-disabled-color: #038c7f;
--bs-btn-border-color: #038C7F; --bs-btn-border-color: #038c7f;
--bs-btn-hover-bg: #02675d; --bs-btn-hover-bg: #02675d;
--bs-btn-hover-border-color: #025a52; --bs-btn-hover-border-color: #025a52;
--bs-btn-active-bg: #025a52; --bs-btn-active-bg: #025a52;
--bs-btn-active-border-color: #025a52; --bs-btn-active-border-color: #025a52;
--bs-btn-disabled-border-color: #038C7F; --bs-btn-disabled-border-color: #038c7f;
} }
body { body {
@ -48,5 +46,5 @@ body {
.container-list-entry:hover .container-list-entry-icon .bi-box::before { .container-list-entry:hover .container-list-entry-icon .bi-box::before {
/* .bi-box-fill */ /* .bi-box-fill */
content: "\F7D2" content: "\F7D2";
} }

View file

@ -1,13 +1,13 @@
import React from "react" import { StrictMode } from "react";
import ReactDOM from "react-dom/client" import ReactDOM from "react-dom/client";
import App from "./App" import App from "./App";
import "bootstrap/dist/css/bootstrap.min.css"; import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap-icons/font/bootstrap-icons.css"; import "bootstrap-icons/font/bootstrap-icons.css";
import "./main.css"; import "./main.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode> <StrictMode>
<App /> <App />
</React.StrictMode> </StrictMode>
) );

View file

@ -1,14 +0,0 @@
export default interface ContainerEntry {
ContainerId: string;
ContainerName: string;
ImageName: string;
ImageNameShort: string;
ImageVersion: string;
ImageCreatedDate: string;
NewVersion: string;
NewVersionCreated: string;
HasUpdate: boolean;
Selected: boolean;
IsChecking: boolean;
}

View file

@ -0,0 +1,6 @@
import { CheckResponse, ContainerListEntry } from "../services/Api";
export default interface ContainerModel extends ContainerListEntry, CheckResponse {
Selected: boolean;
IsChecking: boolean;
}

View file

@ -16,7 +16,7 @@ const headers = () => ({
"Authorization": "Bearer " + token "Authorization": "Bearer " + token
}); });
export const logIn = async (password: string, remember: boolean) => { export const logIn = async (password: string, remember: boolean): Promise<boolean> => {
token = password; token = password;
const response = await fetch(apiBasePath + "list", { const response = await fetch(apiBasePath + "list", {
headers: headers() headers: headers()
@ -33,7 +33,7 @@ export const logIn = async (password: string, remember: boolean) => {
return false; return false;
}; };
export const checkLogin = async () => { export const checkLogin = async (): Promise<boolean> => {
const savedToken = localStorage.getItem(tokenStorageKey); const savedToken = localStorage.getItem(tokenStorageKey);
if (savedToken) { if (savedToken) {
return await logIn(savedToken, false); return await logIn(savedToken, false);
@ -47,17 +47,17 @@ export const logOut = () => {
localStorage.clear(); localStorage.clear();
}; };
export const list = async () => { export const list = async (): Promise<ListResponse> => {
const response = await fetch(apiBasePath + "list", { const response = await fetch(apiBasePath + "list", {
headers: headers() headers: headers()
}); });
const data = await response.json(); const data = await response.json();
return data; return data as ListResponse;
}; };
export const check = async (containerId: string) => { export const check = async (containerId: string): Promise<CheckResponse> => {
const requestData = { const requestData: CheckRequest = {
ContainerId: containerId ContainerID: containerId
}; };
const response = await fetch(apiBasePath + "check", { const response = await fetch(apiBasePath + "check", {
method: "POST", method: "POST",
@ -68,14 +68,14 @@ export const check = async (containerId: string) => {
body: JSON.stringify(requestData) body: JSON.stringify(requestData)
}); });
const data = await response.json(); const data = await response.json();
return data; return data as CheckResponse;
}; };
export const update = async (images?: string[]) => { export const update = async (images?: string[]): Promise<boolean> => {
let updateUrl = new URL(apiBasePath + "/update"); let updateUrl = new URL(apiBasePath + "/update");
if (images instanceof Array) { if (images instanceof Array) {
images.map(image => updateUrl.searchParams.append("image", image)); images.map((image) => updateUrl.searchParams.append("image", image));
} }
const response = await fetch(updateUrl.toString(), { const response = await fetch(updateUrl.toString(), {
@ -83,4 +83,28 @@ export const update = async (images?: string[]) => {
}); });
return response.ok; return response.ok;
}; };
export interface ListResponse {
Containers: ContainerListEntry[];
}
export interface ContainerListEntry {
ContainerID: string;
ContainerName: string;
ImageName: string;
ImageNameShort: string;
ImageVersion: string;
ImageCreatedDate: string;
}
export interface CheckRequest {
ContainerID: string;
}
export interface CheckResponse {
ContainerID: string;
HasUpdate: boolean;
NewVersion: string;
NewVersionCreated: string;
}

View file

@ -1,5 +1,5 @@
import { defineConfig, loadEnv } from 'vite' import { defineConfig, loadEnv } from "vite"
import react from '@vitejs/plugin-react' import react from "@vitejs/plugin-react"
const htmlPlugin = (mode: string) => { const htmlPlugin = (mode: string) => {
const env = loadEnv(mode, "."); const env = loadEnv(mode, ".");