watchtower/web/src/components/ContainerView.tsx

141 lines
5.1 KiB
TypeScript
Raw Normal View History

2022-11-15 00:06:15 +01:00
import { useEffect, useState } from "react";
2022-11-15 13:34:40 +01:00
import ContainerModel from "../models/ContainerModel";
2022-11-15 00:06:15 +01:00
import { check, list, update } from "../services/Api";
import ContainerList from "./ContainerList";
import Spinner from "./Spinner";
import SpinnerModal from "./SpinnerModal";
2022-11-15 13:34:40 +01:00
import { UpdateSelected, UpdateAll, UpdateCheck } from "./UpdateButtons";
2022-11-15 00:06:15 +01:00
interface ViewModel {
2022-11-15 13:34:40 +01:00
Containers: ContainerModel[];
2022-11-15 00:06:15 +01:00
}
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: [] });
const containers = viewModel.Containers;
2022-11-15 13:34:40 +01:00
const containersWithUpdates = containers.filter((c) => c.HasUpdate);
const containersWithoutUpdates = containers.filter((c) => !c.HasUpdate);
const hasSelectedContainers = containers.some((c) => c.Selected);
2022-11-15 00:06:15 +01:00
const hasUpdates = containersWithUpdates.length > 0;
const checkForUpdates = async () => {
setChecking(true);
2022-11-15 13:34:40 +01:00
setViewModel((m) => ({
2022-11-15 00:06:15 +01:00
...m,
2022-11-15 13:34:40 +01:00
Containers: m.Containers.map((c) => ({
2022-11-15 00:06:15 +01:00
...c,
IsChecking: true
}))
}));
await Promise.all(containers.map(async (c1) => {
2022-11-15 13:34:40 +01:00
const result = await check(c1.ContainerID);
setViewModel((m) => ({
2022-11-15 00:06:15 +01:00
...m,
2022-11-15 13:34:40 +01:00
Containers: m.Containers.map((c2) => (c1.ContainerID === c2.ContainerID ? {
2022-11-15 00:06:15 +01:00
...c2,
...result,
IsChecking: false
} : c2
))
}));
}));
setChecking(false);
setHasChecked(true);
};
const listContainers = async () => {
setLoading(true);
const data = await list();
2022-11-15 13:34:40 +01:00
setViewModel({
Containers: data.Containers.map((c) => ({
...c,
Selected: false,
IsChecking: false,
HasUpdate: false,
IsUpdating: false,
NewVersion: "",
NewVersionCreated: ""
}))
});
2022-11-15 00:06:15 +01:00
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 () => {
2022-11-15 13:34:40 +01:00
const selectedImages = containers.filter((c) => c.Selected === true).map((c) => c.ImageNameShort);
2022-11-15 00:06:15 +01:00
await updateImages(selectedImages);
};
2022-11-15 13:34:40 +01:00
const onContainerClick = (container: ContainerModel) => {
setViewModel((m) => ({
2022-11-15 00:06:15 +01:00
...m,
2022-11-15 13:34:40 +01:00
Containers: m.Containers.map((c2) => (container.ContainerID === c2.ContainerID ? {
2022-11-15 00:06:15 +01:00
...c2,
Selected: !c2.Selected,
} : c2
))
}));
};
2022-11-15 13:34:40 +01:00
useEffect(() => {
listContainers();
}, []);
2022-11-15 00:06:15 +01:00
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>
);
};
export default ContainerView;