mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-26 14:28:04 +00:00
Rename frontend directory (#472)
* Rename directory * Cleanup * Recover lost images * remove lint
This commit is contained in:
41
frontend/src/components/InstalledPackages/HealthStatus.tsx
Normal file
41
frontend/src/components/InstalledPackages/HealthStatus.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { HD_RESOURCE_CONDITION_TYPE } from "../../API/releases";
|
||||
import { Tooltip } from "flowbite-react";
|
||||
import { ReleaseHealthStatus } from "../../data/types";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
interface Props {
|
||||
statusData: ReleaseHealthStatus[];
|
||||
}
|
||||
|
||||
const HealthStatus = ({ statusData }: Props) => {
|
||||
const statuses = statusData.map((item) => {
|
||||
for (let i = 0; i < item.status.conditions.length; i++) {
|
||||
const cond = item.status.conditions[i];
|
||||
|
||||
if (cond.type !== HD_RESOURCE_CONDITION_TYPE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={uuidv4()} // this is not a good practice, we need to fetch some unique id from the backend
|
||||
content={`${cond.status} ${item.kind} ${item.metadata.name}`}
|
||||
>
|
||||
<span
|
||||
className={`inline-block ${
|
||||
cond.status === "Healthy"
|
||||
? "bg-success"
|
||||
: cond.status === "Progressing"
|
||||
? "bg-warning"
|
||||
: "bg-danger"
|
||||
} w-2.5 h-2.5 rounded-sm`}
|
||||
></span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return <div className="flex flex-wrap gap-1">{statuses}</div>;
|
||||
};
|
||||
|
||||
export default HealthStatus;
|
||||
@@ -0,0 +1,41 @@
|
||||
// InstalledPackageCard.stories.ts|tsx
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import InstalledPackageCard from "./InstalledPackageCard";
|
||||
|
||||
//👇 This default export determines where your story goes in the story list
|
||||
export default {
|
||||
/* 👇 The title prop is optional.
|
||||
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
||||
* to learn how to generate automatic titles
|
||||
*/
|
||||
title: "InstalledPackageCard",
|
||||
component: InstalledPackageCard,
|
||||
} as ComponentMeta<typeof InstalledPackageCard>;
|
||||
|
||||
//👇 We create a “template” of how args map to rendering
|
||||
const Template: ComponentStory<typeof InstalledPackageCard> = (args) => (
|
||||
<InstalledPackageCard {...args} />
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
release: {
|
||||
id: "",
|
||||
name: "",
|
||||
namespace: "",
|
||||
revision: 1,
|
||||
updated: "",
|
||||
status: "",
|
||||
chart: "",
|
||||
chart_name: "",
|
||||
chart_ver: "",
|
||||
app_version: "",
|
||||
icon: "",
|
||||
description: "",
|
||||
has_tests: false,
|
||||
chartName: "", // duplicated in some cases in the backend, we need to resolve this
|
||||
chartVersion: "", // duplicated in some cases in the backend, we need to resolve this
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,153 @@
|
||||
import { useState } from "react";
|
||||
import { Release } from "../../data/types";
|
||||
import { BsArrowUpCircleFill, BsPlusCircleFill } from "react-icons/bs";
|
||||
import { getAge } from "../../timeUtils";
|
||||
import StatusLabel, {
|
||||
DeploymentStatus,
|
||||
getStatusColor,
|
||||
} from "../common/StatusLabel";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import apiService from "../../API/apiService";
|
||||
import HealthStatus from "./HealthStatus";
|
||||
import HelmGrayIcon from "../../assets/helm-gray-50.svg";
|
||||
import Spinner from "../Spinner";
|
||||
import { useGetLatestVersion } from "../../API/releases";
|
||||
import { isNewerVersion } from "../../utils";
|
||||
import { LatestChartVersion } from "../../API/interfaces";
|
||||
import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
type InstalledPackageCardProps = {
|
||||
release: Release;
|
||||
};
|
||||
|
||||
export default function InstalledPackageCard({
|
||||
release,
|
||||
}: InstalledPackageCardProps) {
|
||||
const navigate = useNavigateWithSearchParams();
|
||||
|
||||
const { context: selectedCluster } = useParams();
|
||||
const [isMouseOver, setIsMouseOver] = useState(false);
|
||||
|
||||
const { data: latestVersionResult } = useGetLatestVersion(release.chartName, {
|
||||
queryKey: ["chartName", release.chartName],
|
||||
cacheTime: 0,
|
||||
});
|
||||
|
||||
const { data: statusData } = useQuery<any>({
|
||||
queryKey: ["resourceStatus", release],
|
||||
queryFn: () => apiService.getResourceStatus({ release }),
|
||||
});
|
||||
|
||||
const latestVersionData: LatestChartVersion | undefined =
|
||||
latestVersionResult?.[0];
|
||||
|
||||
const canUpgrade =
|
||||
!latestVersionData?.version || !release.chartVersion
|
||||
? false
|
||||
: isNewerVersion(release.chartVersion, latestVersionData?.version);
|
||||
|
||||
const installRepoSuggestion = latestVersionData?.isSuggestedRepo
|
||||
? latestVersionData.repository
|
||||
: null;
|
||||
|
||||
const handleMouseOver = () => {
|
||||
setIsMouseOver(true);
|
||||
};
|
||||
|
||||
const handleMouseOut = () => {
|
||||
setIsMouseOver(false);
|
||||
};
|
||||
|
||||
const handleOnClick = () => {
|
||||
const { name, namespace } = release;
|
||||
navigate(
|
||||
`/${selectedCluster}/${namespace}/${name}/installed/revision/${release.revision}`,
|
||||
{ state: release }
|
||||
);
|
||||
};
|
||||
|
||||
const statusColor = getStatusColor(release.status as DeploymentStatus);
|
||||
const borderLeftColor: { [key: string]: string } = {
|
||||
[DeploymentStatus.DEPLOYED]: "border-l-border-deployed",
|
||||
[DeploymentStatus.FAILED]: "border-l-text-danger",
|
||||
[DeploymentStatus.PENDING]: "border-l-border",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
borderLeftColor[release.status]
|
||||
} text-xs grid grid-cols-12 items-center bg-white rounded-md p-2 py-6 my-2 custom-shadow border-l-4 border-l-[${statusColor}] cursor-pointer ${
|
||||
isMouseOver && "custom-shadow-lg"
|
||||
}`}
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
onClick={handleOnClick}
|
||||
>
|
||||
<img
|
||||
src={release.icon || HelmGrayIcon}
|
||||
alt="helm release icon"
|
||||
className="w-[45px] mx-4 col-span-1 min-w-[45px]"
|
||||
/>
|
||||
|
||||
<div className="col-span-11 -mb-5">
|
||||
<div className="grid grid-cols-11">
|
||||
<div className="col-span-3 font-bold text-xl mr-0.5 font-roboto-slab">
|
||||
{release.name}
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<StatusLabel status={release.status} />
|
||||
</div>
|
||||
<div className="col-span-2 font-bold">{release.chart}</div>
|
||||
<div className="col-span-1 font-bold text-xs">
|
||||
#{release.revision}
|
||||
</div>
|
||||
<div className="col-span-1 font-bold text-xs">
|
||||
{release.namespace}
|
||||
</div>
|
||||
<div className="col-span-1 font-bold text-xs">{getAge(release)}</div>
|
||||
</div>
|
||||
<div
|
||||
className="grid grid-cols-11 text-xs mt-3"
|
||||
style={{ marginBottom: "12px" }}
|
||||
>
|
||||
<div className="col-span-3 h-12 line-clamp-3 mr-1">
|
||||
{release.description}
|
||||
</div>
|
||||
<div className="col-span-3 mr-2">
|
||||
{statusData ? (
|
||||
<HealthStatus statusData={statusData} />
|
||||
) : (
|
||||
<Spinner size={4} />
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-2 text-muted flex flex-col items">
|
||||
<span>CHART VERSION</span>
|
||||
{(canUpgrade || installRepoSuggestion) && (
|
||||
<div
|
||||
className="text-upgradable flex flex-row items-center gap-1 font-bold"
|
||||
title={`upgrade available: ${latestVersionData?.version} from ${latestVersionData?.repository}`}
|
||||
>
|
||||
{canUpgrade && !installRepoSuggestion ? (
|
||||
<>
|
||||
<BsArrowUpCircleFill />
|
||||
UPGRADE
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BsPlusCircleFill />
|
||||
ADD REPO
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-1 text-muted">REVISION</div>
|
||||
<div className="col-span-1 text-muted">NAMESPACE</div>
|
||||
<div className="col-span-1 text-muted">UPDATED</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// InstalledPackagesHeader.stories.ts|tsx
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import InstalledPackagesHeader from "./InstalledPackagesHeader";
|
||||
|
||||
//👇 This default export determines where your story goes in the story list
|
||||
export default {
|
||||
/* 👇 The title prop is optional.
|
||||
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
||||
* to learn how to generate automatic titles
|
||||
*/
|
||||
title: "InstalledPackagesHeader",
|
||||
component: InstalledPackagesHeader,
|
||||
} as ComponentMeta<typeof InstalledPackagesHeader>;
|
||||
|
||||
//👇 We create a “template” of how args map to rendering
|
||||
const Template: ComponentStory<typeof InstalledPackagesHeader> = (args) => (
|
||||
<InstalledPackagesHeader {...args} />
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
filteredReleases: [
|
||||
{
|
||||
id: "",
|
||||
name: "",
|
||||
namespace: "",
|
||||
revision: 1,
|
||||
updated: "",
|
||||
status: "",
|
||||
chart: "",
|
||||
chart_name: "",
|
||||
chart_ver: "",
|
||||
app_version: "",
|
||||
icon: "",
|
||||
description: "",
|
||||
has_tests: false,
|
||||
chartName: "", // duplicated in some cases in the backend, we need to resolve this
|
||||
chartVersion: "", // duplicated in some cases in the
|
||||
},
|
||||
{
|
||||
id: "",
|
||||
name: "",
|
||||
namespace: "",
|
||||
revision: 1,
|
||||
updated: "",
|
||||
status: "",
|
||||
chart: "",
|
||||
chart_name: "",
|
||||
chart_ver: "",
|
||||
app_version: "",
|
||||
icon: "",
|
||||
description: "",
|
||||
has_tests: false,
|
||||
chartName: "", // duplicated in some cases in the backend, we need to resolve this
|
||||
chartVersion: "", // duplicated in some cases in the
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import HeaderLogo from "../../assets/packges-header.svg";
|
||||
import { Release } from "../../data/types";
|
||||
|
||||
type InstalledPackagesHeaderProps = {
|
||||
filteredReleases?: Release[];
|
||||
setFilterKey: React.Dispatch<React.SetStateAction<string>>;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
export default function InstalledPackagesHeader({
|
||||
filteredReleases,
|
||||
setFilterKey,
|
||||
isLoading,
|
||||
}: InstalledPackagesHeaderProps) {
|
||||
const numOfPackages = filteredReleases?.length;
|
||||
const showNoPackageAlert = Boolean(
|
||||
!isLoading && (numOfPackages === undefined || numOfPackages === 0)
|
||||
);
|
||||
return (
|
||||
<div className="custom-shadow rounded-t-md ">
|
||||
<div className="flex items-center justify-between bg-white px-2 py-0.5 font-inter rounded-t-md ">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={HeaderLogo}
|
||||
alt="Helm-DashBoard"
|
||||
className="display-inline h-12 ml-3 mr-3 w-[28px] "
|
||||
/>
|
||||
<h2 className="display-inline font-bold text-base ">{`Installed Charts (${
|
||||
numOfPackages || "0"
|
||||
})`}</h2>
|
||||
</div>
|
||||
|
||||
<div className="w-1/3">
|
||||
<input
|
||||
className="border-installed-charts-filter rounded p-1 text-sm w-11/12"
|
||||
placeholder="Filter..."
|
||||
type="text"
|
||||
onChange={(ev) => setFilterKey(ev.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showNoPackageAlert && (
|
||||
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
|
||||
Looks like you don't have any charts installed.
|
||||
"Repository" section may be a good place to start.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// InstalledPackagesList.stories.ts|tsx
|
||||
|
||||
import { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||
import InstalledPackagesList from "./InstalledPackagesList";
|
||||
|
||||
//👇 This default export determines where your story goes in the story list
|
||||
export default {
|
||||
/* 👇 The title prop is optional.
|
||||
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
||||
* to learn how to generate automatic titles
|
||||
*/
|
||||
title: "InstalledPackagesList",
|
||||
component: InstalledPackagesList,
|
||||
} as ComponentMeta<typeof InstalledPackagesList>;
|
||||
|
||||
//👇 We create a “template” of how args map to rendering
|
||||
const Template: ComponentStory<typeof InstalledPackagesList> = (args) => (
|
||||
<InstalledPackagesList {...args} />
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
installedReleases: [
|
||||
{
|
||||
id: "",
|
||||
name: "",
|
||||
namespace: "",
|
||||
revision: 1,
|
||||
updated: "",
|
||||
status: "",
|
||||
chart: "",
|
||||
chart_name: "",
|
||||
chart_ver: "",
|
||||
app_version: "",
|
||||
icon: "",
|
||||
description: "",
|
||||
has_tests: false,
|
||||
chartName: "", // duplicated in some cases in the backend, we need to resolve this
|
||||
chartVersion: "", // duplicated in some cases in the
|
||||
},
|
||||
{
|
||||
id: "",
|
||||
name: "",
|
||||
namespace: "",
|
||||
revision: 1,
|
||||
updated: "",
|
||||
status: "",
|
||||
chart: "",
|
||||
chart_name: "",
|
||||
chart_ver: "",
|
||||
app_version: "",
|
||||
icon: "",
|
||||
description: "",
|
||||
has_tests: false,
|
||||
chartName: "", // duplicated in some cases in the backend, we need to resolve this
|
||||
chartVersion: "", // duplicated in some cases in the
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import InstalledPackageCard from "./InstalledPackageCard";
|
||||
import { Release } from "../../data/types";
|
||||
|
||||
type InstalledPackagesListProps = {
|
||||
filteredReleases: Release[];
|
||||
};
|
||||
|
||||
export default function InstalledPackagesList({
|
||||
filteredReleases,
|
||||
}: InstalledPackagesListProps) {
|
||||
return (
|
||||
<div>
|
||||
{filteredReleases.map((installedPackage: Release) => {
|
||||
return (
|
||||
<InstalledPackageCard
|
||||
key={installedPackage.name}
|
||||
release={installedPackage}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user