mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-17 15:40:12 +01:00
Use Vite build tool
This commit is contained in:
parent
57b4862a5b
commit
5645cd23fe
40 changed files with 3513 additions and 2715 deletions
153
web/src/components/ContainerView.tsx
Normal file
153
web/src/components/ContainerView.tsx
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import ContainerEntry from "../models/ContainerEntry"
|
||||
import { check, list, update } from "../services/Api";
|
||||
import ContainerList from "./ContainerList";
|
||||
import Spinner from "./Spinner";
|
||||
import SpinnerModal from "./SpinnerModal";
|
||||
|
||||
interface ViewModel {
|
||||
Containers: ContainerEntry[];
|
||||
}
|
||||
|
||||
const ContainerView = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [updating, setUpdating] = useState(false);
|
||||
const [hasChecked, setHasChecked] = useState(false);
|
||||
const [viewModel, setViewModel] = useState<ViewModel>({ Containers: [] });
|
||||
|
||||
useEffect(() => {
|
||||
listContainers();
|
||||
}, []);
|
||||
|
||||
const containers = viewModel.Containers;
|
||||
const containersWithUpdates = containers.filter(c => c.HasUpdate);
|
||||
const containersWithoutUpdates = containers.filter(c => !c.HasUpdate);
|
||||
const hasSelectedContainers = containers.some(c => c.Selected);
|
||||
const hasUpdates = containersWithUpdates.length > 0;
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
setChecking(true);
|
||||
|
||||
setViewModel(m => ({
|
||||
...m,
|
||||
Containers: m.Containers.map(c => ({
|
||||
...c,
|
||||
IsChecking: true
|
||||
}))
|
||||
}));
|
||||
|
||||
await Promise.all(containers.map(async (c1) => {
|
||||
const result = await check(c1.ContainerId);
|
||||
setViewModel(m => ({
|
||||
...m,
|
||||
Containers: m.Containers.map((c2, i) => (c1.ContainerId === c2.ContainerId ? {
|
||||
...c2,
|
||||
...result,
|
||||
IsChecking: false
|
||||
} : c2
|
||||
))
|
||||
}));
|
||||
}));
|
||||
|
||||
setChecking(false);
|
||||
setHasChecked(true);
|
||||
};
|
||||
|
||||
const listContainers = async () => {
|
||||
setLoading(true);
|
||||
const data = await list();
|
||||
setViewModel(data);
|
||||
setLoading(false);
|
||||
setHasChecked(false);
|
||||
};
|
||||
|
||||
const updateImages = async (imagesToUpdate?: string[]) => {
|
||||
setUpdating(true);
|
||||
await update(imagesToUpdate);
|
||||
await listContainers();
|
||||
await checkForUpdates();
|
||||
setUpdating(false);
|
||||
};
|
||||
|
||||
const updateAll = async () => {
|
||||
await updateImages();
|
||||
};
|
||||
|
||||
const updateSelected = async () => {
|
||||
const selectedImages = containers.filter(c => c.Selected === true).map(c => c.ImageNameShort);
|
||||
await updateImages(selectedImages);
|
||||
};
|
||||
|
||||
const onContainerClick = (container: ContainerEntry) => {
|
||||
setViewModel(m => ({
|
||||
...m,
|
||||
Containers: m.Containers.map(c2 => (container.ContainerId === c2.ContainerId ? {
|
||||
...c2,
|
||||
Selected: !c2.Selected,
|
||||
} : c2
|
||||
))
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="mt-5 p-5 d-block">
|
||||
<SpinnerModal visible={updating} title="Updating containers" message="Please wait..." />
|
||||
<div className="row mb-2">
|
||||
<div className="col-12 col-md-4 d-flex align-items-center">
|
||||
{hasUpdates
|
||||
? <span>{containersWithUpdates.length} container{containersWithUpdates.length === 1 ? " has" : "s have"} updates.</span>
|
||||
: checking
|
||||
? <span>Checking for updates...</span>
|
||||
: (hasChecked && containers.length > 0)
|
||||
? <><i className="bi bi-check-circle-fill fs-4 text-primary me-2"></i><span>All containers are up to date.</span></>
|
||||
: <span>{containers.length} running container{containers.length !== 1 && "s"} found.</span>}
|
||||
</div>
|
||||
<div className="col-12 col-md-8 text-end">
|
||||
{hasUpdates && <UpdateSelected onClick={updateSelected} disabled={checking || !hasSelectedContainers} />}
|
||||
{hasUpdates && <UpdateAll onClick={updateAll} disabled={checking} />}
|
||||
<UpdateCheck onClick={checkForUpdates} disabled={checking} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ContainerList containers={containersWithUpdates} onContainerClick={onContainerClick} />
|
||||
|
||||
{hasUpdates && containersWithoutUpdates.length > 0 &&
|
||||
<div className="row mt-4 mb-2">
|
||||
<div className="col-4 d-flex align-items-center">
|
||||
{containersWithoutUpdates.length} container{containersWithoutUpdates.length === 1 ? " is" : "s are"} up to date.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<ContainerList containers={containersWithoutUpdates} onContainerClick={onContainerClick} />
|
||||
|
||||
{loading && <Spinner />}
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue