|
@@ -3,7 +3,7 @@ import ProvisionerStatus, {
|
|
|
TFResource,
|
|
TFResource,
|
|
|
TFResourceError,
|
|
TFResourceError,
|
|
|
} from "components/ProvisionerStatus";
|
|
} from "components/ProvisionerStatus";
|
|
|
-import React, { useEffect, useRef, useState } from "react";
|
|
|
|
|
|
|
+import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
|
import api from "shared/api";
|
|
import api from "shared/api";
|
|
|
import { NewWebsocketOptions, useWebsockets } from "shared/hooks/useWebsockets";
|
|
import { NewWebsocketOptions, useWebsockets } from "shared/hooks/useWebsockets";
|
|
|
|
|
|
|
@@ -69,6 +69,8 @@ export const StatusPage = ({
|
|
|
updateGlobalErrorsForModule,
|
|
updateGlobalErrorsForModule,
|
|
|
} = useTFModules();
|
|
} = useTFModules();
|
|
|
|
|
|
|
|
|
|
+ const { moduleStatuses } = useModuleChecker(tfModules);
|
|
|
|
|
+
|
|
|
const filterBySelectedInfras = (currentInfra: Infra) => {
|
|
const filterBySelectedInfras = (currentInfra: Infra) => {
|
|
|
if (!Array.isArray(selectedFilters) || !selectedFilters?.length) {
|
|
if (!Array.isArray(selectedFilters) || !selectedFilters?.length) {
|
|
|
return true;
|
|
return true;
|
|
@@ -279,10 +281,20 @@ export const StatusPage = ({
|
|
|
const hasModuleWithError = tfModules.find(
|
|
const hasModuleWithError = tfModules.find(
|
|
|
(module) => module.status === "error"
|
|
(module) => module.status === "error"
|
|
|
);
|
|
);
|
|
|
|
|
+
|
|
|
const hasModuleInCreatingState = tfModules.find(
|
|
const hasModuleInCreatingState = tfModules.find(
|
|
|
(module) => module.status === "creating"
|
|
(module) => module.status === "creating"
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
|
|
+ const hasModuleWithTimerElapsed = moduleStatuses.find(
|
|
|
|
|
+ (module) => module.status === "failed"
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (hasModuleWithTimerElapsed) {
|
|
|
|
|
+ setInfraStatus({ hasError: true });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (hasModuleInCreatingState) {
|
|
if (hasModuleInCreatingState) {
|
|
|
setInfraStatus(null);
|
|
setInfraStatus(null);
|
|
|
return;
|
|
return;
|
|
@@ -297,7 +309,7 @@ export const StatusPage = ({
|
|
|
setInfraStatus({ hasError: true });
|
|
setInfraStatus({ hasError: true });
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- }, [tfModules]);
|
|
|
|
|
|
|
+ }, [tfModules, moduleStatuses]);
|
|
|
|
|
|
|
|
const sortedModules = tfModules.sort((a, b) =>
|
|
const sortedModules = tfModules.sort((a, b) =>
|
|
|
b.id < a.id ? -1 : b.id > a.id ? 1 : 0
|
|
b.id < a.id ? -1 : b.id > a.id ? 1 : 0
|
|
@@ -346,6 +358,7 @@ const useTFModules = () => {
|
|
|
status: infra.status,
|
|
status: infra.status,
|
|
|
got_desired: false,
|
|
got_desired: false,
|
|
|
created_at: infra.created_at,
|
|
created_at: infra.created_at,
|
|
|
|
|
+ updated_at: infra.updated_at,
|
|
|
};
|
|
};
|
|
|
setModule(infra.id, module);
|
|
setModule(infra.id, module);
|
|
|
};
|
|
};
|
|
@@ -471,3 +484,152 @@ const useTFModules = () => {
|
|
|
updateGlobalErrorsForModule,
|
|
updateGlobalErrorsForModule,
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+const useModuleChecker = (modules: TFModule[]) => {
|
|
|
|
|
+ const [timers, setTimers] = useState<{
|
|
|
|
|
+ [timerModuleId: number]: NodeJS.Timeout;
|
|
|
|
|
+ }>({});
|
|
|
|
|
+
|
|
|
|
|
+ const [moduleStatuses, setModuleStatus] = useState<{
|
|
|
|
|
+ [timerModuleId: number]: "failed" | "creating" | "success";
|
|
|
|
|
+ }>({});
|
|
|
|
|
+
|
|
|
|
|
+ const didModuleTimedOut = (infra: TFModule) => {
|
|
|
|
|
+ const last_updated = new Date(infra.updated_at).getTime();
|
|
|
|
|
+ const current_date = new Date().getTime();
|
|
|
|
|
+
|
|
|
|
|
+ let diff = (current_date - last_updated) / 1000 / 60;
|
|
|
|
|
+ const minutes_elapsed = Math.abs(Math.round(diff));
|
|
|
|
|
+
|
|
|
|
|
+ if (minutes_elapsed >= 45) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const hasModuleAnyResourcesProvisionedOrErrored = (module: TFModule) => {
|
|
|
|
|
+ if (!Array.isArray(module.resources)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ module.resources.every(
|
|
|
|
|
+ (resource) => resource.provisioned || resource.errored?.errored_out
|
|
|
|
|
+ )
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const hasModuleBeenSuccessfullyProvisioned = (module: TFModule) => {
|
|
|
|
|
+ if (!Array.isArray(module.resources)) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (module.resources.every((resource) => resource.provisioned)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const setupTimeoutToCheckModuleTimeout = (module: TFModule) => {
|
|
|
|
|
+ const timer = setTimeout(() => {
|
|
|
|
|
+ if (!didModuleTimedOut(module)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!hasModuleAnyResourcesProvisionedOrErrored(module)) {
|
|
|
|
|
+ setModuleStatus((modulesStatus) => ({
|
|
|
|
|
+ ...modulesStatus,
|
|
|
|
|
+ [module.id]: "failed",
|
|
|
|
|
+ }));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (hasModuleBeenSuccessfullyProvisioned(module)) {
|
|
|
|
|
+ setModuleStatus((modulesStatus) => ({
|
|
|
|
|
+ ...modulesStatus,
|
|
|
|
|
+ [module.id]: "success",
|
|
|
|
|
+ }));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setModuleStatus((modulesStatus) => ({
|
|
|
|
|
+ ...modulesStatus,
|
|
|
|
|
+ [module.id]: "creating",
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ clearCheckerTimeout(module.id);
|
|
|
|
|
+ }, 1000);
|
|
|
|
|
+ return timer;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const clearCheckerTimeout = (moduleId: number) => {
|
|
|
|
|
+ const moduleInterval = timers[moduleId];
|
|
|
|
|
+ clearTimeout(moduleInterval);
|
|
|
|
|
+ setTimers((timers) => ({
|
|
|
|
|
+ ...timers,
|
|
|
|
|
+ [moduleId]: undefined,
|
|
|
|
|
+ }));
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const clearCheckerTimers = () => {
|
|
|
|
|
+ if (typeof timers !== "object") {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Object.entries(timers).forEach(([moduleId, intervalId]) => {
|
|
|
|
|
+ clearTimeout(intervalId);
|
|
|
|
|
+ setTimers((timers) => ({
|
|
|
|
|
+ ...timers,
|
|
|
|
|
+ [moduleId]: undefined,
|
|
|
|
|
+ }));
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ modules.forEach((module) => {
|
|
|
|
|
+ if (timers[module.id]) {
|
|
|
|
|
+ clearTimeout(timers[module.id]);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (
|
|
|
|
|
+ moduleStatuses[module.id] &&
|
|
|
|
|
+ moduleStatuses[module.id] !== "creating"
|
|
|
|
|
+ ) {
|
|
|
|
|
+ console.log("Module failed or created", module.id);
|
|
|
|
|
+ clearCheckerTimeout(module.id);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const timerId = setupTimeoutToCheckModuleTimeout(module);
|
|
|
|
|
+
|
|
|
|
|
+ setTimers((timers) => ({
|
|
|
|
|
+ ...timers,
|
|
|
|
|
+ [module.id]: timerId,
|
|
|
|
|
+ }));
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ clearCheckerTimers();
|
|
|
|
|
+ };
|
|
|
|
|
+ }, [modules, moduleStatuses]);
|
|
|
|
|
+
|
|
|
|
|
+ const moduleStatusesArray = useMemo(() => {
|
|
|
|
|
+ if (typeof moduleStatuses !== "object") {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return Object.entries(moduleStatuses).map(([moduleId, status]) => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: moduleId,
|
|
|
|
|
+ status,
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ }, [moduleStatuses]);
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ moduleStatuses: moduleStatusesArray,
|
|
|
|
|
+ };
|
|
|
|
|
+};
|