Huge bump of versions + husky + fixed DropDown key issue and pointer (#628)

* Bump lint-staged

* Check

* Check

* Added husky

* Check

* Check

* Check

* Check

* Check

* Check

* Check

* Check

* Fix husky

* Used * instead **/* in lint-staged

* Bump tailwindcss and related

* Added @tailwindcss/vite and removed postcss

* Added lint into staged

* Bump @babel/core and updated .prettierignore

* Removed tailwind.config.cjs

* Added ThemeInit

* Added cursor-pointer to Help dropdown

* Bump react-router

* Removed @types/uuid and react-router-dom

* Bump diff2html, prettier, @typescript-eslint/eslint-plugin, @typescript-eslint/parser

* removed vite-plugin-html-config and @babel/core

* removed "@eslint/eslintrc" and "@eslint/js"

* Removed redundant link

* Returned plugins and source to index.css

* Set dark to false in tailwindcss

* Fixed storybook

* Fixed useGetLatestVersion with correct gcTime: 0 option

* Added eslint-plugin-prettier

* Removed spaces

* ClustersList.tsx improved and type fixes for another files

* Repository.tsx improved

* Huge fix of types

* Huge fix of types missed

* Fixed type of SingleValue

* Added cursor pointer
This commit is contained in:
yuri-sakharov
2025-11-29 18:49:51 +02:00
committed by GitHub
parent 1129651e6c
commit 7572f00f7c
65 changed files with 1476 additions and 1893 deletions

2
.husky/pre-commit Executable file
View File

@@ -0,0 +1,2 @@
cd frontend || exit 1
npm run pre:commit

2
frontend/.flowbite-react/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
class-list.json
pid

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://unpkg.com/flowbite-react/schema.json",
"components": [],
"dark": false,
"path": "src/components",
"prefix": "",
"rsc": true,
"tsx": true,
"version": 4
}

View File

@@ -0,0 +1,22 @@
/* eslint-disable */
// @ts-nocheck
// biome-ignore-all lint: auto-generated file
// This file is auto-generated by the flowbite-react CLI.
// Do not edit this file directly.
// Instead, edit the .flowbite-react/config.json file.
import { StoreInit } from "flowbite-react/store/init";
import React from "react";
export const CONFIG = {
dark: true,
prefix: "",
version: 4,
};
export function ThemeInit() {
return <StoreInit {...CONFIG} />;
}
ThemeInit.displayName = "ThemeInit";

View File

@@ -1,3 +1,10 @@
# Ignore artifacts:
build
coverage
.env
.gitignore
.npmrc
.prettierignore
yarn.lock
package-lock.json
.flowbite-react/*

View File

@@ -3,19 +3,13 @@ import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx|mdx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-docs"
],
addons: ["@storybook/addon-links", "@storybook/addon-docs"],
core: {},
framework: {
name: "@storybook/react-vite",
options: {},
},
features: {
mdx2Csf: true,
}
};
export default config;

View File

@@ -1,7 +1,6 @@
import "tailwindcss/tailwind.css";
import "../src/index.css";
import { BrowserRouter } from "react-router-dom";
import { BrowserRouter } from "react-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { Preview, StoryFn } from "@storybook/react";

View File

@@ -2,6 +2,7 @@
Welcome to the frontend of the helm dashboard.
We care most about keeping the project:
1. Maintainable
2. Extendable
3. Contributor friendly
@@ -28,12 +29,11 @@ Please follow through the file structure to understand how things are structured
1. Make sure you cloned the project correctly. This is explained in this [stage](https://github.com/komodorio/helm-dashboard/blob/helm-dashboard-v2/dashboard/README.md#setting-up-your-development-environment).
2. run the backend server. This is also explained in the above link.
2. go to `frontend` in your local project.
3. in order to install dependencies and start the development server
3. go to `frontend` in your local project.
4. in order to install dependencies and start the development server
- `npm i`
- `npm run dev`
4. with the default integration the dashboard should run on http://localhost:5173/
5. with the default integration the dashboard should run on http://localhost:5173/
# Component library

View File

@@ -39,6 +39,8 @@ module.exports = defineConfig([{
extends: compat.extends(
"enpitech",
"eslint:recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
// "plugin:@typescript-eslint/recommended-requiring-type-checking", TODO enable and fix the types

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"dependencies": {
"@tanstack/react-query": "^5.90.11",
"compare-versions": "^6.1.1",
"diff2html": "^3.4.46",
"diff2html": "^3.4.52",
"flowbite-react": "^0.12.10",
"highlight.js": "^11.11.1",
"html-react-parser": "^5.2.10",
@@ -17,54 +17,57 @@
"react-icons": "^5.5.0",
"react-intersection-observer": "^10.0.0",
"react-modern-drawer": "^1.4.0",
"react-router-dom": "^6.9.0",
"react-router": "^7.9.6",
"react-select": "^5.10.2",
"swagger-ui-react": "^5.30.3",
"uuid": "^13.0.0"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.1",
"@storybook/addon-docs": "^10.0.8",
"@storybook/addon-links": "^10.0.8",
"@storybook/mdx2-csf": "^1.1.0",
"@storybook/react-vite": "^10.0.8",
"@tailwindcss/vite": "^4.1.17",
"@types/luxon": "^3.7.1",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/swagger-ui-react": "^5.18.0",
"@types/uuid": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0",
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.48.0",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.14",
"autoprefixer": "^10.4.22",
"cypress": "^13.3.0",
"eslint": "^9.39.1",
"eslint-config-enpitech": "^1.0.17",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-storybook": "^10.0.8",
"flowbite": "^4.0.1",
"globals": "^16.5.0",
"lint-staged": "^13.2.3",
"postcss": "^8.4.24",
"prettier": "^3.6.2",
"husky": "^9.1.7",
"lint-staged": "^16.2.7",
"prettier": "^3.7.1",
"storybook": "10.0.8",
"tailwindcss": "^3.3.2",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"vite": "^7.2.4",
"vite-plugin-html-config": "^2.0.2",
"vite-plugin-static-copy": "^3.1.4"
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
"src/*.{js,jsx,ts,tsx}": [
"npm run lint:fix",
"npm run prettier:fix"
],
"*.{json,css,md,mdx}": "npm run prettier:fix"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"prepare": "cd .. && husky",
"pre:commit": "lint-staged",
"test": "echo \"Error: no test specified. Please use 'cypress:run' or 'cypress:open' commands\" && exit 1",
"tsc:check": "tsc --noEmit",
"storybook": "storybook dev -p 6006",

View File

@@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

View File

@@ -4,9 +4,9 @@ import {
Release,
ReleaseHealthStatus,
ReleaseRevision,
Repository,
} from "../data/types";
import { type QueryFunctionContext } from "@tanstack/react-query";
interface ClustersResponse {
AuthInfo: string;
Cluster: string;
@@ -90,12 +90,17 @@ class ApiService {
getRepositoryCharts = async ({
queryKey,
}: QueryFunctionContext<Chart[], Repository>) => {
}: {
queryKey: readonly unknown[];
}): Promise<Chart[]> => {
const [, repository] = queryKey;
const data = await this.fetchWithDefaults(
if (!repository) {
return [];
}
return await this.fetchWithDefaults<Chart[]>(
`/api/helm/repositories/${repository}`
);
return data;
};
getChartVersions = async ({
@@ -126,16 +131,16 @@ class ApiService {
getReleasesHistory = async ({
queryKey,
}: QueryFunctionContext<Release[], Release>): Promise<ReleaseRevision[]> => {
}: {
queryKey: readonly [string, Record<string, string | undefined>];
}): Promise<ReleaseRevision[]> => {
const [, params] = queryKey;
if (!params.namespace || !params.chart) return [];
const data = await this.fetchWithDefaults<ReleaseRevision[]>(
return await this.fetchWithDefaults<ReleaseRevision[]>(
`/api/helm/releases/${params.namespace}/${params.chart}/history`
);
return data;
};
getValues = async ({

View File

@@ -1,24 +1,23 @@
import {
useQuery,
type UseQueryOptions,
useMutation,
type UseMutationOptions,
useQuery,
type UseQueryOptions,
} from "@tanstack/react-query";
import { ChartVersion, Release } from "../data/types";
import { LatestChartVersion } from "./interfaces";
import apiService from "./apiService";
import { getVersionManifestFormData } from "./shared";
import { isNewerVersion } from "../utils";
export const HD_RESOURCE_CONDITION_TYPE = "hdHealth"; // it's our custom condition type, only one exists
export function useGetInstalledReleases(
context: string,
options?: UseQueryOptions<Release[]>
) {
export function useGetInstalledReleases(context: string) {
return useQuery<Release[]>({
queryKey: ["installedReleases", context],
queryFn: () =>
apiService.fetchWithDefaults<Release[]>("/api/helm/releases"),
...(options ?? {}),
retry: false,
});
}
@@ -74,18 +73,14 @@ export function useGetReleaseManifest({
}
// List of installed k8s resources for this release
export function useGetResources(
ns: string,
name: string,
options?: UseQueryOptions<StructuredResources[]>
) {
export function useGetResources(ns: string, name: string, enabled?: boolean) {
const { data, ...rest } = useQuery<StructuredResources[]>({
queryKey: ["resources", ns, name],
queryFn: () =>
apiService.fetchWithDefaults<StructuredResources[]>(
`/api/helm/releases/${ns}/${name}/resources?health=true`
),
...(options ?? {}),
enabled,
});
return {
@@ -138,6 +133,7 @@ export function useGetLatestVersion(
apiService.fetchWithDefaults<ChartVersion[]>(
`/api/helm/repositories/latestver?name=${chartName}`
),
gcTime: 0,
...(options ?? {}),
});
}
@@ -151,6 +147,8 @@ export function useGetVersions(
apiService.fetchWithDefaults<LatestChartVersion[]>(
`/api/helm/repositories/versions?name=${chartName}`
),
select: (data) =>
data?.sort((a, b) => (isNewerVersion(a.version, b.version) ? 1 : -1)),
...(options ?? {}),
});
}
@@ -250,12 +248,12 @@ export function useChartReleaseValues({
userDefinedValue?: string;
revision?: number;
version?: string;
options?: UseQueryOptions<unknown>;
options?: UseQueryOptions<string>;
}) {
return useQuery<unknown>({
return useQuery<string>({
queryKey: ["values", namespace, release, userDefinedValue, version],
queryFn: () =>
apiService.fetchWithDefaults<unknown>(
apiService.fetchWithDefaults(
`/api/helm/releases/${namespace}/${release}/values?${"userDefined=true"}${
revision ? `&revision=${revision}` : ""
}`,
@@ -267,6 +265,12 @@ export function useChartReleaseValues({
});
}
export type VersionData = {
version: string;
repository?: string;
urls: string[];
};
export const useVersionData = ({
version,
userValues,
@@ -275,7 +279,7 @@ export const useVersionData = ({
namespace,
releaseName,
isInstallRepoChart = false,
options,
enabled = true,
}: {
version: string;
userValues: string;
@@ -284,9 +288,9 @@ export const useVersionData = ({
namespace: string;
releaseName: string;
isInstallRepoChart?: boolean;
options?: UseQueryOptions;
enabled?: boolean;
}) => {
return useQuery({
return useQuery<{ [key: string]: string }>({
queryKey: [
version,
userValues,
@@ -311,15 +315,16 @@ export const useVersionData = ({
namespace ? namespace : "[empty]"
}${`/${releaseName}`}`;
const data = await apiService.fetchWithDefaults(fetchUrl, {
method: "post",
body: formData,
});
return data;
return await apiService.fetchWithDefaults<{ [key: string]: string }>(
fetchUrl,
{
method: "post",
body: formData,
}
);
},
...(options ?? {}),
enabled,
});
};

View File

@@ -1,5 +1,5 @@
import Header from "./layout/Header";
import { HashRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
import { HashRouter, Outlet, Route, Routes, useParams } from "react-router";
import "./index.css";
import Installed from "./pages/Installed";
import RepositoryPage from "./pages/Repository";

View File

@@ -17,6 +17,7 @@
*
*
*/
import { JSX, ReactNode } from "react";
export type BadgeCode = "success" | "warning" | "error" | "unknown";
@@ -29,7 +30,7 @@ export const BadgeCodes = Object.freeze({
export interface BadgeProps {
type: BadgeCode;
children: React.ReactNode;
children: ReactNode;
additionalClassNames?: string;
}
export default function Badge(props: BadgeProps): JSX.Element {
@@ -41,7 +42,7 @@ export default function Badge(props: BadgeProps): JSX.Element {
};
const badgeBase =
"inline-flex items-center px-1 py-1 rounded text-xs font-light";
"inline-flex items-center px-1 py-1 rounded-sm text-xs font-light";
const badgeElem = (
<span

View File

@@ -5,7 +5,7 @@ describe("Button component tests", () => {
const buttonText = "buttonText";
it("renders", () => {
mount(<Button onClick={() => {}}></Button>);
mount(<Button onClick={() => {}} label=""></Button>);
cy.get("button").should("exist");
});
@@ -17,14 +17,14 @@ describe("Button component tests", () => {
it("calls onClick when clicked", () => {
const onClickStub = cy.stub().as("onClick");
mount(<Button onClick={onClickStub}></Button>);
mount(<Button onClick={onClickStub} label={""}></Button>);
cy.get("button").click();
cy.get("@onClick").should("have.been.calledOnce");
});
it("should be disabled", () => {
mount(<Button onClick={() => {}} disabled></Button>);
mount(<Button onClick={() => {}} disabled label={""}></Button>);
cy.get("button").should("be.disabled");
});

View File

@@ -12,11 +12,12 @@
*
*
*/
import { HTMLAttributes, JSX, ReactNode } from "react";
// this is a type declaration for the action prop.
// it is a function that takes a string as an argument and returns void.
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
export interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
children: ReactNode;
disabled?: boolean;
onClick: () => void;
className?: string;
@@ -26,7 +27,7 @@ export default function Button(props: ButtonProps): JSX.Element {
<>
<button
onClick={props.onClick}
className={`${props.className} bg-white border border-gray-300 hover:bg-gray-50 text-black py-1 px-4 rounded `}
className={`${props.className} bg-white border border-gray-300 hover:bg-gray-50 text-black py-1 px-4 rounded-sm `}
disabled={props.disabled}
>
{props.children}

View File

@@ -1,6 +1,6 @@
import { AppContextProvider } from "../context/AppContext";
import ClustersList from "./ClustersList";
import { BrowserRouter } from "react-router-dom";
import { BrowserRouter } from "react-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Release } from "../data/types";

View File

@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { useEffect, useEffectEvent, useMemo, useState } from "react";
import { Cluster, Release } from "../data/types";
import apiService from "../API/apiService";
import { useQuery } from "@tanstack/react-query";
@@ -43,30 +43,39 @@ function ClustersList({
}: ClustersListProps) {
const { upsertSearchParams, removeSearchParam } = useCustomSearchParams();
const { clusterMode } = useAppContext();
const [sortedClusters, setSortedClusters] = useState<Cluster[]>([]);
const { data: clusters } = useQuery<Cluster[]>({
const { data: clusters, isSuccess } = useQuery<Cluster[]>({
queryKey: ["clusters", selectedCluster],
queryFn: apiService.getClusters,
onSuccess(data) {
const sortedData = data?.sort((a, b) =>
getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name))
);
if (sortedData && sortedData.length > 0 && !selectedCluster) {
onClusterChange(sortedData[0].Name);
}
if (selectedCluster) {
const cluster = data.find(
(cluster) => getCleanClusterName(cluster.Name) === selectedCluster
);
if (!filteredNamespaces && cluster?.Namespace) {
upsertSearchParams("filteredNamespace", cluster.Namespace);
}
}
},
});
const onSuccess = useEffectEvent((clusters: Cluster[]) => {
const sortedData = [...clusters].sort((a, b) =>
getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name))
);
setSortedClusters(sortedData);
if (sortedData && sortedData.length > 0 && !selectedCluster) {
onClusterChange(sortedData[0].Name);
}
if (selectedCluster) {
const cluster = clusters.find(
(cluster) => getCleanClusterName(cluster.Name) === selectedCluster
);
if (!filteredNamespaces && cluster?.Namespace) {
upsertSearchParams("filteredNamespace", cluster.Namespace);
}
}
});
useEffect(() => {
if (clusters && isSuccess) {
onSuccess(clusters);
}
}, [clusters, isSuccess]);
const namespaces = useMemo(() => {
const mapNamespaces = new Map<string, number>();
@@ -98,39 +107,33 @@ function ClustersList({
};
return (
<div className="bg-white flex flex-col p-2 rounded custom-shadow text-cluster-list w-48 m-5 h-fit pb-4 custom-">
<div className="bg-white flex flex-col p-2 rounded-sm custom-shadow text-cluster-list w-48 m-5 h-fit pb-4 custom-">
{!clusterMode ? (
<>
<label className="font-bold">Clusters</label>
{clusters
?.sort((a, b) =>
getCleanClusterName(a.Name).localeCompare(
getCleanClusterName(b.Name)
)
)
?.map((cluster) => {
return (
<span
key={cluster.Name}
className="data-cy-clusterName flex items-center mt-2 text-xs"
>
<input
className="cursor-pointer data-cy-clustersInput"
onChange={(e) => {
onClusterChange(e.target.value);
}}
type="radio"
id={cluster.Name}
value={cluster.Name}
checked={cluster.Name === selectedCluster}
name="clusters"
/>
<label htmlFor={cluster.Name} className="ml-1 ">
{getCleanClusterName(cluster.Name)}
</label>
</span>
);
})}
{sortedClusters?.map((cluster) => {
return (
<span
key={cluster.Name}
className="data-cy-clusterName flex items-center mt-2 text-xs"
>
<input
className="cursor-pointer data-cy-clustersInput"
onChange={(e) => {
onClusterChange(e.target.value);
}}
type="radio"
id={cluster.Name}
value={cluster.Name}
checked={cluster.Name === selectedCluster}
name="clusters"
/>
<label htmlFor={cluster.Name} className="ml-1 ">
{getCleanClusterName(cluster.Name)}
</label>
</span>
);
})}
</>
) : null}

View File

@@ -27,7 +27,7 @@ const HealthStatus = ({ statusData }: Props) => {
: cond.status === "Progressing"
? "bg-warning"
: "bg-danger"
} w-2.5 h-2.5 rounded-sm`}
} w-2.5 h-2.5 rounded-xs`}
></span>
</Tooltip>
);

View File

@@ -1,5 +1,5 @@
import { useState } from "react";
import { Release } from "../../data/types";
import { Release, ReleaseHealthStatus } from "../../data/types";
import { BsArrowUpCircleFill, BsPlusCircleFill } from "react-icons/bs";
import { getAge } from "../../timeUtils";
import StatusLabel, {
@@ -33,13 +33,12 @@ export default function InstalledPackageCard({
});
const { data: latestVersionResult } = useGetLatestVersion(release.chartName, {
queryKey: ["chartName", release.chartName],
cacheTime: 0,
});
const { data: statusData } = useQuery<unknown>({
const { data: statusData } = useQuery<ReleaseHealthStatus[] | null>({
queryKey: ["resourceStatus", release],
enabled: inView,
queryFn: () => apiService.getResourceStatus({ release }),
enabled: inView,
});
const latestVersionData: LatestChartVersion | undefined =

View File

@@ -32,7 +32,7 @@ export default function InstalledPackagesHeader({
<div className="w-1/3">
<input
className="border-installed-charts-filter rounded p-1 text-sm w-11/12"
className="border-installed-charts-filter rounded-sm p-1 text-sm w-11/12"
placeholder="Filter..."
type="text"
onChange={(ev) => setFilterKey(ev.target.value)}
@@ -41,7 +41,7 @@ export default function InstalledPackagesHeader({
</div>
{showNoPackageAlert && (
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
Looks like you don&apos;t have any charts installed.
&quot;Repository&quot; section may be a good place to start.
</div>

View File

@@ -1,4 +1,4 @@
import { NavLink, useLocation, useParams } from "react-router-dom";
import { NavLink, useLocation, useParams } from "react-router";
import { useAppContext } from "../context/AppContext";
const LinkWithSearchParams = ({
@@ -12,7 +12,7 @@ const LinkWithSearchParams = ({
children: React.ReactNode;
}) => {
const { search } = useLocation();
const { context } = useParams();
const { context = "" } = useParams();
const { clusterMode } = useAppContext();
const params = new URLSearchParams(search);

View File

@@ -24,6 +24,7 @@
*
*
*/
import { JSX } from "react";
// define the SelectMenuItem type:
// This is an object with a label and id.

View File

@@ -22,7 +22,7 @@ function ShutDownButton() {
<button
onClick={handleClick}
title="Shut down the Helm Dashboard application"
className="flex justify-center w-full mr-5 py-3 border border-transparent hover:border hover:border-gray-500 rounded hover:rounded-lg"
className="flex justify-center w-full mr-5 py-3 border border-transparent hover:border hover:border-gray-500 rounded-sm hover:rounded-lg"
>
<BsPower className="w-6" />
</button>

View File

@@ -25,7 +25,7 @@ export default function Tabs({ tabs, selectedTab }: TabsProps) {
{tabs.map((tab) => (
<button
key={tab.label}
className={`cursor-pointer px-4 py-2 text-sm font-normal text-tab-color focus:outline-none"
className={`cursor-pointer px-4 py-2 text-sm font-normal text-tab-color focus:outline-hidden"
${
selectedTab.value === tab.value &&
"border-b-[3px] border-tab-color"

View File

@@ -14,6 +14,7 @@
*
*
*/
import { JSX } from "react";
interface TabsBarProps {
tabs: Array<{ name: string; component: JSX.Element }>;

View File

@@ -12,6 +12,7 @@
* @return JSX.Element
*
*/
import { JSX } from "react";
export interface TextInputProps {
label: string;

View File

@@ -1,4 +1,4 @@
import { type ReactElement, cloneElement } from "react";
import { type ReactElement, cloneElement, HTMLAttributes } from "react";
export default function Tooltip({
id,
@@ -11,11 +11,16 @@ export default function Tooltip({
}) {
return (
<>
{cloneElement(element, { "data-tooltip-target": id })}
{cloneElement(
element as ReactElement<HTMLAttributes<HTMLElement>>,
{
"data-tooltip-target": id,
} as HTMLAttributes<HTMLElement>
)}
<div
id={id}
role="tooltip"
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700"
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-xs opacity-0 tooltip dark:bg-gray-700"
>
{title}
<div className="tooltip-arrow" data-popper-arrow></div>
@@ -24,14 +29,14 @@ export default function Tooltip({
<button
data-tooltip-target="tooltip-default"
type="button"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Default tooltip
</button>
<div
id="tooltip-default"
role="tooltip"
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700"
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-xs opacity-0 tooltip dark:bg-gray-700"
>
Tooltip content
<div className="tooltip-arrow" data-popper-arrow></div>

View File

@@ -8,7 +8,7 @@ export const Troubleshoot = () => {
target="_blank"
rel="noreferrer"
>
<button className="bg-primary text-white p-2 flex items-center rounded text-sm font-medium font-roboto">
<button className="bg-primary text-white p-2 flex items-center rounded-sm text-sm font-medium font-roboto">
Troubleshoot in Komodor
<RiExternalLinkLine className="ml-2 text-lg" />
</button>

View File

@@ -21,6 +21,7 @@ interface ButtonProps {
* Optional click handler
*/
onClick?: () => void;
disabled?: boolean;
}
/**

View File

@@ -1,4 +1,4 @@
import { ReactNode, useEffect, useRef, useState } from "react";
import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
import ArrowDownIcon from "../../assets/arrow-down-icon.svg";
export type DropDownItem = {
@@ -62,7 +62,7 @@ function DropDown({ items }: DropDownProps) {
Y: e.pageY,
}));
}}
className="flex items-center justify-between"
className="flex items-center justify-between cursor-pointer"
>
Help
<img src={ArrowDownIcon} className="ml-2 w-[10px] h-[10px]" />
@@ -71,10 +71,10 @@ function DropDown({ items }: DropDownProps) {
{popupState.isOpen && (
<div
ref={modalRef}
className={`z-10 flex flex-col py-1 gap-1 bg-white mt-3 absolute rounded border top-[${popupState.Y}] left-[${popupState.X}] border-gray-200`}
className={`z-10 flex flex-col py-1 gap-1 bg-white mt-3 absolute rounded-sm border top-[${popupState.Y}] left-[${popupState.X}] border-gray-200`}
>
{items.map((item) => (
<>
<Fragment key={item.id}>
{item.isSeparator ? (
<div className="bg-gray-300 h-[1px]" />
) : (
@@ -96,7 +96,7 @@ function DropDown({ items }: DropDownProps) {
<span>{item.text}</span>
</div>
)}
</>
</Fragment>
))}
</div>
)}

View File

@@ -1,4 +1,4 @@
import { VFC, useState } from "react";
import { useState } from "react";
import { Header } from "../Header/Header";
import "./page.css";
@@ -7,7 +7,7 @@ type User = {
name: string;
};
export const Page: VFC = () => {
export const Page = () => {
const [user, setUser] = useState<User>();
return (

View File

@@ -5,7 +5,7 @@ import useAlertError from "../../hooks/useAlertError";
import useCustomSearchParams from "../../hooks/useCustomSearchParams";
import { useAppContext } from "../../context/AppContext";
import { useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import apiService from "../../API/apiService";
interface FormKeys {
@@ -80,10 +80,10 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
isOpen={isOpen}
onClose={onClose}
bottomContent={
<div className="flex justify-end p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<div className="flex justify-end p-6 gap-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<button
data-cy="add-chart-repository-button"
className="flex items-center text-white font-medium px-3 py-1.5 bg-primary hover:bg-add-repo focus:ring-4 focus:outline-none focus:ring-blue-300 disabled:bg-blue-300 rounded-lg text-base text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
className="flex items-center text-white font-medium px-3 py-1.5 bg-primary hover:bg-add-repo focus:ring-4 focus:outline-hidden focus:ring-blue-300 disabled:bg-blue-300 rounded-lg text-base text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 cursor-pointer"
onClick={addRepository}
disabled={isLoading}
>
@@ -109,7 +109,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
data-cy="add-chart-name"
type="text"
placeholder="Komodorio"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
/>
</label>
<label className="flex-1" htmlFor="url">
@@ -127,7 +127,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
data-cy="add-chart-url"
type="text"
placeholder="https://helm-charts.komodor.io"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
/>
</label>
</div>
@@ -144,7 +144,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
required
id="username"
type="text"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
/>
</label>
<label className="flex-1" htmlFor="password">
@@ -159,7 +159,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
required
id="password"
type="text"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
/>
</label>
</div>

View File

@@ -33,7 +33,7 @@ export default function ErrorModal({
);
const bottomContent = (
<div className="flex py-6 px-4 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<div className="flex py-6 px-4 gap-2 border-t border-gray-200 rounded-b dark:border-gray-600">
<span className="text-sm text-muted fs-80 text-gray-500">
Hint: Komodor has the same HELM capabilities, with enterprise features
and support.{" "}

View File

@@ -17,7 +17,7 @@ export const ChartValues = ({
Chart Value Reference:
</label>
<pre
className="text-base bg-chart-values p-2 rounded font-medium w-full max-h-[330px] block overflow-y-auto font-sf-mono"
className="text-base bg-chart-values p-2 rounded-sm font-medium w-full max-h-[330px] block overflow-y-auto font-sf-mono"
dangerouslySetInnerHTML={
chartValues && !loading
? {

View File

@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import useDebounce from "../../../hooks/useDebounce";
export const GeneralDetails = ({
@@ -27,7 +27,7 @@ export const GeneralDetails = ({
const { context } = useParams();
const inputClassName = ` text-lg py-1 px-2 border border-1 border-gray-300 ${
disabled ? "bg-gray-200" : "bg-white "
} rounded`;
} rounded-sm`;
return (
<div className="flex gap-8">
<div>

View File

@@ -1,10 +1,11 @@
import { useParams } from "react-router-dom";
import { useMemo, useState } from "react";
import { useParams } from "react-router";
import { useEffect, useEffectEvent, useMemo, useState } from "react";
import {
useChartReleaseValues,
useGetReleaseManifest,
useGetVersions,
useVersionData,
VersionData,
} from "../../../API/releases";
import Modal, { ModalButtonStyle } from "../Modal";
import { GeneralDetails } from "./GeneralDetails";
@@ -12,7 +13,7 @@ import { ManifestDiff } from "./ManifestDiff";
import { useMutation } from "@tanstack/react-query";
import useNavigateWithSearchParams from "../../../hooks/useNavigateWithSearchParams";
import { VersionToInstall } from "./VersionToInstall";
import { isNewerVersion, isNoneEmptyArray } from "../../../utils";
import { isNoneEmptyArray } from "../../../utils";
import useCustomSearchParams from "../../../hooks/useCustomSearchParams";
import { useChartRepoValues } from "../../../API/repositories";
import { useDiffData } from "../../../API/shared";
@@ -20,18 +21,18 @@ import { InstallChartModalProps } from "../../../data/types";
import { DefinedValues } from "./DefinedValues";
import apiService from "../../../API/apiService";
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
import { LatestChartVersion } from "../../../API/interfaces";
export const InstallReleaseChartModal = ({
isOpen,
onClose,
chartName,
currentlyInstalledChartVersion,
latestVersion,
isUpgrade = false,
latestRevision,
}: InstallChartModalProps) => {
const navigate = useNavigateWithSearchParams();
const [userValues, setUserValues] = useState<string>();
const [userValues, setUserValues] = useState<string>("");
const [installError, setInstallError] = useState("");
const {
@@ -44,38 +45,37 @@ export const InstallReleaseChartModal = ({
const [namespace, setNamespace] = useState(queryNamespace || "");
const [releaseName, setReleaseName] = useState(_releaseName || "");
const { error: versionsError, data: _versions } = useGetVersions(chartName, {
select: (data) => {
return data?.sort((a, b) =>
isNewerVersion(a.version, b.version) ? 1 : -1
);
},
onSuccess: (data) => {
const empty = { version: "", repository: "", urls: [] };
return setSelectedVersionData(data[0] ?? empty);
},
const {
error: versionsError,
data: _versions = [],
isSuccess,
} = useGetVersions(chartName);
const [selectedVersionData, setSelectedVersionData] = useState<VersionData>();
const [versions, setVersions] = useState<
Array<LatestChartVersion & { isChartVersion: boolean }>
>([]);
const onSuccess = useEffectEvent(() => {
const empty = { version: "", repository: "", urls: [] };
setSelectedVersionData(_versions[0] ?? empty);
setVersions(
_versions?.map((v) => ({
...v,
isChartVersion: v.version === currentlyInstalledChartVersion,
}))
);
});
const versions = _versions?.map((v) => ({
...v,
isChartVersion: v.version === currentlyInstalledChartVersion,
}));
useEffect(() => {
if (isSuccess && _versions.length) {
onSuccess();
}
}, [isSuccess, _versions]);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
latestVersion = latestVersion ?? currentlyInstalledChartVersion; // a guard for typescript, latestVersion is always defined
const [selectedVersionData, setSelectedVersionData] = useState<{
version: string;
repository?: string;
urls: string[];
}>();
const selectedVersion = useMemo(() => {
return selectedVersionData?.version;
}, [selectedVersionData]);
const selectedRepo = useMemo(() => {
return selectedVersionData?.repository || "";
}, [selectedVersionData]);
const selectedVersion = selectedVersionData?.version || "";
const selectedRepo = selectedVersionData?.repository || "";
const chartAddress = useMemo(() => {
if (!selectedVersionData || !selectedVersionData.repository) return "";
@@ -86,13 +86,13 @@ export const InstallReleaseChartModal = ({
}, [selectedVersionData, chartName]);
// the original chart values
const { data: chartValues } = useChartRepoValues({
version: selectedVersion || "",
const { data: chartValues = "" } = useChartRepoValues({
version: selectedVersion,
chart: chartAddress,
});
// The user defined values (if any we're set)
const { data: releaseValues, isLoading: loadingReleaseValues } =
const { data: releaseValues = "", isLoading: loadingReleaseValues } =
useChartReleaseValues({
namespace,
release: String(releaseName),
@@ -100,16 +100,15 @@ export const InstallReleaseChartModal = ({
});
// This hold the selected version manifest, we use it for the diff
const { data: selectedVerData, error: selectedVerDataError } = useVersionData(
{
version: selectedVersion || "",
userValues: userValues || "",
const { data: selectedVerData = {}, error: selectedVerDataError } =
useVersionData({
version: selectedVersion,
userValues,
chartAddress,
releaseValues,
namespace,
releaseName,
}
);
});
const { data: currentVerManifest, error: currentVerManifestError } =
useGetReleaseManifest({
@@ -123,14 +122,14 @@ export const InstallReleaseChartModal = ({
error: diffError,
} = useDiffData({
selectedRepo,
versionsError: versionsError as string,
currentVerManifest,
versionsError: versionsError as unknown as string, // TODO fix it
currentVerManifest: currentVerManifest as unknown as string, // TODO fix it
selectedVerData,
chart: chartAddress,
});
// Confirm method (install)
const setReleaseVersionMutation = useMutation({
const setReleaseVersionMutation = useMutation<VersionData>({
mutationKey: [
"setVersion",
namespace,
@@ -149,8 +148,7 @@ export const InstallReleaseChartModal = ({
}
formData.append("version", selectedVersion || "");
formData.append("values", userValues || releaseValues || ""); // if userValues is empty, we use the release values
const data = await apiService.fetchWithDefaults(
return await apiService.fetchWithDefaults(
`/api/helm/releases/${
namespace ? namespace : "default"
}${`/${releaseName}`}`,
@@ -159,7 +157,6 @@ export const InstallReleaseChartModal = ({
body: formData,
}
);
return data;
},
onSuccess: async (response) => {
onClose();
@@ -187,7 +184,7 @@ export const InstallReleaseChartModal = ({
title={
<InstallUpgradeTitle
isUpgrade={isUpgrade}
releaseValues={isUpgrade || releaseValues}
releaseValues={isUpgrade || !!releaseValues}
chartName={chartName}
/>
}
@@ -197,11 +194,11 @@ export const InstallReleaseChartModal = ({
id: "1",
callback: setReleaseVersionMutation.mutate,
variant: ModalButtonStyle.info,
isLoading: setReleaseVersionMutation.isLoading,
isLoading: setReleaseVersionMutation.isPending,
disabled:
loadingReleaseValues ||
isLoadingDiff ||
setReleaseVersionMutation.isLoading,
setReleaseVersionMutation.isPending,
},
]}
>
@@ -233,11 +230,11 @@ export const InstallReleaseChartModal = ({
diff={diffData as string}
isLoading={isLoadingDiff}
error={
(currentVerManifestError as string) ||
(selectedVerDataError as string) ||
(diffError as string) ||
(currentVerManifestError as unknown as string) || // TODO fix it
(selectedVerDataError as unknown as string) ||
(diffError as unknown as string) ||
installError ||
(versionsError as string)
(versionsError as unknown as string)
}
/>
</Modal>

View File

@@ -1,5 +1,5 @@
import { useParams } from "react-router-dom";
import { useMemo, useState } from "react";
import { useParams } from "react-router";
import { useEffect, useEffectEvent, useMemo, useState } from "react";
import { useGetVersions, useVersionData } from "../../../API/releases";
import Modal, { ModalButtonStyle } from "../Modal";
import { GeneralDetails } from "./GeneralDetails";
@@ -8,19 +8,19 @@ import { useMutation } from "@tanstack/react-query";
import { useChartRepoValues } from "../../../API/repositories";
import useNavigateWithSearchParams from "../../../hooks/useNavigateWithSearchParams";
import { VersionToInstall } from "./VersionToInstall";
import { isNewerVersion, isNoneEmptyArray } from "../../../utils";
import { isNoneEmptyArray } from "../../../utils";
import { useDiffData } from "../../../API/shared";
import { InstallChartModalProps } from "../../../data/types";
import { DefinedValues } from "./DefinedValues";
import apiService from "../../../API/apiService";
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
import { LatestChartVersion } from "../../../API/interfaces";
export const InstallRepoChartModal = ({
isOpen,
onClose,
chartName,
currentlyInstalledChartVersion,
latestVersion,
}: InstallChartModalProps) => {
const navigate = useNavigateWithSearchParams();
const [userValues, setUserValues] = useState("");
@@ -31,42 +31,46 @@ export const InstallRepoChartModal = ({
const [namespace, setNamespace] = useState("");
const [releaseName, setReleaseName] = useState(chartName);
const { error: versionsError, data: _versions } = useGetVersions(chartName, {
select: (data) => {
return data?.sort((a, b) =>
isNewerVersion(a.version, b.version) ? 1 : -1
);
},
onSuccess: (data) => {
const empty = { version: "", repository: "", urls: [] };
const versionsToRepo = data.filter(
(v) => v.repository === currentRepoCtx
);
const {
error: versionsError,
data: _versions = [],
isSuccess,
} = useGetVersions(chartName);
return setSelectedVersionData(versionsToRepo[0] ?? empty);
},
});
const [versions, setVersions] = useState<
Array<LatestChartVersion & { isChartVersion: boolean }>
>([]);
const versions = _versions?.map((v) => ({
...v,
isChartVersion: v.version === currentlyInstalledChartVersion,
}));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
latestVersion = latestVersion ?? currentlyInstalledChartVersion; // a guard for typescript, latestVersion is always defined
const [selectedVersionData, setSelectedVersionData] = useState<{
version: string;
repository?: string;
urls: string[];
}>();
const selectedVersion = useMemo(() => {
return selectedVersionData?.version;
}, [selectedVersionData]);
const onSuccess = useEffectEvent(() => {
const empty = { version: "", repository: "", urls: [] };
const versionsToRepo = _versions.filter(
(v) => v.repository === currentRepoCtx
);
const selectedRepo = useMemo(() => {
return selectedVersionData?.repository;
}, [selectedVersionData]);
setSelectedVersionData(versionsToRepo[0] ?? empty);
setVersions(
_versions?.map((v) => ({
...v,
isChartVersion: v.version === currentlyInstalledChartVersion,
}))
);
});
useEffect(() => {
if (isSuccess && _versions.length) {
onSuccess();
}
}, [isSuccess, _versions]);
const selectedVersion = selectedVersionData?.version;
const selectedRepo = selectedVersionData?.repository;
const chartAddress = useMemo(() => {
if (!selectedVersionData || !selectedVersionData?.repository) {
@@ -77,15 +81,15 @@ export const InstallRepoChartModal = ({
: `${selectedVersionData?.repository}/${chartName}`;
}, [selectedVersionData, chartName]);
const { data: chartValues, isLoading: loadingChartValues } =
const { data: chartValues = "", isLoading: loadingChartValues } =
useChartRepoValues({
version: selectedVersion || "",
chart: chartAddress,
});
// This hold the selected version manifest, we use it for the diff
const { data: selectedVerData, error: selectedVerDataError } = useVersionData(
{
const { data: selectedVerData = {}, error: selectedVerDataError } =
useVersionData({
version: selectedVersion || "",
userValues,
chartAddress,
@@ -93,11 +97,8 @@ export const InstallRepoChartModal = ({
namespace,
releaseName,
isInstallRepoChart: true,
options: {
enabled: Boolean(chartAddress),
},
}
);
enabled: Boolean(chartAddress),
});
const {
data: diffData,
@@ -105,14 +106,17 @@ export const InstallRepoChartModal = ({
error: diffError,
} = useDiffData({
selectedRepo: selectedRepo || "",
versionsError: versionsError as string,
versionsError: versionsError as unknown as string, // TODO fix it
currentVerManifest: "", // current version manifest should always be empty since its a fresh install
selectedVerData,
chart: chartAddress,
});
// Confirm method (install)
const setReleaseVersionMutation = useMutation({
const setReleaseVersionMutation = useMutation<{
namespace: string;
name: string;
}>({
mutationKey: [
"setVersion",
namespace,
@@ -130,17 +134,17 @@ export const InstallRepoChartModal = ({
formData.append("version", selectedVersion || "");
formData.append("values", userValues);
formData.append("name", releaseName || "");
const data = await apiService.fetchWithDefaults(
return await apiService.fetchWithDefaults(
`/api/helm/releases/${namespace ? namespace : "default"}`,
{
method: "post",
body: formData,
}
);
return data;
},
onSuccess: async (response) => {
onSuccess: async (response: { namespace: string; name: string }) => {
onClose();
navigate(`/${response.namespace}/${response.name}/installed/revision/1`);
},
@@ -169,11 +173,11 @@ export const InstallRepoChartModal = ({
id: "1",
callback: setReleaseVersionMutation.mutate,
variant: ModalButtonStyle.info,
isLoading: setReleaseVersionMutation.isLoading,
isLoading: setReleaseVersionMutation.isPending,
disabled:
loadingChartValues ||
isLoadingDiff ||
setReleaseVersionMutation.isLoading,
setReleaseVersionMutation.isPending,
},
]}
>
@@ -205,10 +209,10 @@ export const InstallRepoChartModal = ({
diff={diffData as string}
isLoading={isLoadingDiff}
error={
(selectedVerDataError as string) ||
(diffError as string) ||
(selectedVerDataError as unknown as string) || // TODO fix it
(diffError as unknown as string) ||
installError ||
(versionsError as string)
(versionsError as unknown as string)
}
/>
</Modal>

View File

@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import Select, { components } from "react-select";
import { FC, useMemo, useState } from "react";
import Select, { components, GroupBase, SingleValueProps } from "react-select";
import { BsCheck2 } from "react-icons/bs";
import { NonEmptyArray } from "../../../data/types";
@@ -10,7 +10,19 @@ interface Version {
urls: string[];
}
export const VersionToInstall: React.FC<{
type VersionOptionType = {
value: Omit<Version, "isChartVersion">;
label: string;
check: boolean;
};
type SpecificSingleValueProps = SingleValueProps<
VersionOptionType,
false, // IsMulti
GroupBase<VersionOptionType>
>;
export const VersionToInstall: FC<{
versions: NonEmptyArray<Version>;
initialVersion?: {
repository?: string;
@@ -78,14 +90,19 @@ export const VersionToInstall: React.FC<{
}}
value={selectedOption ?? initOpt}
components={{
SingleValue: ({ children, ...props }) => (
<components.SingleValue {...props}>
<span className="text-green-700 font-bold">{children}</span>
{props.data.check && showCurrentVersion && (
<BsCheck2 className="inline-block ml-2 text-green-700 font-bold" />
)}
</components.SingleValue>
),
SingleValue: ({ children, ...props }) => {
const OriginalSingleValue =
components.SingleValue as FC<SpecificSingleValueProps>;
return (
<OriginalSingleValue {...props}>
<span className="text-green-700 font-bold">{children}</span>
{props.data.check && showCurrentVersion && (
<BsCheck2 className="inline-block ml-2 text-green-700 font-bold" />
)}
</OriginalSingleValue>
);
},
Option: ({ children, innerProps, data }) => (
<div
className={

View File

@@ -51,7 +51,7 @@ const customModalActions: ModalAction[] = [
id: "1",
text: "custom button 1",
className:
"text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800",
"text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-hidden focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800",
callback: () => {
action("clickCustomButton")("confirmModal: clicked custom button 1");
},

View File

@@ -41,23 +41,23 @@ const Modal = ({
const colorVariants = new Map<ModalButtonStyle, string>([
[
ModalButtonStyle.default,
"text-base font-semibold text-gray-500 bg-white hover:bg-gray-100 disabled:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 font-medium px-5 py-1 hover:text-gray-900 focus:z-10 ",
"text-base font-semibold text-gray-500 bg-white hover:bg-gray-100 disabled:bg-gray-200 focus:ring-4 focus:outline-hidden focus:ring-gray-200 rounded-lg border border-gray-200 font-medium px-5 py-1 hover:text-gray-900 focus:z-10 ",
],
[
ModalButtonStyle.info,
"font-semibold text-white bg-blue-700 hover:bg-blue-800 disabled:bg-blue-700/80 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
"font-semibold text-white bg-blue-700 hover:bg-blue-800 disabled:bg-blue-700/80 focus:ring-4 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
],
[
ModalButtonStyle.success,
"font-semibold text-white bg-green-700 hover:bg-green-800 disabled:bg-green-700/80 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
"font-semibold text-white bg-green-700 hover:bg-green-800 disabled:bg-green-700/80 focus:ring-4 focus:outline-hidden focus:ring-green-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
],
[
ModalButtonStyle.error,
"font-semibold text-white bg-red-700 hover:bg-red-800 disabled:bg-red-700/80 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
"font-semibold text-white bg-red-700 hover:bg-red-800 disabled:bg-red-700/80 focus:ring-4 focus:outline-hidden focus:ring-red-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
],
[
ModalButtonStyle.disabled,
"font-semibold text-gray-500 bg-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-base font-medium px-3 py-1.5 hover:text-gray-900 focus:z-10 ",
"font-semibold text-gray-500 bg-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-hidden focus:ring-gray-200 rounded-lg border border-gray-200 text-base font-medium px-3 py-1.5 hover:text-gray-900 focus:z-10 ",
],
]);
@@ -78,14 +78,14 @@ const Modal = ({
return createPortal(
<>
{isOpen && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity ">
<div className="fixed inset-0 bg-gray-500 bg-black/75 transition-opacity ">
<div className="flex justify-center">
<div
style={{
maxHeight: "95vh",
overflow: "hidden",
}}
className={`relative rounded-lg shadow m-7 w-2/5 max-w-[1300px] ${
className={`relative rounded-lg shadow-sm m-7 w-2/5 max-w-[1300px] ${
!containerClassNames ||
(containerClassNames && !containerClassNames.includes("bg-"))
? "bg-white"
@@ -98,7 +98,7 @@ const Modal = ({
{onClose ? (
<button
type="button"
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white cursor-pointer"
data-modal-hide="staticModal"
onClick={() => onClose()}
>
@@ -118,20 +118,20 @@ const Modal = ({
) : null}
</div>
)}
<div className="p-4 space-y-6 overflow-y-auto max-h-[calc(100vh_-_200px)]">
<div className="p-4 gap-6 overflow-y-auto max-h-[calc(100vh_-_200px)]">
{children}
</div>
{bottomContent ? (
<div className="p-5 text-sm">{bottomContent}</div>
) : (
<div className="flex justify-end p-6 space-x-2 border-t border-gray-200 rounded-b ">
<div className="flex justify-end p-6 gap-2 border-t border-gray-200 rounded-b ">
{actions?.map((action) => (
<button
key={action.id}
type="button"
className={
action.isLoading
? `flex items-center font-bold justify-around space-x-1 ${getClassName(
? `flex items-center font-bold justify-around gap-1 ${getClassName(
action
)}`
: `${getClassName(action)} `

View File

@@ -30,7 +30,7 @@ function RepositoriesList({
return (
<>
<div className="h-fit bg-white w-72 flex flex-col p-3 rounded custom-shadow text-dark gap-3">
<div className="h-fit bg-white w-72 flex flex-col p-3 rounded-sm custom-shadow text-dark gap-3">
<label className="font-bold">Repositories</label>
<div className="flex flex-col gap-1">
{repositories?.map((repository) => (
@@ -60,7 +60,7 @@ function RepositoriesList({
data-cy="install-repository-button"
type="button"
style={{ marginTop: "10px" }}
className="h-8 w-fit flex items-center gap-2 border rounded text-muted border-gray-300 px-3 py-1 text-sm font-semibold"
className="h-8 w-fit flex items-center gap-2 border rounded-sm text-muted border-gray-300 px-3 py-1 text-sm font-semibold cursor-pointer"
onClick={() => setShowAddRepositoryModal(true)}
>
+ Add Repository

View File

@@ -6,7 +6,7 @@ import apiService from "../../API/apiService";
import Spinner from "../Spinner";
import { useUpdateRepo } from "../../API/repositories";
import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { useAppContext } from "../../context/AppContext";
type RepositoryViewerProps = {
@@ -22,7 +22,6 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
const navigate = useNavigate();
const { data: charts, isLoading } = useQuery<Chart[]>({
//@ts-ignore
queryKey: ["charts", repository?.name || ""],
queryFn: apiService.getRepositoryCharts,
refetchOnWindowFocus: false,
@@ -76,7 +75,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
if (repository === undefined) {
return (
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
Looks like you don&apos;t have any repositories installed. You can add
one with the &quot;Add Repository&quot; button on the left side bar.
</div>
@@ -98,8 +97,8 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
update.mutate();
}}
>
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded">
{update.isLoading ? <Spinner size={4} /> : <BsArrowRepeat />}
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded-sm">
{update.isPending ? <Spinner size={4} /> : <BsArrowRepeat />}
Update
</span>
</button>
@@ -108,7 +107,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
removeRepository();
}}
>
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded">
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded-sm">
{isRemoveLoading ? <Spinner size={4} /> : <BsTrash3 />}
Remove
</span>
@@ -119,7 +118,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
value={searchValue}
type="text"
placeholder="Filter..."
className="mt-2 h-8 p-2 text-sm w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow rounded"
className="mt-2 h-8 p-2 text-sm w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow rounded-sm"
/>
</div>
</div>
@@ -142,7 +141,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
)}
{showNoChartsAlert && (
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
Looks like you don&apos;t have any repositories installed. You can add
one with the &quot;Add Repository&quot; button on the left side bar.
</div>

View File

@@ -11,7 +11,7 @@ import {
} from "react-icons/bs";
import { Release, ReleaseRevision } from "../../data/types";
import StatusLabel, { DeploymentStatus } from "../common/StatusLabel";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { useNavigate, useParams, useSearchParams } from "react-router";
import {
useGetReleaseInfoByType,
useGetLatestVersion,
@@ -91,14 +91,14 @@ export default function RevisionDetails({
refetch: refetchLatestVersion,
isLoading: isLoadingLatestVersion,
isRefetching: isRefetchingLatestVersion,
} = useGetLatestVersion(release.chart_name, { cacheTime: 0 });
} = useGetLatestVersion(release.chart_name);
const [showTestsResults, setShowTestResults] = useState(false);
const { setShowErrorModal } = useAlertError();
const {
mutate: runTests,
isLoading: isRunningTests,
isPending: isRunningTests,
data: testResults,
} = useTestRelease({
onError: (error) => {
@@ -306,7 +306,7 @@ export default function RevisionDetails({
function RevisionTag({ caption, text }: RevisionTagProps) {
return (
<span className="bg-revision p-1 rounded px-2 text-sm">
<span className="bg-revision p-1 rounded-sm px-2 text-sm">
<span>{caption}:</span>
<span className="font-bold"> {text}</span>
</span>
@@ -326,7 +326,7 @@ const Rollback = ({
const [showRollbackDiff, setShowRollbackDiff] = useState(false);
const revisionInt = parseInt(revision || "", 10);
const { mutate: rollbackRelease, isLoading: isRollingBackRelease } =
const { mutate: rollbackRelease, isPending: isRollingBackRelease } =
useRollbackRelease({
onSuccess: () => {
navigate(
@@ -421,7 +421,7 @@ const Rollback = ({
}, [data, isLoading, fetchedDataSuccessfully]);
return (
<div className="flex flex-col space-y-4">
<div className="flex flex-col gap-4">
{isLoading ? (
<div className="flex gap-2 text-sm">
<Spinner />
@@ -454,9 +454,7 @@ const Rollback = ({
const Uninstall = () => {
const [isOpen, setIsOpen] = useState(false);
const { namespace = "", chart = "" } = useParams();
const { data: resources } = useGetResources(namespace, chart, {
enabled: isOpen,
});
const { data: resources } = useGetResources(namespace, chart, isOpen);
const uninstallMutation = useMutation({
mutationKey: ["uninstall", namespace, chart],
@@ -497,7 +495,7 @@ const Uninstall = () => {
id: "1",
callback: uninstallMutation.mutate,
variant: ModalButtonStyle.info,
isLoading: uninstallMutation.isLoading,
isLoading: uninstallMutation.isPending,
},
]}
containerClassNames="w-[800px]"

View File

@@ -1,7 +1,7 @@
import { ChangeEvent, useMemo, useState, useRef, useEffect } from "react";
import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js";
import { useGetReleaseInfoByType } from "../../API/releases";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import useCustomSearchParams from "../../hooks/useCustomSearchParams";
import parse from "html-react-parser";
@@ -134,7 +134,7 @@ function RevisionDiff({
return (
<div>
<div className="flex mb-3 p-2 border border-revision flex-row items-center justify-between w-full bg-white rounded">
<div className="flex mb-3 p-2 border border-gray-200 border-revision flex-row items-center justify-between w-full bg-white rounded-sm">
<div className="flex items-center">
<input
checked={viewMode === "view"}
@@ -186,7 +186,7 @@ function RevisionDiff({
<div>
Diff with specific revision:
<input
className="border ml-2 border-gray-500 w-10 p-1 rounded-sm"
className="border ml-2 border-gray-500 w-10 p-1 rounded-xs"
type="text"
value={specificVersion}
onChange={(e) => setSpecificVersion(Number(e.target.value))}
@@ -201,7 +201,7 @@ function RevisionDiff({
type="checkbox"
onChange={handleUserDefinedCheckbox}
checked={!!userDefinedValue}
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
/>
<label
htmlFor="user-define-only-checkbox"
@@ -215,7 +215,9 @@ function RevisionDiff({
{isLoading ? <Spinner /> : ""}
{viewMode === VIEW_MODE_VIEW_ONLY && content ? (
<div className="bg-white overflow-x-auto w-full p-3 relative">
<pre className="bg-white rounded font-sf-mono">{parse(content)}</pre>
<pre className="bg-white rounded-sm font-sf-mono">
{parse(content)}
</pre>
</div>
) : (
""

View File

@@ -1,5 +1,5 @@
import { useMemo, useState } from "react";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import hljs from "highlight.js";
import { RiExternalLinkLine } from "react-icons/ri";
@@ -32,19 +32,19 @@ export default function RevisionResource({ isLatest }: Props) {
cellPadding={6}
className="border-spacing-y-2 font-semibold border-separate w-full text-xs "
>
<thead className="bg-zinc-200 font-bold h-8 rounded">
<thead className="bg-zinc-200 font-bold h-8 rounded-sm">
<tr>
<td className="pl-6 rounded">RESOURCE TYPE</td>
<td className="pl-6 rounded-sm">RESOURCE TYPE</td>
<td>NAME</td>
<td>STATUS</td>
<td>STATUS MESSAGE</td>
<td className="rounded"></td>
<td className="rounded-sm"></td>
</tr>
</thead>
{isLoading ? (
<Spinner />
) : (
<tbody className="bg-white mt-4 h-8 rounded w-full">
<tbody className="bg-white mt-4 h-8 rounded-sm w-full">
{resources?.length ? (
resources
.sort(function (a, b) {
@@ -65,7 +65,7 @@ export default function RevisionResource({ isLatest }: Props) {
))
) : (
<tr>
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
Looks like you don&apos;t have any resources.{" "}
<RiExternalLinkLine className="ml-2 text-lg" />
</div>
@@ -100,11 +100,11 @@ const ResourceRow = ({
return (
<>
<tr className="min-w-[100%] min-h[70px] text-sm py-2">
<td className="pl-6 rounded text-sm font-normal w-48">{kind}</td>
<td className="pl-6 rounded-sm text-sm font-normal w-48">{kind}</td>
<td className="font-bold text-sm w-56">{name}</td>
<td>{reason ? <Badge type={badgeType}>{reason}</Badge> : null}</td>
<td className="rounded text-gray-100">
<div className="flex flex-col space-y-1 flex-start">
<td className="rounded-sm text-gray-100">
<div className="flex flex-col gap-1 flex-start">
{message && (
<div className="text-gray-500 font-thin">{message}</div>
)}
@@ -113,7 +113,7 @@ const ResourceRow = ({
)}
</div>
</td>
<td className="rounded">
<td className="rounded-sm">
{isLatest && reason !== "NotFound" ? (
<div className="flex justify-end items-center mr-36">
<Button className="px-1 text-xs" onClick={toggleDrawer}>
@@ -183,7 +183,7 @@ const DescribeResource = ({
<div className="flex items-center gap-4 pr-4">
<a
href="https://www.komodor.com/helm-dash/?utm_campaign=Helm%20Dashboard%20%7C%20CTA&amp;utm_source=helm-dash&amp;utm_medium=cta&amp;utm_content=helm-dash"
className="bg-primary text-white p-1.5 text-sm flex items-center rounded"
className="bg-primary text-white p-1.5 text-sm flex items-center rounded-sm"
target="_blank"
rel="noreferrer"
>
@@ -207,7 +207,7 @@ const DescribeResource = ({
) : (
<div className="h-full overflow-y-auto ">
<pre
className="bg-white rounded p-4 font-medium text-base font-sf-mono"
className="bg-white rounded-sm p-4 font-medium text-base font-sf-mono"
style={{ overflow: "unset" }}
dangerouslySetInnerHTML={{
__html: yamlFormattedData,

View File

@@ -1,5 +1,5 @@
import { BsArrowDownRight, BsArrowUpRight } from "react-icons/bs";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import { compare } from "compare-versions";
import { ReleaseRevision } from "../../data/types";
@@ -40,7 +40,7 @@ export default function RevisionsList({
}
onClick={() => changeRelease(release.revision)}
key={release.revision}
className={`flex flex-col border rounded-md mx-5 p-2 gap-4 cursor-pointer ${
className={`flex flex-col border border-gray-200 rounded-md mx-5 p-2 gap-4 cursor-pointer ${
release.revision === selectedRevision
? "border-revision-dark bg-white"
: "border-revision-light bg-body-background"

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo } from "react";
import { useSearchParams } from "react-router-dom";
import { useSearchParams } from "react-router";
const useCustomSearchParams = () => {
const [search, setSearch] = useSearchParams();

View File

@@ -3,7 +3,7 @@ import {
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
} from "react-router";
import { useAppContext } from "../context/AppContext";
const useNavigateWithSearchParams = () => {
@@ -19,7 +19,7 @@ const useNavigateWithSearchParams = () => {
let prefixedUrl = url;
if (!clusterMode) {
prefixedUrl = `/${encodeURIComponent(context)}${url}`;
prefixedUrl = `/${encodeURIComponent(context ?? "")}${url}`;
}
navigate(`${prefixedUrl}${search}`, ...restArgs);
};

View File

@@ -1,74 +1,117 @@
/* 1. Google Fonts */
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Inter:wght@400;500&family=Poppins:wght@500;600&family=Roboto+Slab:wght@400;700");
/* Google Fonts */
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Inter:wght@400;500&family=Poppins:wght@500;600&family=Roboto+Slab:wght@400;700")
layer(base);
/* 2. Tailwind directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Tailwind directives */
@import "tailwindcss";
/* 3. Theme variables (CSS-first approach) */
:root {
/* Fonts */
--font-roboto: "Roboto", serif;
--font-roboto-slab: "Roboto Slab", serif;
--font-inter: "Inter", serif;
--font-poppins: "Poppins", sans-serif;
--font-sf-mono:
SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
@plugin 'flowbite/plugin';
@source "../node_modules/flowbite";
@plugin "flowbite-react/plugin/tailwindcss";
@source "../.flowbite-react/class-list.json";
/* Font weights */
--font-weight-thin: 100;
--font-weight-hairline: 100;
--font-weight-extralight: 200;
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-semibold: 500;
--font-weight-extrabold: 600;
--font-weight-bold: 700;
/* Colors */
--color-body: #3d4048;
--color-white: #ffffff;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-cluster-list: #3d4048;
--color-dark: #3d4048;
--color-tab-color: #3b3d45;
--color-primary: #1347ff;
--color-secondary: #eceff2;
--color-muted: #707583;
--color-error-border: #dc3545;
--color-error-background: #f9d7da;
--color-error-color: #842029;
--color-failed: #fc1683;
--color-deployed: #1fa470;
--color-superseded: #9195a1;
--color-pending: #5ab0ff;
--color-danger: #ff0072;
--color-text-danger: #fc1683;
--color-text-warning: #ffc107;
--color-text-success: #a4f8d7;
--color-upgradable: #0d6efd;
--color-warning: #ffa800;
--color-success: #00c2ab;
--color-border-deployed: #1be99a;
--color-revision-light: #dcdddf;
--color-revision-dark: #007bff;
--color-installed-charts-filter: #dad8ce;
--color-dropdown: #e9ecef;
--color-chart-values: #eceff2;
--color-add-repo: #0b5ed7;
--color-link-color: #0d6efd;
--color-body-background: #f4f7fa;
--color-repository: #d6effe;
--color-revision: #d6effe;
--color-header-install: #ebefff;
--color-upgrade-color: #fc1683;
/* Components layer */
@utility card {
background-color: var(--color-white);
border-radius: var(--radius-lg);
border: 1px solid var(--color-gray-200);
box-shadow: var(--shadow-xl);
padding: --spacing(12) --spacing(6) --spacing(6);
}
@utility card-hover {
background-color: var(--color-gray-100);
border-radius: var(--radius-lg);
padding: --spacing(6);
box-shadow: var(--shadow-xl);
}
@utility card-active {
background-color: var(--color-gray-200);
border-radius: var(--radius-lg);
padding: --spacing(6);
box-shadow: var(--shadow-xl);
}
@utility card-disabled {
background-color: var(--color-gray-300);
border-radius: var(--radius-lg);
padding: --spacing(6);
box-shadow: var(--shadow-xl);
}
@utility custom-shadow {
position: relative;
box-sizing: border-box;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
}
@utility error-dialog {
border: var(--color-error-border);
background-color: var(--color-error-background);
color: var(--color-error-color);
}
/* 4. Base layer */
@layer utilities {
/* Theme variables (CSS-first approach) */
:root {
/* Fonts */
--font-roboto: "Roboto", serif;
--font-roboto-slab: "Roboto Slab", serif;
--font-inter: "Inter", serif;
--font-poppins: "Poppins", sans-serif;
--font-sf-mono:
SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace;
/* Font weights */
--font-weight-thin: 100;
--font-weight-hairline: 100;
--font-weight-extralight: 200;
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-semibold: 500;
--font-weight-extrabold: 600;
--font-weight-bold: 700;
/* Colors */
--color-body: #3d4048;
--color-white: #ffffff;
--color-gray-100: #f3f4f6;
--color-gray-200: #e5e7eb;
--color-gray-300: #d1d5db;
--color-cluster-list: #3d4048;
--color-dark: #3d4048;
--color-tab-color: #3b3d45;
--color-primary: #1347ff;
--color-secondary: #eceff2;
--color-muted: #707583;
--color-error-border: #dc3545;
--color-error-background: #f9d7da;
--color-error-color: #842029;
--color-failed: #fc1683;
--color-deployed: #1fa470;
--color-superseded: #9195a1;
--color-pending: #5ab0ff;
--color-danger: #ff0072;
--color-text-danger: #fc1683;
--color-text-warning: #ffc107;
--color-text-success: #a4f8d7;
--color-upgradable: #0d6efd;
--color-warning: #ffa800;
--color-success: #00c2ab;
--color-border-deployed: #1be99a;
--color-revision-light: #dcdddf;
--color-revision-dark: #007bff;
--color-installed-charts-filter: #dad8ce;
--color-dropdown: #e9ecef;
--color-chart-values: #eceff2;
--color-add-repo: #0b5ed7;
--color-link-color: #0d6efd;
--color-body-background: #f4f7fa;
--color-repository: #d6effe;
--color-revision: #d6effe;
--color-header-install: #ebefff;
--color-upgrade-color: #fc1683;
}
}
/* Base layer */
@layer base {
body {
color: var(--color-body);
@@ -82,58 +125,14 @@
}
}
/* 5. Components layer */
@layer components {
.card {
background-color: var(--color-white);
border-radius: theme("borderRadius.lg");
border: 1px solid var(--color-gray-200);
box-shadow: theme("boxShadow.xl");
padding: theme("spacing.12") theme("spacing.6") theme("spacing.6");
}
.card-hover {
background-color: var(--color-gray-100);
border-radius: theme("borderRadius.lg");
padding: theme("spacing.6");
box-shadow: theme("boxShadow.xl");
}
.card-active {
background-color: var(--color-gray-200);
border-radius: theme("borderRadius.lg");
padding: theme("spacing.6");
box-shadow: theme("boxShadow.xl");
}
.card-disabled {
background-color: var(--color-gray-300);
border-radius: theme("borderRadius.lg");
padding: theme("spacing.6");
box-shadow: theme("boxShadow.xl");
}
.custom-shadow {
position: relative;
box-sizing: border-box;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
}
.error-dialog {
border: var(--color-error-border);
background-color: var(--color-error-background);
color: var(--color-error-color);
}
}
/* 6. Code & pre fonts */
/* Code & pre fonts */
pre,
code {
font-family: var(--font-sf-mono);
font-size: 12.5px;
}
/* 7. Portal positioning */
/* Portal positioning */
#portal {
top: 0;
width: 40%;
@@ -141,13 +140,13 @@ code {
position: absolute;
}
/* 8. Required fields */
/* Required fields */
.require:after {
content: " *";
color: red;
}
/* 9. Input focus shadow */
/* Input focus shadow */
.input-box-shadow:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}

View File

@@ -1,4 +1,4 @@
import { useLocation } from "react-router-dom";
import { useLocation } from "react-router";
import LogoHeader from "../assets/logo-header.svg";
import DropDown from "../components/common/DropDown";
import WatcherIcon from "../assets/k8s-watcher.svg";
@@ -13,15 +13,22 @@ import { useGetApplicationStatus } from "../API/other";
import LinkWithSearchParams from "../components/LinkWithSearchParams";
import apiService from "../API/apiService";
import { useAppContext } from "../context/AppContext";
import { useEffect, useEffectEvent } from "react";
export default function Header() {
const { clusterMode, setClusterMode } = useAppContext();
const { data: statusData } = useGetApplicationStatus({
onSuccess: (data) => {
setClusterMode(data.ClusterMode);
},
const { data: statusData, isSuccess } = useGetApplicationStatus();
const onSuccess = useEffectEvent(() => {
setClusterMode(!!statusData?.ClusterMode);
});
useEffect(() => {
if (isSuccess && statusData) {
onSuccess();
}
}, [isSuccess, statusData]);
const location = useLocation();
const openProjectPage = () => {
@@ -46,7 +53,7 @@ export default function Header() {
const getBtnStyle = (identifier: string) =>
`text-md py-2.5 px-5 ${
location.pathname.includes(`/${identifier}`)
? " text-primary rounded-sm bg-header-install"
? " text-primary rounded-xs bg-header-install"
: ""
}`;
@@ -129,7 +136,7 @@ export default function Header() {
</div>
</div>
<div className="h-16 flex items-center text-sm ">
<div className="flex p-1 gap-2 border bottom-gray-200 rounded min-w-max">
<div className="flex p-1 gap-2 border bottom-gray-200 rounded-sm min-w-max">
<img src={WatcherIcon} width={40} height={40} />
<div className="flex flex-col">
<a

View File

@@ -1,4 +1,5 @@
import "../App.css";
import { JSX } from "react";
function Sidebar(): JSX.Element {
return (

View File

@@ -1,10 +1,12 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { ThemeInit } from "../.flowbite-react/init";
import App from "./App";
import "./index.css";
createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<ThemeInit />
<App />
</StrictMode>
);

View File

@@ -2,10 +2,10 @@ import InstalledPackagesHeader from "../components/InstalledPackages/InstalledPa
import InstalledPackagesList from "../components/InstalledPackages/InstalledPackagesList";
import ClustersList from "../components/ClustersList";
import { useGetInstalledReleases } from "../API/releases";
import { useMemo, useState } from "react";
import { useEffect, useEffectEvent, useMemo, useState } from "react";
import Spinner from "../components/Spinner";
import useAlertError from "../hooks/useAlertError";
import { useParams, useNavigate } from "react-router-dom";
import { useParams, useNavigate } from "react-router";
import useCustomSearchParams from "../hooks/useCustomSearchParams";
import { Release } from "../data/types";
@@ -27,18 +27,21 @@ function Installed() {
const [filterKey, setFilterKey] = useState<string>("");
const alertError = useAlertError();
const { data, isLoading, isRefetching } = useGetInstalledReleases(
context ?? "",
{
retry: false,
onError: (e) => {
alertError.setShowErrorModal({
title: "Failed to get list of charts",
msg: (e as Error).message,
});
},
const { data, isLoading, isRefetching, isError, error } =
useGetInstalledReleases(context ?? "");
const onError = useEffectEvent(() => {
alertError.setShowErrorModal({
title: "Failed to get list of charts",
msg: error?.message ?? "",
});
});
useEffect(() => {
if (isError) {
onError();
}
);
}, [isError]);
const filteredReleases = useMemo(() => {
return (

View File

@@ -1,11 +1,10 @@
import { useMemo, useEffect } from "react";
import { useMemo, useEffect, useEffectEvent } from "react";
import RepositoriesList from "../components/repository/RepositoriesList";
import RepositoryViewer from "../components/repository/RepositoryViewer";
import { Repository } from "../data/types";
import { useGetRepositories } from "../API/repositories";
import { HelmRepositories } from "../API/interfaces";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import { useAppContext } from "../context/AppContext";
import useNavigateWithSearchParams from "../hooks/useNavigateWithSearchParams";
@@ -34,16 +33,25 @@ function RepositoryPage() {
}
}, [selectedRepo, repoFromParams, context, navigate]);
const { data: repositories = [] } = useGetRepositories({
onSuccess: (data: HelmRepositories) => {
const sortedData = data?.sort((a, b) => a.name.localeCompare(b.name));
const { data: repositories = [], isSuccess } = useGetRepositories();
if (sortedData && sortedData.length > 0 && !repoFromParams) {
handleRepositoryChanged(sortedData[0]);
}
},
const onSuccess = useEffectEvent(() => {
// TODO should we passe sorted to RepositoriesList as in ClustersList?
const sortedData = [...repositories]?.sort((a, b) =>
a.name.localeCompare(b.name)
);
if (sortedData && sortedData.length > 0 && !repoFromParams) {
handleRepositoryChanged(sortedData[0]);
}
});
useEffect(() => {
if (repositories.length && isSuccess) {
onSuccess();
}
}, [repositories, isSuccess]);
const selectedRepository = useMemo(() => {
if (repoFromParams) {
return repositories?.find((repo) => repo.name === repoFromParams);

View File

@@ -1,8 +1,8 @@
import { useMemo } from "react";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import RevisionDetails from "../components/revision/RevisionDetails";
import RevisionsList from "../components/revision/RevisionsList";
import { ReleaseRevision } from "../data/types";
import { Release, ReleaseRevision } from "../data/types";
import { useQuery } from "@tanstack/react-query";
import apiService from "../API/apiService";
import Spinner from "../components/Spinner";
@@ -15,18 +15,15 @@ function Revision() {
const selectedRevision = revision ? parseInt(revision, 10) : 0;
const { data: releaseRevisions, isLoading: isLoadingHistory } = useQuery<
ReleaseRevision[]
>({
//eslint-ignore
//@ts-ignore
queryKey: ["releasesHistory", restParams],
queryFn: apiService.getReleasesHistory,
});
const { data: releaseRevisions = [], isLoading: isLoadingHistory } = useQuery(
{
queryKey: ["releasesHistory", restParams],
queryFn: apiService.getReleasesHistory,
}
);
const latestRevision = useMemo(
() =>
Array.isArray(releaseRevisions) &&
releaseRevisions.reduce((max, revisionData) => {
return Math.max(max, revisionData.revision);
}, Number.MIN_SAFE_INTEGER),
@@ -40,7 +37,7 @@ function Revision() {
const selectedRelease = useMemo(() => {
if (selectedRevision && releaseRevisions) {
return (releaseRevisions as ReleaseRevision[]).find(
return releaseRevisions.find(
(r: ReleaseRevision) => r.revision === selectedRevision
);
}
@@ -70,14 +67,10 @@ function Revision() {
</div>
) : selectedRelease ? (
<RevisionDetails
//@ts-ignore
release={selectedRelease}
installedRevision={
//@ts-ignore
releaseRevisions?.[0] as ReleaseRevision
}
release={selectedRelease as Release} // TODO fix it
installedRevision={releaseRevisions?.[0]}
isLatest={selectedRelease.revision === latestRevision}
latestRevision={latestRevision.revision}
latestRevision={latestRevision}
/>
) : null}
</div>
@@ -88,12 +81,12 @@ function Revision() {
const RevisionSidebarSkeleton = () => {
return (
<>
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
</>
);
};

View File

@@ -1,9 +1,9 @@
import { DateTime, type DurationLikeObject } from "luxon";
import { DateTime, DateTimeMaybeValid, type DurationLikeObject } from "luxon";
import { ReleaseRevision } from "./data/types";
export function getAge(obj1: ReleaseRevision, obj2?: ReleaseRevision) {
const date = DateTime.fromISO(obj1.updated);
let dateNext = DateTime.now();
let dateNext: DateTimeMaybeValid = DateTime.now();
if (obj2) {
dateNext = DateTime.fromISO(obj2.updated);
}

View File

@@ -1,12 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
"./node_modules/flowbite/**/*.js",
"./node_modules/flowbite-react/**/*.{js,jsx,ts,tsx}",
],
plugins: [
require("flowbite/plugin")
],
};

View File

@@ -10,7 +10,7 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,

View File

@@ -2,7 +2,7 @@
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]

View File

@@ -1,6 +1,7 @@
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import { viteStaticCopy } from 'vite-plugin-static-copy';
import { viteStaticCopy } from "vite-plugin-static-copy";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
@@ -8,38 +9,36 @@ export default defineConfig(({ mode }) => {
return {
plugins: [
react(),
tailwindcss(),
viteStaticCopy({
targets: [
{
src: 'public/analytics.js',
src: "public/analytics.js",
dest: "assets/",
},
{
src: 'public/openapi.json',
src: "public/openapi.json",
dest: "assets/",
},
{
src: 'public/logo.svg',
src: "public/logo.svg",
dest: "assets/",
},
]
})
],
}),
],
build: {
assetsDir: "./assets/",
outDir: "../pkg/frontend/dist",
emptyOutDir: true,
rollupOptions: {
output: {
manualChunks: {
react: ['react', 'react-dom', 'react-router-dom'],
vendors: ['luxon','highlight.js','diff2html','swagger-ui-react']
}
}
}
},
css: {
postcss: './postcss.config.cjs'
rollupOptions: {
output: {
manualChunks: {
react: ["react", "react-dom", "react-router"],
vendors: ["luxon", "highlight.js", "diff2html", "swagger-ui-react"],
},
},
},
},
server: {
proxy: {

File diff suppressed because it is too large Load Diff