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: # Ignore artifacts:
build build
coverage 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 = { const config: StorybookConfig = {
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx|mdx)"], stories: ["../src/**/*.stories.@(js|jsx|ts|tsx|mdx)"],
addons: [ addons: ["@storybook/addon-links", "@storybook/addon-docs"],
"@storybook/addon-links",
"@storybook/addon-docs"
],
core: {}, core: {},
framework: { framework: {
name: "@storybook/react-vite", name: "@storybook/react-vite",
options: {}, options: {},
}, },
features: {
mdx2Csf: true,
}
}; };
export default config; export default config;

View File

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

View File

@@ -2,6 +2,7 @@
Welcome to the frontend of the helm dashboard. Welcome to the frontend of the helm dashboard.
We care most about keeping the project: We care most about keeping the project:
1. Maintainable 1. Maintainable
2. Extendable 2. Extendable
3. Contributor friendly 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). 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. run the backend server. This is also explained in the above link.
2. go to `frontend` in your local project. 3. go to `frontend` in your local project.
3. in order to install dependencies and start the development server 4. in order to install dependencies and start the development server
- `npm i` - `npm i`
- `npm run dev` - `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 # Component library

View File

@@ -39,6 +39,8 @@ module.exports = defineConfig([{
extends: compat.extends( extends: compat.extends(
"enpitech", "enpitech",
"eslint:recommended",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended", "plugin:react-hooks/recommended",
// "plugin:@typescript-eslint/recommended-requiring-type-checking", TODO enable and fix the types // "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"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import Header from "./layout/Header"; 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 "./index.css";
import Installed from "./pages/Installed"; import Installed from "./pages/Installed";
import RepositoryPage from "./pages/Repository"; import RepositoryPage from "./pages/Repository";

View File

@@ -17,6 +17,7 @@
* *
* *
*/ */
import { JSX, ReactNode } from "react";
export type BadgeCode = "success" | "warning" | "error" | "unknown"; export type BadgeCode = "success" | "warning" | "error" | "unknown";
@@ -29,7 +30,7 @@ export const BadgeCodes = Object.freeze({
export interface BadgeProps { export interface BadgeProps {
type: BadgeCode; type: BadgeCode;
children: React.ReactNode; children: ReactNode;
additionalClassNames?: string; additionalClassNames?: string;
} }
export default function Badge(props: BadgeProps): JSX.Element { export default function Badge(props: BadgeProps): JSX.Element {
@@ -41,7 +42,7 @@ export default function Badge(props: BadgeProps): JSX.Element {
}; };
const badgeBase = 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 = ( const badgeElem = (
<span <span

View File

@@ -5,7 +5,7 @@ describe("Button component tests", () => {
const buttonText = "buttonText"; const buttonText = "buttonText";
it("renders", () => { it("renders", () => {
mount(<Button onClick={() => {}}></Button>); mount(<Button onClick={() => {}} label=""></Button>);
cy.get("button").should("exist"); cy.get("button").should("exist");
}); });
@@ -17,14 +17,14 @@ describe("Button component tests", () => {
it("calls onClick when clicked", () => { it("calls onClick when clicked", () => {
const onClickStub = cy.stub().as("onClick"); const onClickStub = cy.stub().as("onClick");
mount(<Button onClick={onClickStub}></Button>); mount(<Button onClick={onClickStub} label={""}></Button>);
cy.get("button").click(); cy.get("button").click();
cy.get("@onClick").should("have.been.calledOnce"); cy.get("@onClick").should("have.been.calledOnce");
}); });
it("should be disabled", () => { it("should be disabled", () => {
mount(<Button onClick={() => {}} disabled></Button>); mount(<Button onClick={() => {}} disabled label={""}></Button>);
cy.get("button").should("be.disabled"); 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. // this is a type declaration for the action prop.
// it is a function that takes a string as an argument and returns void. // it is a function that takes a string as an argument and returns void.
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> { export interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
children: React.ReactNode; children: ReactNode;
disabled?: boolean; disabled?: boolean;
onClick: () => void; onClick: () => void;
className?: string; className?: string;
@@ -26,7 +27,7 @@ export default function Button(props: ButtonProps): JSX.Element {
<> <>
<button <button
onClick={props.onClick} 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} disabled={props.disabled}
> >
{props.children} {props.children}

View File

@@ -1,6 +1,6 @@
import { AppContextProvider } from "../context/AppContext"; import { AppContextProvider } from "../context/AppContext";
import ClustersList from "./ClustersList"; import ClustersList from "./ClustersList";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Release } from "../data/types"; 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 { Cluster, Release } from "../data/types";
import apiService from "../API/apiService"; import apiService from "../API/apiService";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -43,30 +43,39 @@ function ClustersList({
}: ClustersListProps) { }: ClustersListProps) {
const { upsertSearchParams, removeSearchParam } = useCustomSearchParams(); const { upsertSearchParams, removeSearchParam } = useCustomSearchParams();
const { clusterMode } = useAppContext(); const { clusterMode } = useAppContext();
const [sortedClusters, setSortedClusters] = useState<Cluster[]>([]);
const { data: clusters } = useQuery<Cluster[]>({ const { data: clusters, isSuccess } = useQuery<Cluster[]>({
queryKey: ["clusters", selectedCluster], queryKey: ["clusters", selectedCluster],
queryFn: apiService.getClusters, queryFn: apiService.getClusters,
onSuccess(data) { });
const sortedData = data?.sort((a, b) =>
const onSuccess = useEffectEvent((clusters: Cluster[]) => {
const sortedData = [...clusters].sort((a, b) =>
getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name)) getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name))
); );
setSortedClusters(sortedData);
if (sortedData && sortedData.length > 0 && !selectedCluster) { if (sortedData && sortedData.length > 0 && !selectedCluster) {
onClusterChange(sortedData[0].Name); onClusterChange(sortedData[0].Name);
} }
if (selectedCluster) { if (selectedCluster) {
const cluster = data.find( const cluster = clusters.find(
(cluster) => getCleanClusterName(cluster.Name) === selectedCluster (cluster) => getCleanClusterName(cluster.Name) === selectedCluster
); );
if (!filteredNamespaces && cluster?.Namespace) { if (!filteredNamespaces && cluster?.Namespace) {
upsertSearchParams("filteredNamespace", cluster.Namespace); upsertSearchParams("filteredNamespace", cluster.Namespace);
} }
} }
},
}); });
useEffect(() => {
if (clusters && isSuccess) {
onSuccess(clusters);
}
}, [clusters, isSuccess]);
const namespaces = useMemo(() => { const namespaces = useMemo(() => {
const mapNamespaces = new Map<string, number>(); const mapNamespaces = new Map<string, number>();
@@ -98,17 +107,11 @@ function ClustersList({
}; };
return ( 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 ? ( {!clusterMode ? (
<> <>
<label className="font-bold">Clusters</label> <label className="font-bold">Clusters</label>
{clusters {sortedClusters?.map((cluster) => {
?.sort((a, b) =>
getCleanClusterName(a.Name).localeCompare(
getCleanClusterName(b.Name)
)
)
?.map((cluster) => {
return ( return (
<span <span
key={cluster.Name} key={cluster.Name}

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ export default function InstalledPackagesHeader({
<div className="w-1/3"> <div className="w-1/3">
<input <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..." placeholder="Filter..."
type="text" type="text"
onChange={(ev) => setFilterKey(ev.target.value)} onChange={(ev) => setFilterKey(ev.target.value)}
@@ -41,7 +41,7 @@ export default function InstalledPackagesHeader({
</div> </div>
{showNoPackageAlert && ( {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. Looks like you don&apos;t have any charts installed.
&quot;Repository&quot; section may be a good place to start. &quot;Repository&quot; section may be a good place to start.
</div> </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"; import { useAppContext } from "../context/AppContext";
const LinkWithSearchParams = ({ const LinkWithSearchParams = ({
@@ -12,7 +12,7 @@ const LinkWithSearchParams = ({
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
const { search } = useLocation(); const { search } = useLocation();
const { context } = useParams(); const { context = "" } = useParams();
const { clusterMode } = useAppContext(); const { clusterMode } = useAppContext();
const params = new URLSearchParams(search); const params = new URLSearchParams(search);

View File

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

View File

@@ -22,7 +22,7 @@ function ShutDownButton() {
<button <button
onClick={handleClick} onClick={handleClick}
title="Shut down the Helm Dashboard application" 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" /> <BsPower className="w-6" />
</button> </button>

View File

@@ -25,7 +25,7 @@ export default function Tabs({ tabs, selectedTab }: TabsProps) {
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <button
key={tab.label} 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 && selectedTab.value === tab.value &&
"border-b-[3px] border-tab-color" "border-b-[3px] border-tab-color"

View File

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

View File

@@ -12,6 +12,7 @@
* @return JSX.Element * @return JSX.Element
* *
*/ */
import { JSX } from "react";
export interface TextInputProps { export interface TextInputProps {
label: string; 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({ export default function Tooltip({
id, id,
@@ -11,11 +11,16 @@ export default function Tooltip({
}) { }) {
return ( return (
<> <>
{cloneElement(element, { "data-tooltip-target": id })} {cloneElement(
element as ReactElement<HTMLAttributes<HTMLElement>>,
{
"data-tooltip-target": id,
} as HTMLAttributes<HTMLElement>
)}
<div <div
id={id} id={id}
role="tooltip" 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} {title}
<div className="tooltip-arrow" data-popper-arrow></div> <div className="tooltip-arrow" data-popper-arrow></div>
@@ -24,14 +29,14 @@ export default function Tooltip({
<button <button
data-tooltip-target="tooltip-default" data-tooltip-target="tooltip-default"
type="button" 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 Default tooltip
</button> </button>
<div <div
id="tooltip-default" id="tooltip-default"
role="tooltip" 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 Tooltip content
<div className="tooltip-arrow" data-popper-arrow></div> <div className="tooltip-arrow" data-popper-arrow></div>

View File

@@ -8,7 +8,7 @@ export const Troubleshoot = () => {
target="_blank" target="_blank"
rel="noreferrer" 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 Troubleshoot in Komodor
<RiExternalLinkLine className="ml-2 text-lg" /> <RiExternalLinkLine className="ml-2 text-lg" />
</button> </button>

View File

@@ -21,6 +21,7 @@ interface ButtonProps {
* Optional click handler * Optional click handler
*/ */
onClick?: () => void; 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"; import ArrowDownIcon from "../../assets/arrow-down-icon.svg";
export type DropDownItem = { export type DropDownItem = {
@@ -62,7 +62,7 @@ function DropDown({ items }: DropDownProps) {
Y: e.pageY, Y: e.pageY,
})); }));
}} }}
className="flex items-center justify-between" className="flex items-center justify-between cursor-pointer"
> >
Help Help
<img src={ArrowDownIcon} className="ml-2 w-[10px] h-[10px]" /> <img src={ArrowDownIcon} className="ml-2 w-[10px] h-[10px]" />
@@ -71,10 +71,10 @@ function DropDown({ items }: DropDownProps) {
{popupState.isOpen && ( {popupState.isOpen && (
<div <div
ref={modalRef} 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) => ( {items.map((item) => (
<> <Fragment key={item.id}>
{item.isSeparator ? ( {item.isSeparator ? (
<div className="bg-gray-300 h-[1px]" /> <div className="bg-gray-300 h-[1px]" />
) : ( ) : (
@@ -96,7 +96,7 @@ function DropDown({ items }: DropDownProps) {
<span>{item.text}</span> <span>{item.text}</span>
</div> </div>
)} )}
</> </Fragment>
))} ))}
</div> </div>
)} )}

View File

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

View File

@@ -5,7 +5,7 @@ import useAlertError from "../../hooks/useAlertError";
import useCustomSearchParams from "../../hooks/useCustomSearchParams"; import useCustomSearchParams from "../../hooks/useCustomSearchParams";
import { useAppContext } from "../../context/AppContext"; import { useAppContext } from "../../context/AppContext";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import apiService from "../../API/apiService"; import apiService from "../../API/apiService";
interface FormKeys { interface FormKeys {
@@ -80,10 +80,10 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
isOpen={isOpen} isOpen={isOpen}
onClose={onClose} onClose={onClose}
bottomContent={ 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 <button
data-cy="add-chart-repository-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} onClick={addRepository}
disabled={isLoading} disabled={isLoading}
> >
@@ -109,7 +109,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
data-cy="add-chart-name" data-cy="add-chart-name"
type="text" type="text"
placeholder="Komodorio" 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>
<label className="flex-1" htmlFor="url"> <label className="flex-1" htmlFor="url">
@@ -127,7 +127,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
data-cy="add-chart-url" data-cy="add-chart-url"
type="text" type="text"
placeholder="https://helm-charts.komodor.io" 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> </label>
</div> </div>
@@ -144,7 +144,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
required required
id="username" id="username"
type="text" 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>
<label className="flex-1" htmlFor="password"> <label className="flex-1" htmlFor="password">
@@ -159,7 +159,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
required required
id="password" id="password"
type="text" 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>
</div> </div>

View File

@@ -33,7 +33,7 @@ export default function ErrorModal({
); );
const bottomContent = ( 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"> <span className="text-sm text-muted fs-80 text-gray-500">
Hint: Komodor has the same HELM capabilities, with enterprise features Hint: Komodor has the same HELM capabilities, with enterprise features
and support.{" "} and support.{" "}

View File

@@ -17,7 +17,7 @@ export const ChartValues = ({
Chart Value Reference: Chart Value Reference:
</label> </label>
<pre <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={ dangerouslySetInnerHTML={
chartValues && !loading chartValues && !loading
? { ? {

View File

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

View File

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

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ const customModalActions: ModalAction[] = [
id: "1", id: "1",
text: "custom button 1", text: "custom button 1",
className: 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: () => { callback: () => {
action("clickCustomButton")("confirmModal: clicked custom button 1"); action("clickCustomButton")("confirmModal: clicked custom button 1");
}, },

View File

@@ -41,23 +41,23 @@ const Modal = ({
const colorVariants = new Map<ModalButtonStyle, string>([ const colorVariants = new Map<ModalButtonStyle, string>([
[ [
ModalButtonStyle.default, 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, 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, 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, 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, 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( return createPortal(
<> <>
{isOpen && ( {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 className="flex justify-center">
<div <div
style={{ style={{
maxHeight: "95vh", maxHeight: "95vh",
overflow: "hidden", 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 && !containerClassNames.includes("bg-")) (containerClassNames && !containerClassNames.includes("bg-"))
? "bg-white" ? "bg-white"
@@ -98,7 +98,7 @@ const Modal = ({
{onClose ? ( {onClose ? (
<button <button
type="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" data-modal-hide="staticModal"
onClick={() => onClose()} onClick={() => onClose()}
> >
@@ -118,20 +118,20 @@ const Modal = ({
) : null} ) : null}
</div> </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} {children}
</div> </div>
{bottomContent ? ( {bottomContent ? (
<div className="p-5 text-sm">{bottomContent}</div> <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) => ( {actions?.map((action) => (
<button <button
key={action.id} key={action.id}
type="button" type="button"
className={ className={
action.isLoading action.isLoading
? `flex items-center font-bold justify-around space-x-1 ${getClassName( ? `flex items-center font-bold justify-around gap-1 ${getClassName(
action action
)}` )}`
: `${getClassName(action)} ` : `${getClassName(action)} `

View File

@@ -30,7 +30,7 @@ function RepositoriesList({
return ( 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> <label className="font-bold">Repositories</label>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
{repositories?.map((repository) => ( {repositories?.map((repository) => (
@@ -60,7 +60,7 @@ function RepositoriesList({
data-cy="install-repository-button" data-cy="install-repository-button"
type="button" type="button"
style={{ marginTop: "10px" }} 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)} onClick={() => setShowAddRepositoryModal(true)}
> >
+ Add Repository + Add Repository

View File

@@ -6,7 +6,7 @@ import apiService from "../../API/apiService";
import Spinner from "../Spinner"; import Spinner from "../Spinner";
import { useUpdateRepo } from "../../API/repositories"; import { useUpdateRepo } from "../../API/repositories";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router";
import { useAppContext } from "../../context/AppContext"; import { useAppContext } from "../../context/AppContext";
type RepositoryViewerProps = { type RepositoryViewerProps = {
@@ -22,7 +22,6 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const { data: charts, isLoading } = useQuery<Chart[]>({ const { data: charts, isLoading } = useQuery<Chart[]>({
//@ts-ignore
queryKey: ["charts", repository?.name || ""], queryKey: ["charts", repository?.name || ""],
queryFn: apiService.getRepositoryCharts, queryFn: apiService.getRepositoryCharts,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
@@ -76,7 +75,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
if (repository === undefined) { if (repository === undefined) {
return ( 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 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. one with the &quot;Add Repository&quot; button on the left side bar.
</div> </div>
@@ -98,8 +97,8 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
update.mutate(); 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"> <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.isLoading ? <Spinner size={4} /> : <BsArrowRepeat />} {update.isPending ? <Spinner size={4} /> : <BsArrowRepeat />}
Update Update
</span> </span>
</button> </button>
@@ -108,7 +107,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
removeRepository(); 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 />} {isRemoveLoading ? <Spinner size={4} /> : <BsTrash3 />}
Remove Remove
</span> </span>
@@ -119,7 +118,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
value={searchValue} value={searchValue}
type="text" type="text"
placeholder="Filter..." 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>
</div> </div>
@@ -142,7 +141,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
)} )}
{showNoChartsAlert && ( {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 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. one with the &quot;Add Repository&quot; button on the left side bar.
</div> </div>

View File

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

View File

@@ -1,7 +1,7 @@
import { ChangeEvent, useMemo, useState, useRef, useEffect } from "react"; import { ChangeEvent, useMemo, useState, useRef, useEffect } from "react";
import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js"; import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js";
import { useGetReleaseInfoByType } from "../../API/releases"; import { useGetReleaseInfoByType } from "../../API/releases";
import { useParams } from "react-router-dom"; import { useParams } from "react-router";
import useCustomSearchParams from "../../hooks/useCustomSearchParams"; import useCustomSearchParams from "../../hooks/useCustomSearchParams";
import parse from "html-react-parser"; import parse from "html-react-parser";
@@ -134,7 +134,7 @@ function RevisionDiff({
return ( return (
<div> <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"> <div className="flex items-center">
<input <input
checked={viewMode === "view"} checked={viewMode === "view"}
@@ -186,7 +186,7 @@ function RevisionDiff({
<div> <div>
Diff with specific revision: Diff with specific revision:
<input <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" type="text"
value={specificVersion} value={specificVersion}
onChange={(e) => setSpecificVersion(Number(e.target.value))} onChange={(e) => setSpecificVersion(Number(e.target.value))}
@@ -201,7 +201,7 @@ function RevisionDiff({
type="checkbox" type="checkbox"
onChange={handleUserDefinedCheckbox} onChange={handleUserDefinedCheckbox}
checked={!!userDefinedValue} 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 <label
htmlFor="user-define-only-checkbox" htmlFor="user-define-only-checkbox"
@@ -215,7 +215,9 @@ function RevisionDiff({
{isLoading ? <Spinner /> : ""} {isLoading ? <Spinner /> : ""}
{viewMode === VIEW_MODE_VIEW_ONLY && content ? ( {viewMode === VIEW_MODE_VIEW_ONLY && content ? (
<div className="bg-white overflow-x-auto w-full p-3 relative"> <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> </div>
) : ( ) : (
"" ""

View File

@@ -1,5 +1,5 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router";
import hljs from "highlight.js"; import hljs from "highlight.js";
import { RiExternalLinkLine } from "react-icons/ri"; import { RiExternalLinkLine } from "react-icons/ri";
@@ -32,19 +32,19 @@ export default function RevisionResource({ isLatest }: Props) {
cellPadding={6} cellPadding={6}
className="border-spacing-y-2 font-semibold border-separate w-full text-xs " 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> <tr>
<td className="pl-6 rounded">RESOURCE TYPE</td> <td className="pl-6 rounded-sm">RESOURCE TYPE</td>
<td>NAME</td> <td>NAME</td>
<td>STATUS</td> <td>STATUS</td>
<td>STATUS MESSAGE</td> <td>STATUS MESSAGE</td>
<td className="rounded"></td> <td className="rounded-sm"></td>
</tr> </tr>
</thead> </thead>
{isLoading ? ( {isLoading ? (
<Spinner /> <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?.length ? (
resources resources
.sort(function (a, b) { .sort(function (a, b) {
@@ -65,7 +65,7 @@ export default function RevisionResource({ isLatest }: Props) {
)) ))
) : ( ) : (
<tr> <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.{" "} Looks like you don&apos;t have any resources.{" "}
<RiExternalLinkLine className="ml-2 text-lg" /> <RiExternalLinkLine className="ml-2 text-lg" />
</div> </div>
@@ -100,11 +100,11 @@ const ResourceRow = ({
return ( return (
<> <>
<tr className="min-w-[100%] min-h[70px] text-sm py-2"> <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 className="font-bold text-sm w-56">{name}</td>
<td>{reason ? <Badge type={badgeType}>{reason}</Badge> : null}</td> <td>{reason ? <Badge type={badgeType}>{reason}</Badge> : null}</td>
<td className="rounded text-gray-100"> <td className="rounded-sm text-gray-100">
<div className="flex flex-col space-y-1 flex-start"> <div className="flex flex-col gap-1 flex-start">
{message && ( {message && (
<div className="text-gray-500 font-thin">{message}</div> <div className="text-gray-500 font-thin">{message}</div>
)} )}
@@ -113,7 +113,7 @@ const ResourceRow = ({
)} )}
</div> </div>
</td> </td>
<td className="rounded"> <td className="rounded-sm">
{isLatest && reason !== "NotFound" ? ( {isLatest && reason !== "NotFound" ? (
<div className="flex justify-end items-center mr-36"> <div className="flex justify-end items-center mr-36">
<Button className="px-1 text-xs" onClick={toggleDrawer}> <Button className="px-1 text-xs" onClick={toggleDrawer}>
@@ -183,7 +183,7 @@ const DescribeResource = ({
<div className="flex items-center gap-4 pr-4"> <div className="flex items-center gap-4 pr-4">
<a <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" 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" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
@@ -207,7 +207,7 @@ const DescribeResource = ({
) : ( ) : (
<div className="h-full overflow-y-auto "> <div className="h-full overflow-y-auto ">
<pre <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" }} style={{ overflow: "unset" }}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: yamlFormattedData, __html: yamlFormattedData,

View File

@@ -1,5 +1,5 @@
import { BsArrowDownRight, BsArrowUpRight } from "react-icons/bs"; import { BsArrowDownRight, BsArrowUpRight } from "react-icons/bs";
import { useParams } from "react-router-dom"; import { useParams } from "react-router";
import { compare } from "compare-versions"; import { compare } from "compare-versions";
import { ReleaseRevision } from "../../data/types"; import { ReleaseRevision } from "../../data/types";
@@ -40,7 +40,7 @@ export default function RevisionsList({
} }
onClick={() => changeRelease(release.revision)} onClick={() => changeRelease(release.revision)}
key={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 release.revision === selectedRevision
? "border-revision-dark bg-white" ? "border-revision-dark bg-white"
: "border-revision-light bg-body-background" : "border-revision-light bg-body-background"

View File

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

View File

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

View File

@@ -1,13 +1,55 @@
/* 1. Google Fonts */ /* 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"); @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 directives */
@tailwind base; @import "tailwindcss";
@tailwind components;
@tailwind utilities;
/* 3. Theme variables (CSS-first approach) */ @plugin 'flowbite/plugin';
:root { @source "../node_modules/flowbite";
@plugin "flowbite-react/plugin/tailwindcss";
@source "../.flowbite-react/class-list.json";
/* 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);
}
@layer utilities {
/* Theme variables (CSS-first approach) */
:root {
/* Fonts */ /* Fonts */
--font-roboto: "Roboto", serif; --font-roboto: "Roboto", serif;
--font-roboto-slab: "Roboto Slab", serif; --font-roboto-slab: "Roboto Slab", serif;
@@ -66,9 +108,10 @@
--color-revision: #d6effe; --color-revision: #d6effe;
--color-header-install: #ebefff; --color-header-install: #ebefff;
--color-upgrade-color: #fc1683; --color-upgrade-color: #fc1683;
}
} }
/* 4. Base layer */ /* Base layer */
@layer base { @layer base {
body { body {
color: var(--color-body); color: var(--color-body);
@@ -82,58 +125,14 @@
} }
} }
/* 5. Components layer */ /* Code & pre fonts */
@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 */
pre, pre,
code { code {
font-family: var(--font-sf-mono); font-family: var(--font-sf-mono);
font-size: 12.5px; font-size: 12.5px;
} }
/* 7. Portal positioning */ /* Portal positioning */
#portal { #portal {
top: 0; top: 0;
width: 40%; width: 40%;
@@ -141,13 +140,13 @@ code {
position: absolute; position: absolute;
} }
/* 8. Required fields */ /* Required fields */
.require:after { .require:after {
content: " *"; content: " *";
color: red; color: red;
} }
/* 9. Input focus shadow */ /* Input focus shadow */
.input-box-shadow:focus { .input-box-shadow:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); 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 LogoHeader from "../assets/logo-header.svg";
import DropDown from "../components/common/DropDown"; import DropDown from "../components/common/DropDown";
import WatcherIcon from "../assets/k8s-watcher.svg"; import WatcherIcon from "../assets/k8s-watcher.svg";
@@ -13,15 +13,22 @@ import { useGetApplicationStatus } from "../API/other";
import LinkWithSearchParams from "../components/LinkWithSearchParams"; import LinkWithSearchParams from "../components/LinkWithSearchParams";
import apiService from "../API/apiService"; import apiService from "../API/apiService";
import { useAppContext } from "../context/AppContext"; import { useAppContext } from "../context/AppContext";
import { useEffect, useEffectEvent } from "react";
export default function Header() { export default function Header() {
const { clusterMode, setClusterMode } = useAppContext(); const { clusterMode, setClusterMode } = useAppContext();
const { data: statusData } = useGetApplicationStatus({ const { data: statusData, isSuccess } = useGetApplicationStatus();
onSuccess: (data) => {
setClusterMode(data.ClusterMode); const onSuccess = useEffectEvent(() => {
}, setClusterMode(!!statusData?.ClusterMode);
}); });
useEffect(() => {
if (isSuccess && statusData) {
onSuccess();
}
}, [isSuccess, statusData]);
const location = useLocation(); const location = useLocation();
const openProjectPage = () => { const openProjectPage = () => {
@@ -46,7 +53,7 @@ export default function Header() {
const getBtnStyle = (identifier: string) => const getBtnStyle = (identifier: string) =>
`text-md py-2.5 px-5 ${ `text-md py-2.5 px-5 ${
location.pathname.includes(`/${identifier}`) 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> </div>
<div className="h-16 flex items-center text-sm "> <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} /> <img src={WatcherIcon} width={40} height={40} />
<div className="flex flex-col"> <div className="flex flex-col">
<a <a

View File

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

View File

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

View File

@@ -2,10 +2,10 @@ import InstalledPackagesHeader from "../components/InstalledPackages/InstalledPa
import InstalledPackagesList from "../components/InstalledPackages/InstalledPackagesList"; import InstalledPackagesList from "../components/InstalledPackages/InstalledPackagesList";
import ClustersList from "../components/ClustersList"; import ClustersList from "../components/ClustersList";
import { useGetInstalledReleases } from "../API/releases"; import { useGetInstalledReleases } from "../API/releases";
import { useMemo, useState } from "react"; import { useEffect, useEffectEvent, useMemo, useState } from "react";
import Spinner from "../components/Spinner"; import Spinner from "../components/Spinner";
import useAlertError from "../hooks/useAlertError"; import useAlertError from "../hooks/useAlertError";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router";
import useCustomSearchParams from "../hooks/useCustomSearchParams"; import useCustomSearchParams from "../hooks/useCustomSearchParams";
import { Release } from "../data/types"; import { Release } from "../data/types";
@@ -27,18 +27,21 @@ function Installed() {
const [filterKey, setFilterKey] = useState<string>(""); const [filterKey, setFilterKey] = useState<string>("");
const alertError = useAlertError(); const alertError = useAlertError();
const { data, isLoading, isRefetching } = useGetInstalledReleases( const { data, isLoading, isRefetching, isError, error } =
context ?? "", useGetInstalledReleases(context ?? "");
{
retry: false, const onError = useEffectEvent(() => {
onError: (e) => {
alertError.setShowErrorModal({ alertError.setShowErrorModal({
title: "Failed to get list of charts", title: "Failed to get list of charts",
msg: (e as Error).message, msg: error?.message ?? "",
}); });
}, });
useEffect(() => {
if (isError) {
onError();
} }
); }, [isError]);
const filteredReleases = useMemo(() => { const filteredReleases = useMemo(() => {
return ( 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 RepositoriesList from "../components/repository/RepositoriesList";
import RepositoryViewer from "../components/repository/RepositoryViewer"; import RepositoryViewer from "../components/repository/RepositoryViewer";
import { Repository } from "../data/types"; import { Repository } from "../data/types";
import { useGetRepositories } from "../API/repositories"; import { useGetRepositories } from "../API/repositories";
import { HelmRepositories } from "../API/interfaces"; import { useParams } from "react-router";
import { useParams } from "react-router-dom";
import { useAppContext } from "../context/AppContext"; import { useAppContext } from "../context/AppContext";
import useNavigateWithSearchParams from "../hooks/useNavigateWithSearchParams"; import useNavigateWithSearchParams from "../hooks/useNavigateWithSearchParams";
@@ -34,16 +33,25 @@ function RepositoryPage() {
} }
}, [selectedRepo, repoFromParams, context, navigate]); }, [selectedRepo, repoFromParams, context, navigate]);
const { data: repositories = [] } = useGetRepositories({ const { data: repositories = [], isSuccess } = useGetRepositories();
onSuccess: (data: HelmRepositories) => {
const sortedData = data?.sort((a, b) => a.name.localeCompare(b.name)); 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) { if (sortedData && sortedData.length > 0 && !repoFromParams) {
handleRepositoryChanged(sortedData[0]); handleRepositoryChanged(sortedData[0]);
} }
},
}); });
useEffect(() => {
if (repositories.length && isSuccess) {
onSuccess();
}
}, [repositories, isSuccess]);
const selectedRepository = useMemo(() => { const selectedRepository = useMemo(() => {
if (repoFromParams) { if (repoFromParams) {
return repositories?.find((repo) => repo.name === repoFromParams); return repositories?.find((repo) => repo.name === repoFromParams);

View File

@@ -1,8 +1,8 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router";
import RevisionDetails from "../components/revision/RevisionDetails"; import RevisionDetails from "../components/revision/RevisionDetails";
import RevisionsList from "../components/revision/RevisionsList"; import RevisionsList from "../components/revision/RevisionsList";
import { ReleaseRevision } from "../data/types"; import { Release, ReleaseRevision } from "../data/types";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import apiService from "../API/apiService"; import apiService from "../API/apiService";
import Spinner from "../components/Spinner"; import Spinner from "../components/Spinner";
@@ -15,18 +15,15 @@ function Revision() {
const selectedRevision = revision ? parseInt(revision, 10) : 0; const selectedRevision = revision ? parseInt(revision, 10) : 0;
const { data: releaseRevisions, isLoading: isLoadingHistory } = useQuery< const { data: releaseRevisions = [], isLoading: isLoadingHistory } = useQuery(
ReleaseRevision[] {
>({
//eslint-ignore
//@ts-ignore
queryKey: ["releasesHistory", restParams], queryKey: ["releasesHistory", restParams],
queryFn: apiService.getReleasesHistory, queryFn: apiService.getReleasesHistory,
}); }
);
const latestRevision = useMemo( const latestRevision = useMemo(
() => () =>
Array.isArray(releaseRevisions) &&
releaseRevisions.reduce((max, revisionData) => { releaseRevisions.reduce((max, revisionData) => {
return Math.max(max, revisionData.revision); return Math.max(max, revisionData.revision);
}, Number.MIN_SAFE_INTEGER), }, Number.MIN_SAFE_INTEGER),
@@ -40,7 +37,7 @@ function Revision() {
const selectedRelease = useMemo(() => { const selectedRelease = useMemo(() => {
if (selectedRevision && releaseRevisions) { if (selectedRevision && releaseRevisions) {
return (releaseRevisions as ReleaseRevision[]).find( return releaseRevisions.find(
(r: ReleaseRevision) => r.revision === selectedRevision (r: ReleaseRevision) => r.revision === selectedRevision
); );
} }
@@ -70,14 +67,10 @@ function Revision() {
</div> </div>
) : selectedRelease ? ( ) : selectedRelease ? (
<RevisionDetails <RevisionDetails
//@ts-ignore release={selectedRelease as Release} // TODO fix it
release={selectedRelease} installedRevision={releaseRevisions?.[0]}
installedRevision={
//@ts-ignore
releaseRevisions?.[0] as ReleaseRevision
}
isLatest={selectedRelease.revision === latestRevision} isLatest={selectedRelease.revision === latestRevision}
latestRevision={latestRevision.revision} latestRevision={latestRevision}
/> />
) : null} ) : null}
</div> </div>
@@ -88,12 +81,12 @@ function Revision() {
const RevisionSidebarSkeleton = () => { const RevisionSidebarSkeleton = () => {
return ( return (
<> <>
<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 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 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 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 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 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"; import { ReleaseRevision } from "./data/types";
export function getAge(obj1: ReleaseRevision, obj2?: ReleaseRevision) { export function getAge(obj1: ReleaseRevision, obj2?: ReleaseRevision) {
const date = DateTime.fromISO(obj1.updated); const date = DateTime.fromISO(obj1.updated);
let dateNext = DateTime.now(); let dateNext: DateTimeMaybeValid = DateTime.now();
if (obj2) { if (obj2) {
dateNext = DateTime.fromISO(obj2.updated); 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, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,

View File

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

View File

@@ -1,6 +1,7 @@
import { defineConfig, loadEnv } from "vite"; import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react"; 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 }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), ""); const env = loadEnv(mode, process.cwd(), "");
@@ -8,22 +9,23 @@ export default defineConfig(({ mode }) => {
return { return {
plugins: [ plugins: [
react(), react(),
tailwindcss(),
viteStaticCopy({ viteStaticCopy({
targets: [ targets: [
{ {
src: 'public/analytics.js', src: "public/analytics.js",
dest: "assets/", dest: "assets/",
}, },
{ {
src: 'public/openapi.json', src: "public/openapi.json",
dest: "assets/", dest: "assets/",
}, },
{ {
src: 'public/logo.svg', src: "public/logo.svg",
dest: "assets/", dest: "assets/",
}, },
] ],
}) }),
], ],
build: { build: {
assetsDir: "./assets/", assetsDir: "./assets/",
@@ -32,14 +34,11 @@ export default defineConfig(({ mode }) => {
rollupOptions: { rollupOptions: {
output: { output: {
manualChunks: { manualChunks: {
react: ['react', 'react-dom', 'react-router-dom'], react: ["react", "react-dom", "react-router"],
vendors: ['luxon','highlight.js','diff2html','swagger-ui-react'] vendors: ["luxon", "highlight.js", "diff2html", "swagger-ui-react"],
} },
} },
}
}, },
css: {
postcss: './postcss.config.cjs'
}, },
server: { server: {
proxy: { proxy: {

File diff suppressed because it is too large Load Diff