Enable Health Checks (#1621)

In the release of version 2.0.0 of SCM-Manager, the health checks had been neglected. This makes them visible again in the frontend and adds the ability to trigger them. In addition there are two types of health checks: The "normal" ones, now called "light checks", that are run on startup, and more intense checks run only on request.

As a change to version 1.x, health checks will no longer be persisted for repositories.

Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
René Pfeuffer
2021-04-21 10:09:23 +02:00
committed by GitHub
parent 893cf4af4c
commit 1e83c34823
61 changed files with 2162 additions and 106 deletions

View File

@@ -35,9 +35,19 @@ type Props = {
active: boolean;
className?: string;
headColor?: string;
headTextColor?: string;
};
export const Modal: FC<Props> = ({ title, closeFunction, body, footer, active, className, headColor = "light" }) => {
export const Modal: FC<Props> = ({
title,
closeFunction,
body,
footer,
active,
className,
headColor = "light",
headTextColor = "black"
}) => {
const portalRootElement = usePortalRootElement("modalsRoot");
if (!portalRootElement) {
@@ -56,7 +66,7 @@ export const Modal: FC<Props> = ({ title, closeFunction, body, footer, active, c
<div className="modal-background" onClick={closeFunction} />
<div className="modal-card">
<header className={classNames("modal-card-head", `has-background-${headColor}`)}>
<p className="modal-card-title is-marginless">{title}</p>
<p className={`modal-card-title is-marginless has-text-${headTextColor}`}>{title}</p>
<button className="delete" aria-label="close" onClick={closeFunction} />
</header>
<section className="modal-card-body">{body}</section>

View File

@@ -0,0 +1,60 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC } from "react";
import { Modal } from "../modals";
import { HealthCheckFailure } from "@scm-manager/ui-types";
import { useTranslation } from "react-i18next";
import { Button } from "../buttons";
import HealthCheckFailureList from "./HealthCheckFailureList";
type Props = {
active: boolean;
closeFunction: () => void;
failures?: HealthCheckFailure[];
};
const HealthCheckFailureDetail: FC<Props> = ({ active, closeFunction, failures }) => {
const [t] = useTranslation("repos");
const footer = <Button label={t("healthCheckFailure.close")} action={closeFunction} color="grey" />;
return (
<Modal
body={
<div className={"content"}>
<HealthCheckFailureList failures={failures} />
</div>
}
title={t("healthCheckFailure.title")}
closeFunction={closeFunction}
active={active}
footer={footer}
headColor={"danger"}
headTextColor={"white"}
/>
);
};
export default HealthCheckFailureDetail;

View File

@@ -0,0 +1,69 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { HealthCheckFailure } from "@scm-manager/ui-types";
import React, { FC } from "react";
import { useTranslation } from "react-i18next";
type Props = {
failures?: HealthCheckFailure[];
};
const HealthCheckFailureList: FC<Props> = ({ failures }) => {
const [t] = useTranslation("plugins");
const translationOrDefault = (translationKey: string, defaultValue: string) => {
const translation = t(translationKey);
return translation === translationKey ? defaultValue : translation;
};
if (!failures) {
return null;
}
const failureLine = (failure: HealthCheckFailure) => {
const summary = translationOrDefault(`healthCheckFailures.${failure.id}.summary`, failure.summary);
const description = translationOrDefault(`healthCheckFailures.${failure.id}.description`, failure.description);
return (
<li>
<em>{summary}</em>
<br />
{description}
<br />
{failure.url && (
<a href={failure.url} target="_blank" rel="noreferrer">
{t("healthCheckFailures.detailUrl")}
</a>
)}
</li>
);
};
const failureComponents = failures.map(failureLine);
return <ul>{failureComponents}</ul>;
};
export default HealthCheckFailureList;

View File

@@ -29,6 +29,7 @@ import RepositoryAvatar from "./RepositoryAvatar";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { withTranslation, WithTranslation } from "react-i18next";
import styled from "styled-components";
import HealthCheckFailureDetail from "./HealthCheckFailureDetail";
type DateProp = Date | string;
@@ -39,6 +40,10 @@ type Props = WithTranslation & {
baseDate?: DateProp;
};
type State = {
showHealthCheck: boolean;
};
const RepositoryTag = styled.span`
margin-left: 0.2rem;
background-color: #9a9a9a;
@@ -50,8 +55,26 @@ const RepositoryTag = styled.span`
font-weight: bold;
font-size: 0.7rem;
`;
const RepositoryWarnTag = styled.span`
margin-left: 0.2rem;
background-color: #f14668;
padding: 0.25rem;
border-radius: 5px;
color: white;
overflow: visible;
pointer-events: all;
font-weight: bold;
font-size: 0.7rem;
cursor: help;
`;
class RepositoryEntry extends React.Component<Props> {
class RepositoryEntry extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
showHealthCheck: false
};
}
createLink = (repository: Repository) => {
return `/repo/${repository.namespace}/${repository.name}`;
};
@@ -154,6 +177,19 @@ class RepositoryEntry extends React.Component<Props> {
repositoryFlags.push(<RepositoryTag title={t("exporting.tooltip")}>{t("repository.exporting")}</RepositoryTag>);
}
if (repository.healthCheckFailures && repository.healthCheckFailures.length > 0) {
repositoryFlags.push(
<RepositoryWarnTag
title={t("healthCheckFailure.tooltip")}
onClick={() => {
this.setState({ showHealthCheck: true });
}}
>
{t("repository.healthCheckFailure")}
</RepositoryWarnTag>
);
}
return (
<>
<ExtensionPoint name="repository.card.beforeTitle" props={{ repository }} />
@@ -168,16 +204,27 @@ class RepositoryEntry extends React.Component<Props> {
const footerLeft = this.createFooterLeft(repository, repositoryLink);
const footerRight = this.createFooterRight(repository, baseDate);
const title = this.createTitle();
return (
<CardColumn
avatar={<RepositoryAvatar repository={repository} />}
title={title}
description={repository.description}
link={repositoryLink}
footerLeft={footerLeft}
footerRight={footerRight}
const modal = (
<HealthCheckFailureDetail
closeFunction={() => this.setState({ showHealthCheck: false })}
active={this.state.showHealthCheck}
failures={repository.healthCheckFailures}
/>
);
return (
<>
{modal}
<CardColumn
avatar={<RepositoryAvatar repository={repository} />}
title={title}
description={repository.description}
link={repositoryLink}
footerLeft={footerLeft}
footerRight={footerRight}
/>
</>
);
}
}

View File

@@ -49,6 +49,7 @@ export { default as RepositoryEntry } from "./RepositoryEntry";
export { default as RepositoryEntryLink } from "./RepositoryEntryLink";
export { default as JumpToFileButton } from "./JumpToFileButton";
export { default as CommitAuthor } from "./CommitAuthor";
export { default as HealthCheckFailureDetail } from "./HealthCheckFailureDetail";
export {
File,