2021-02-24 08:17:40 +01:00
|
|
|
/*
|
2024-09-24 09:42:07 +02:00
|
|
|
* Copyright (c) 2020 - present Cloudogu GmbH
|
2021-02-24 08:17:40 +01:00
|
|
|
*
|
2024-09-24 09:42:07 +02:00
|
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
|
|
|
* the terms of the GNU Affero General Public License as published by the Free
|
|
|
|
|
* Software Foundation, version 3.
|
2021-02-24 08:17:40 +01:00
|
|
|
*
|
2024-09-24 09:42:07 +02:00
|
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
|
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
|
|
|
* details.
|
2021-02-24 08:17:40 +01:00
|
|
|
*
|
2024-09-24 09:42:07 +02:00
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
* along with this program. If not, see https://www.gnu.org/licenses/.
|
2021-02-24 08:17:40 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { ApiResult, useIndexLink, useRequiredIndexLink } from "./base";
|
2022-08-02 08:39:37 +02:00
|
|
|
import type { PendingPlugins, Plugin, PluginCollection, HalRepresentation } from "@scm-manager/ui-types";
|
2021-02-24 08:17:40 +01:00
|
|
|
import { useMutation, useQuery, useQueryClient } from "react-query";
|
2021-11-10 10:10:17 +01:00
|
|
|
import { apiClient } from "./apiclient";
|
2021-02-24 08:17:40 +01:00
|
|
|
import { requiredLink } from "./links";
|
2022-02-02 10:02:46 +01:00
|
|
|
import { BadGatewayError } from "./errors";
|
2021-02-24 08:17:40 +01:00
|
|
|
|
2022-08-02 08:39:37 +02:00
|
|
|
const isPluginCollection = (input: HalRepresentation): input is PluginCollection =>
|
|
|
|
|
input._embedded ? "plugins" in input._embedded : false;
|
|
|
|
|
|
2021-02-24 08:17:40 +01:00
|
|
|
type WaitForRestartOptions = {
|
|
|
|
|
initialDelay?: number;
|
|
|
|
|
timeout?: number;
|
|
|
|
|
};
|
|
|
|
|
|
2022-05-31 15:14:52 +02:00
|
|
|
export const waitForRestartAfter = (
|
2021-02-24 08:17:40 +01:00
|
|
|
promise: Promise<any>,
|
|
|
|
|
{ initialDelay = 1000, timeout = 500 }: WaitForRestartOptions = {}
|
|
|
|
|
): Promise<void> => {
|
2022-02-02 10:02:46 +01:00
|
|
|
const endTime = Number(new Date()) + 4 * 60 * 1000;
|
2021-02-24 08:17:40 +01:00
|
|
|
let started = false;
|
|
|
|
|
|
2022-02-02 10:02:46 +01:00
|
|
|
const executor = <T = any>(data: T) => (resolve: (result: T) => void, reject: (error: Error) => void) => {
|
|
|
|
|
// we need some initial delay
|
|
|
|
|
if (!started) {
|
|
|
|
|
started = true;
|
|
|
|
|
setTimeout(executor(data), initialDelay, resolve, reject);
|
|
|
|
|
} else {
|
|
|
|
|
apiClient
|
|
|
|
|
.get("")
|
|
|
|
|
.then(() => resolve(data))
|
|
|
|
|
.catch(() => {
|
|
|
|
|
if (Number(new Date()) < endTime) {
|
|
|
|
|
setTimeout(executor(data), timeout, resolve, reject);
|
|
|
|
|
} else {
|
|
|
|
|
reject(new Error("timeout reached"));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2021-02-24 08:17:40 +01:00
|
|
|
|
2022-02-02 10:02:46 +01:00
|
|
|
return promise
|
|
|
|
|
.catch(err => {
|
|
|
|
|
if (err instanceof BadGatewayError) {
|
|
|
|
|
// in some rare cases the reverse proxy stops forwarding traffic to scm before the response is returned
|
|
|
|
|
// in such a case the reverse proxy returns 502 (bad gateway), so we treat 502 not as error
|
|
|
|
|
return "ok";
|
|
|
|
|
}
|
|
|
|
|
throw err;
|
|
|
|
|
})
|
|
|
|
|
.then(data => new Promise<void>(executor(data)));
|
2021-02-24 08:17:40 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type UseAvailablePluginsOptions = {
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useAvailablePlugins = ({ enabled }: UseAvailablePluginsOptions = {}): ApiResult<PluginCollection> => {
|
|
|
|
|
const indexLink = useRequiredIndexLink("availablePlugins");
|
|
|
|
|
return useQuery<PluginCollection, Error>(
|
|
|
|
|
["plugins", "available"],
|
2022-02-02 10:02:46 +01:00
|
|
|
() => apiClient.get(indexLink).then(response => response.json()),
|
2021-02-24 08:17:40 +01:00
|
|
|
{
|
|
|
|
|
enabled,
|
2022-02-02 10:02:46 +01:00
|
|
|
retry: 3
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type UseInstalledPluginsOptions = {
|
|
|
|
|
enabled?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useInstalledPlugins = ({ enabled }: UseInstalledPluginsOptions = {}): ApiResult<PluginCollection> => {
|
|
|
|
|
const indexLink = useRequiredIndexLink("installedPlugins");
|
|
|
|
|
return useQuery<PluginCollection, Error>(
|
|
|
|
|
["plugins", "installed"],
|
2022-02-02 10:02:46 +01:00
|
|
|
() => apiClient.get(indexLink).then(response => response.json()),
|
2021-02-24 08:17:40 +01:00
|
|
|
{
|
|
|
|
|
enabled,
|
2022-02-02 10:02:46 +01:00
|
|
|
retry: 3
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const usePendingPlugins = (): ApiResult<PendingPlugins> => {
|
|
|
|
|
const indexLink = useIndexLink("pendingPlugins");
|
|
|
|
|
return useQuery<PendingPlugins, Error>(
|
|
|
|
|
["plugins", "pending"],
|
2022-02-02 10:02:46 +01:00
|
|
|
() => apiClient.get(indexLink!).then(response => response.json()),
|
2021-02-24 08:17:40 +01:00
|
|
|
{
|
|
|
|
|
enabled: !!indexLink,
|
2022-02-02 10:02:46 +01:00
|
|
|
retry: 3
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const linkWithRestart = (link: string, restart?: boolean) => {
|
|
|
|
|
if (restart) {
|
|
|
|
|
return link + "WithRestart";
|
|
|
|
|
}
|
|
|
|
|
return link;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type RestartOptions = WaitForRestartOptions & {
|
|
|
|
|
restart?: boolean;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type PluginActionOptions = {
|
|
|
|
|
plugin: Plugin;
|
|
|
|
|
restartOptions: RestartOptions;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useInstallPlugin = () => {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const { mutate, isLoading, error, data } = useMutation<unknown, Error, PluginActionOptions>(
|
|
|
|
|
({ plugin, restartOptions: { restart, ...waitForRestartOptions } }) => {
|
|
|
|
|
const promise = apiClient.post(requiredLink(plugin, linkWithRestart("install", restart)));
|
|
|
|
|
if (restart) {
|
|
|
|
|
return waitForRestartAfter(promise, waitForRestartOptions);
|
|
|
|
|
}
|
|
|
|
|
return promise;
|
|
|
|
|
},
|
|
|
|
|
{
|
2022-02-02 10:02:46 +01:00
|
|
|
onSuccess: () => queryClient.invalidateQueries("plugins")
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
install: (plugin: Plugin, restartOptions: RestartOptions = {}) =>
|
|
|
|
|
mutate({
|
|
|
|
|
plugin,
|
2022-02-02 10:02:46 +01:00
|
|
|
restartOptions
|
2021-02-24 08:17:40 +01:00
|
|
|
}),
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
|
|
|
|
data,
|
2022-02-02 10:02:46 +01:00
|
|
|
isInstalled: !!data
|
2021-02-24 08:17:40 +01:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useUninstallPlugin = () => {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const { mutate, isLoading, error, data } = useMutation<unknown, Error, PluginActionOptions>(
|
|
|
|
|
({ plugin, restartOptions: { restart, ...waitForRestartOptions } }) => {
|
|
|
|
|
const promise = apiClient.post(requiredLink(plugin, linkWithRestart("uninstall", restart)));
|
|
|
|
|
if (restart) {
|
|
|
|
|
return waitForRestartAfter(promise, waitForRestartOptions);
|
|
|
|
|
}
|
|
|
|
|
return promise;
|
|
|
|
|
},
|
|
|
|
|
{
|
2022-02-02 10:02:46 +01:00
|
|
|
onSuccess: () => queryClient.invalidateQueries("plugins")
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
uninstall: (plugin: Plugin, restartOptions: RestartOptions = {}) =>
|
|
|
|
|
mutate({
|
|
|
|
|
plugin,
|
2022-02-02 10:02:46 +01:00
|
|
|
restartOptions
|
2021-02-24 08:17:40 +01:00
|
|
|
}),
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
2022-02-02 10:02:46 +01:00
|
|
|
isUninstalled: !!data
|
2021-02-24 08:17:40 +01:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type UpdatePluginsOptions = {
|
|
|
|
|
plugins: Plugin | PluginCollection;
|
|
|
|
|
restartOptions: RestartOptions;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useUpdatePlugins = () => {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const { mutate, isLoading, error, data } = useMutation<unknown, Error, UpdatePluginsOptions>(
|
|
|
|
|
({ plugins, restartOptions: { restart, ...waitForRestartOptions } }) => {
|
|
|
|
|
const isCollection = isPluginCollection(plugins);
|
|
|
|
|
const promise = apiClient.post(
|
|
|
|
|
requiredLink(plugins, isCollection ? "update" : linkWithRestart("update", restart))
|
|
|
|
|
);
|
|
|
|
|
if (restart && !isCollection) {
|
|
|
|
|
return waitForRestartAfter(promise, waitForRestartOptions);
|
|
|
|
|
}
|
|
|
|
|
return promise;
|
|
|
|
|
},
|
|
|
|
|
{
|
2022-02-02 10:02:46 +01:00
|
|
|
onSuccess: () => queryClient.invalidateQueries("plugins")
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
update: (plugin: Plugin | PluginCollection, restartOptions: RestartOptions = {}) =>
|
|
|
|
|
mutate({
|
|
|
|
|
plugins: plugin,
|
2022-02-02 10:02:46 +01:00
|
|
|
restartOptions
|
2021-02-24 08:17:40 +01:00
|
|
|
}),
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
2022-02-02 10:02:46 +01:00
|
|
|
isUpdated: !!data
|
2021-02-24 08:17:40 +01:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ExecutePendingPlugins = {
|
|
|
|
|
pending: PendingPlugins;
|
|
|
|
|
restartOptions: WaitForRestartOptions;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useExecutePendingPlugins = () => {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const { mutate, isLoading, error, data } = useMutation<unknown, Error, ExecutePendingPlugins>(
|
|
|
|
|
({ pending, restartOptions }) =>
|
|
|
|
|
waitForRestartAfter(apiClient.post(requiredLink(pending, "execute")), restartOptions),
|
|
|
|
|
{
|
2022-02-02 10:02:46 +01:00
|
|
|
onSuccess: () => queryClient.invalidateQueries("plugins")
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
update: (pending: PendingPlugins, restartOptions: WaitForRestartOptions = {}) =>
|
|
|
|
|
mutate({ pending, restartOptions }),
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
2022-02-02 10:02:46 +01:00
|
|
|
isExecuted: !!data
|
2021-02-24 08:17:40 +01:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const useCancelPendingPlugins = () => {
|
|
|
|
|
const queryClient = useQueryClient();
|
|
|
|
|
const { mutate, isLoading, error, data } = useMutation<unknown, Error, PendingPlugins>(
|
2022-02-02 10:02:46 +01:00
|
|
|
pending => apiClient.post(requiredLink(pending, "cancel")),
|
2021-02-24 08:17:40 +01:00
|
|
|
{
|
2022-02-02 10:02:46 +01:00
|
|
|
onSuccess: () => queryClient.invalidateQueries("plugins")
|
2021-02-24 08:17:40 +01:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
update: (pending: PendingPlugins) => mutate(pending),
|
|
|
|
|
isLoading,
|
|
|
|
|
error,
|
2022-02-02 10:02:46 +01:00
|
|
|
isCancelled: !!data
|
2021-02-24 08:17:40 +01:00
|
|
|
};
|
|
|
|
|
};
|