mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-21 18:58:03 +00:00
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:
2
.husky/pre-commit
Executable file
2
.husky/pre-commit
Executable file
@@ -0,0 +1,2 @@
|
||||
cd frontend || exit 1
|
||||
npm run pre:commit
|
||||
2
frontend/.flowbite-react/.gitignore
vendored
Normal file
2
frontend/.flowbite-react/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
class-list.json
|
||||
pid
|
||||
10
frontend/.flowbite-react/config.json
Normal file
10
frontend/.flowbite-react/config.json
Normal 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
|
||||
}
|
||||
22
frontend/.flowbite-react/init.tsx
Normal file
22
frontend/.flowbite-react/init.tsx
Normal 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";
|
||||
@@ -1,3 +1,10 @@
|
||||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
||||
.env
|
||||
.gitignore
|
||||
.npmrc
|
||||
.prettierignore
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
.flowbite-react/*
|
||||
@@ -3,19 +3,13 @@ import type { StorybookConfig } from "@storybook/react-vite";
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx|mdx)"],
|
||||
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-docs"
|
||||
],
|
||||
addons: ["@storybook/addon-links", "@storybook/addon-docs"],
|
||||
core: {},
|
||||
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
features: {
|
||||
mdx2Csf: true,
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "tailwindcss/tailwind.css";
|
||||
import "../src/index.css";
|
||||
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { BrowserRouter } from "react-router";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import type { Preview, StoryFn } from "@storybook/react";
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
Welcome to the frontend of the helm dashboard.
|
||||
We care most about keeping the project:
|
||||
|
||||
1. Maintainable
|
||||
2. Extendable
|
||||
3. Contributor friendly
|
||||
@@ -28,12 +29,11 @@ Please follow through the file structure to understand how things are structured
|
||||
|
||||
1. Make sure you cloned the project correctly. This is explained in this [stage](https://github.com/komodorio/helm-dashboard/blob/helm-dashboard-v2/dashboard/README.md#setting-up-your-development-environment).
|
||||
2. run the backend server. This is also explained in the above link.
|
||||
2. go to `frontend` in your local project.
|
||||
3. in order to install dependencies and start the development server
|
||||
3. go to `frontend` in your local project.
|
||||
4. in order to install dependencies and start the development server
|
||||
- `npm i`
|
||||
- `npm run dev`
|
||||
4. with the default integration the dashboard should run on http://localhost:5173/
|
||||
|
||||
5. with the default integration the dashboard should run on http://localhost:5173/
|
||||
|
||||
# Component library
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ module.exports = defineConfig([{
|
||||
|
||||
extends: compat.extends(
|
||||
"enpitech",
|
||||
"eslint:recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
// "plugin:@typescript-eslint/recommended-requiring-type-checking", TODO enable and fix the types
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
1340
frontend/package-lock.json
generated
1340
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.90.11",
|
||||
"compare-versions": "^6.1.1",
|
||||
"diff2html": "^3.4.46",
|
||||
"diff2html": "^3.4.52",
|
||||
"flowbite-react": "^0.12.10",
|
||||
"highlight.js": "^11.11.1",
|
||||
"html-react-parser": "^5.2.10",
|
||||
@@ -17,54 +17,57 @@
|
||||
"react-icons": "^5.5.0",
|
||||
"react-intersection-observer": "^10.0.0",
|
||||
"react-modern-drawer": "^1.4.0",
|
||||
"react-router-dom": "^6.9.0",
|
||||
"react-router": "^7.9.6",
|
||||
"react-select": "^5.10.2",
|
||||
"swagger-ui-react": "^5.30.3",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@storybook/addon-docs": "^10.0.8",
|
||||
"@storybook/addon-links": "^10.0.8",
|
||||
"@storybook/mdx2-csf": "^1.1.0",
|
||||
"@storybook/react-vite": "^10.0.8",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@types/luxon": "^3.7.1",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/swagger-ui-react": "^5.18.0",
|
||||
"@types/uuid": "^11.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||
"@typescript-eslint/parser": "^8.47.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"cypress": "^13.3.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-config-enpitech": "^1.0.17",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-storybook": "^10.0.8",
|
||||
"flowbite": "^4.0.1",
|
||||
"globals": "^16.5.0",
|
||||
"lint-staged": "^13.2.3",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^3.6.2",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.7",
|
||||
"prettier": "^3.7.1",
|
||||
"storybook": "10.0.8",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-html-config": "^2.0.2",
|
||||
"vite-plugin-static-copy": "^3.1.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*": "prettier --write --ignore-unknown"
|
||||
"src/*.{js,jsx,ts,tsx}": [
|
||||
"npm run lint:fix",
|
||||
"npm run prettier:fix"
|
||||
],
|
||||
"*.{json,css,md,mdx}": "npm run prettier:fix"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"prepare": "cd .. && husky",
|
||||
"pre:commit": "lint-staged",
|
||||
"test": "echo \"Error: no test specified. Please use 'cypress:run' or 'cypress:open' commands\" && exit 1",
|
||||
"tsc:check": "tsc --noEmit",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
Release,
|
||||
ReleaseHealthStatus,
|
||||
ReleaseRevision,
|
||||
Repository,
|
||||
} from "../data/types";
|
||||
import { type QueryFunctionContext } from "@tanstack/react-query";
|
||||
|
||||
interface ClustersResponse {
|
||||
AuthInfo: string;
|
||||
Cluster: string;
|
||||
@@ -90,12 +90,17 @@ class ApiService {
|
||||
|
||||
getRepositoryCharts = async ({
|
||||
queryKey,
|
||||
}: QueryFunctionContext<Chart[], Repository>) => {
|
||||
}: {
|
||||
queryKey: readonly unknown[];
|
||||
}): Promise<Chart[]> => {
|
||||
const [, repository] = queryKey;
|
||||
const data = await this.fetchWithDefaults(
|
||||
if (!repository) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return await this.fetchWithDefaults<Chart[]>(
|
||||
`/api/helm/repositories/${repository}`
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
getChartVersions = async ({
|
||||
@@ -126,16 +131,16 @@ class ApiService {
|
||||
|
||||
getReleasesHistory = async ({
|
||||
queryKey,
|
||||
}: QueryFunctionContext<Release[], Release>): Promise<ReleaseRevision[]> => {
|
||||
}: {
|
||||
queryKey: readonly [string, Record<string, string | undefined>];
|
||||
}): Promise<ReleaseRevision[]> => {
|
||||
const [, params] = queryKey;
|
||||
|
||||
if (!params.namespace || !params.chart) return [];
|
||||
|
||||
const data = await this.fetchWithDefaults<ReleaseRevision[]>(
|
||||
return await this.fetchWithDefaults<ReleaseRevision[]>(
|
||||
`/api/helm/releases/${params.namespace}/${params.chart}/history`
|
||||
);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
getValues = async ({
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import {
|
||||
useQuery,
|
||||
type UseQueryOptions,
|
||||
useMutation,
|
||||
type UseMutationOptions,
|
||||
useQuery,
|
||||
type UseQueryOptions,
|
||||
} from "@tanstack/react-query";
|
||||
import { ChartVersion, Release } from "../data/types";
|
||||
import { LatestChartVersion } from "./interfaces";
|
||||
import apiService from "./apiService";
|
||||
import { getVersionManifestFormData } from "./shared";
|
||||
import { isNewerVersion } from "../utils";
|
||||
|
||||
export const HD_RESOURCE_CONDITION_TYPE = "hdHealth"; // it's our custom condition type, only one exists
|
||||
|
||||
export function useGetInstalledReleases(
|
||||
context: string,
|
||||
options?: UseQueryOptions<Release[]>
|
||||
) {
|
||||
export function useGetInstalledReleases(context: string) {
|
||||
return useQuery<Release[]>({
|
||||
queryKey: ["installedReleases", context],
|
||||
queryFn: () =>
|
||||
apiService.fetchWithDefaults<Release[]>("/api/helm/releases"),
|
||||
...(options ?? {}),
|
||||
retry: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,18 +73,14 @@ export function useGetReleaseManifest({
|
||||
}
|
||||
|
||||
// List of installed k8s resources for this release
|
||||
export function useGetResources(
|
||||
ns: string,
|
||||
name: string,
|
||||
options?: UseQueryOptions<StructuredResources[]>
|
||||
) {
|
||||
export function useGetResources(ns: string, name: string, enabled?: boolean) {
|
||||
const { data, ...rest } = useQuery<StructuredResources[]>({
|
||||
queryKey: ["resources", ns, name],
|
||||
queryFn: () =>
|
||||
apiService.fetchWithDefaults<StructuredResources[]>(
|
||||
`/api/helm/releases/${ns}/${name}/resources?health=true`
|
||||
),
|
||||
...(options ?? {}),
|
||||
enabled,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -138,6 +133,7 @@ export function useGetLatestVersion(
|
||||
apiService.fetchWithDefaults<ChartVersion[]>(
|
||||
`/api/helm/repositories/latestver?name=${chartName}`
|
||||
),
|
||||
gcTime: 0,
|
||||
...(options ?? {}),
|
||||
});
|
||||
}
|
||||
@@ -151,6 +147,8 @@ export function useGetVersions(
|
||||
apiService.fetchWithDefaults<LatestChartVersion[]>(
|
||||
`/api/helm/repositories/versions?name=${chartName}`
|
||||
),
|
||||
select: (data) =>
|
||||
data?.sort((a, b) => (isNewerVersion(a.version, b.version) ? 1 : -1)),
|
||||
...(options ?? {}),
|
||||
});
|
||||
}
|
||||
@@ -250,12 +248,12 @@ export function useChartReleaseValues({
|
||||
userDefinedValue?: string;
|
||||
revision?: number;
|
||||
version?: string;
|
||||
options?: UseQueryOptions<unknown>;
|
||||
options?: UseQueryOptions<string>;
|
||||
}) {
|
||||
return useQuery<unknown>({
|
||||
return useQuery<string>({
|
||||
queryKey: ["values", namespace, release, userDefinedValue, version],
|
||||
queryFn: () =>
|
||||
apiService.fetchWithDefaults<unknown>(
|
||||
apiService.fetchWithDefaults(
|
||||
`/api/helm/releases/${namespace}/${release}/values?${"userDefined=true"}${
|
||||
revision ? `&revision=${revision}` : ""
|
||||
}`,
|
||||
@@ -267,6 +265,12 @@ export function useChartReleaseValues({
|
||||
});
|
||||
}
|
||||
|
||||
export type VersionData = {
|
||||
version: string;
|
||||
repository?: string;
|
||||
urls: string[];
|
||||
};
|
||||
|
||||
export const useVersionData = ({
|
||||
version,
|
||||
userValues,
|
||||
@@ -275,7 +279,7 @@ export const useVersionData = ({
|
||||
namespace,
|
||||
releaseName,
|
||||
isInstallRepoChart = false,
|
||||
options,
|
||||
enabled = true,
|
||||
}: {
|
||||
version: string;
|
||||
userValues: string;
|
||||
@@ -284,9 +288,9 @@ export const useVersionData = ({
|
||||
namespace: string;
|
||||
releaseName: string;
|
||||
isInstallRepoChart?: boolean;
|
||||
options?: UseQueryOptions;
|
||||
enabled?: boolean;
|
||||
}) => {
|
||||
return useQuery({
|
||||
return useQuery<{ [key: string]: string }>({
|
||||
queryKey: [
|
||||
version,
|
||||
userValues,
|
||||
@@ -311,15 +315,16 @@ export const useVersionData = ({
|
||||
namespace ? namespace : "[empty]"
|
||||
}${`/${releaseName}`}`;
|
||||
|
||||
const data = await apiService.fetchWithDefaults(fetchUrl, {
|
||||
method: "post",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
return data;
|
||||
return await apiService.fetchWithDefaults<{ [key: string]: string }>(
|
||||
fetchUrl,
|
||||
{
|
||||
method: "post",
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
...(options ?? {}),
|
||||
enabled,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Header from "./layout/Header";
|
||||
import { HashRouter, Outlet, Route, Routes, useParams } from "react-router-dom";
|
||||
import { HashRouter, Outlet, Route, Routes, useParams } from "react-router";
|
||||
import "./index.css";
|
||||
import Installed from "./pages/Installed";
|
||||
import RepositoryPage from "./pages/Repository";
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
import { JSX, ReactNode } from "react";
|
||||
|
||||
export type BadgeCode = "success" | "warning" | "error" | "unknown";
|
||||
|
||||
@@ -29,7 +30,7 @@ export const BadgeCodes = Object.freeze({
|
||||
|
||||
export interface BadgeProps {
|
||||
type: BadgeCode;
|
||||
children: React.ReactNode;
|
||||
children: ReactNode;
|
||||
additionalClassNames?: string;
|
||||
}
|
||||
export default function Badge(props: BadgeProps): JSX.Element {
|
||||
@@ -41,7 +42,7 @@ export default function Badge(props: BadgeProps): JSX.Element {
|
||||
};
|
||||
|
||||
const badgeBase =
|
||||
"inline-flex items-center px-1 py-1 rounded text-xs font-light";
|
||||
"inline-flex items-center px-1 py-1 rounded-sm text-xs font-light";
|
||||
|
||||
const badgeElem = (
|
||||
<span
|
||||
|
||||
@@ -5,7 +5,7 @@ describe("Button component tests", () => {
|
||||
const buttonText = "buttonText";
|
||||
|
||||
it("renders", () => {
|
||||
mount(<Button onClick={() => {}}></Button>);
|
||||
mount(<Button onClick={() => {}} label=""></Button>);
|
||||
cy.get("button").should("exist");
|
||||
});
|
||||
|
||||
@@ -17,14 +17,14 @@ describe("Button component tests", () => {
|
||||
it("calls onClick when clicked", () => {
|
||||
const onClickStub = cy.stub().as("onClick");
|
||||
|
||||
mount(<Button onClick={onClickStub}></Button>);
|
||||
mount(<Button onClick={onClickStub} label={""}></Button>);
|
||||
|
||||
cy.get("button").click();
|
||||
cy.get("@onClick").should("have.been.calledOnce");
|
||||
});
|
||||
|
||||
it("should be disabled", () => {
|
||||
mount(<Button onClick={() => {}} disabled></Button>);
|
||||
mount(<Button onClick={() => {}} disabled label={""}></Button>);
|
||||
|
||||
cy.get("button").should("be.disabled");
|
||||
});
|
||||
|
||||
@@ -12,11 +12,12 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
import { HTMLAttributes, JSX, ReactNode } from "react";
|
||||
|
||||
// this is a type declaration for the action prop.
|
||||
// it is a function that takes a string as an argument and returns void.
|
||||
export interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
children: React.ReactNode;
|
||||
export interface ButtonProps extends HTMLAttributes<HTMLButtonElement> {
|
||||
children: ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
@@ -26,7 +27,7 @@ export default function Button(props: ButtonProps): JSX.Element {
|
||||
<>
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
className={`${props.className} bg-white border border-gray-300 hover:bg-gray-50 text-black py-1 px-4 rounded `}
|
||||
className={`${props.className} bg-white border border-gray-300 hover:bg-gray-50 text-black py-1 px-4 rounded-sm `}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
{props.children}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppContextProvider } from "../context/AppContext";
|
||||
import ClustersList from "./ClustersList";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import { BrowserRouter } from "react-router";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { Release } from "../data/types";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
||||
import { Cluster, Release } from "../data/types";
|
||||
import apiService from "../API/apiService";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
@@ -43,30 +43,39 @@ function ClustersList({
|
||||
}: ClustersListProps) {
|
||||
const { upsertSearchParams, removeSearchParam } = useCustomSearchParams();
|
||||
const { clusterMode } = useAppContext();
|
||||
const [sortedClusters, setSortedClusters] = useState<Cluster[]>([]);
|
||||
|
||||
const { data: clusters } = useQuery<Cluster[]>({
|
||||
const { data: clusters, isSuccess } = useQuery<Cluster[]>({
|
||||
queryKey: ["clusters", selectedCluster],
|
||||
queryFn: apiService.getClusters,
|
||||
onSuccess(data) {
|
||||
const sortedData = data?.sort((a, b) =>
|
||||
getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name))
|
||||
);
|
||||
|
||||
if (sortedData && sortedData.length > 0 && !selectedCluster) {
|
||||
onClusterChange(sortedData[0].Name);
|
||||
}
|
||||
|
||||
if (selectedCluster) {
|
||||
const cluster = data.find(
|
||||
(cluster) => getCleanClusterName(cluster.Name) === selectedCluster
|
||||
);
|
||||
if (!filteredNamespaces && cluster?.Namespace) {
|
||||
upsertSearchParams("filteredNamespace", cluster.Namespace);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const onSuccess = useEffectEvent((clusters: Cluster[]) => {
|
||||
const sortedData = [...clusters].sort((a, b) =>
|
||||
getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name))
|
||||
);
|
||||
setSortedClusters(sortedData);
|
||||
|
||||
if (sortedData && sortedData.length > 0 && !selectedCluster) {
|
||||
onClusterChange(sortedData[0].Name);
|
||||
}
|
||||
|
||||
if (selectedCluster) {
|
||||
const cluster = clusters.find(
|
||||
(cluster) => getCleanClusterName(cluster.Name) === selectedCluster
|
||||
);
|
||||
if (!filteredNamespaces && cluster?.Namespace) {
|
||||
upsertSearchParams("filteredNamespace", cluster.Namespace);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (clusters && isSuccess) {
|
||||
onSuccess(clusters);
|
||||
}
|
||||
}, [clusters, isSuccess]);
|
||||
|
||||
const namespaces = useMemo(() => {
|
||||
const mapNamespaces = new Map<string, number>();
|
||||
|
||||
@@ -98,39 +107,33 @@ function ClustersList({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-white flex flex-col p-2 rounded custom-shadow text-cluster-list w-48 m-5 h-fit pb-4 custom-">
|
||||
<div className="bg-white flex flex-col p-2 rounded-sm custom-shadow text-cluster-list w-48 m-5 h-fit pb-4 custom-">
|
||||
{!clusterMode ? (
|
||||
<>
|
||||
<label className="font-bold">Clusters</label>
|
||||
{clusters
|
||||
?.sort((a, b) =>
|
||||
getCleanClusterName(a.Name).localeCompare(
|
||||
getCleanClusterName(b.Name)
|
||||
)
|
||||
)
|
||||
?.map((cluster) => {
|
||||
return (
|
||||
<span
|
||||
key={cluster.Name}
|
||||
className="data-cy-clusterName flex items-center mt-2 text-xs"
|
||||
>
|
||||
<input
|
||||
className="cursor-pointer data-cy-clustersInput"
|
||||
onChange={(e) => {
|
||||
onClusterChange(e.target.value);
|
||||
}}
|
||||
type="radio"
|
||||
id={cluster.Name}
|
||||
value={cluster.Name}
|
||||
checked={cluster.Name === selectedCluster}
|
||||
name="clusters"
|
||||
/>
|
||||
<label htmlFor={cluster.Name} className="ml-1 ">
|
||||
{getCleanClusterName(cluster.Name)}
|
||||
</label>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
{sortedClusters?.map((cluster) => {
|
||||
return (
|
||||
<span
|
||||
key={cluster.Name}
|
||||
className="data-cy-clusterName flex items-center mt-2 text-xs"
|
||||
>
|
||||
<input
|
||||
className="cursor-pointer data-cy-clustersInput"
|
||||
onChange={(e) => {
|
||||
onClusterChange(e.target.value);
|
||||
}}
|
||||
type="radio"
|
||||
id={cluster.Name}
|
||||
value={cluster.Name}
|
||||
checked={cluster.Name === selectedCluster}
|
||||
name="clusters"
|
||||
/>
|
||||
<label htmlFor={cluster.Name} className="ml-1 ">
|
||||
{getCleanClusterName(cluster.Name)}
|
||||
</label>
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ const HealthStatus = ({ statusData }: Props) => {
|
||||
: cond.status === "Progressing"
|
||||
? "bg-warning"
|
||||
: "bg-danger"
|
||||
} w-2.5 h-2.5 rounded-sm`}
|
||||
} w-2.5 h-2.5 rounded-xs`}
|
||||
></span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { Release } from "../../data/types";
|
||||
import { Release, ReleaseHealthStatus } from "../../data/types";
|
||||
import { BsArrowUpCircleFill, BsPlusCircleFill } from "react-icons/bs";
|
||||
import { getAge } from "../../timeUtils";
|
||||
import StatusLabel, {
|
||||
@@ -33,13 +33,12 @@ export default function InstalledPackageCard({
|
||||
});
|
||||
const { data: latestVersionResult } = useGetLatestVersion(release.chartName, {
|
||||
queryKey: ["chartName", release.chartName],
|
||||
cacheTime: 0,
|
||||
});
|
||||
|
||||
const { data: statusData } = useQuery<unknown>({
|
||||
const { data: statusData } = useQuery<ReleaseHealthStatus[] | null>({
|
||||
queryKey: ["resourceStatus", release],
|
||||
enabled: inView,
|
||||
queryFn: () => apiService.getResourceStatus({ release }),
|
||||
enabled: inView,
|
||||
});
|
||||
|
||||
const latestVersionData: LatestChartVersion | undefined =
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function InstalledPackagesHeader({
|
||||
|
||||
<div className="w-1/3">
|
||||
<input
|
||||
className="border-installed-charts-filter rounded p-1 text-sm w-11/12"
|
||||
className="border-installed-charts-filter rounded-sm p-1 text-sm w-11/12"
|
||||
placeholder="Filter..."
|
||||
type="text"
|
||||
onChange={(ev) => setFilterKey(ev.target.value)}
|
||||
@@ -41,7 +41,7 @@ export default function InstalledPackagesHeader({
|
||||
</div>
|
||||
|
||||
{showNoPackageAlert && (
|
||||
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
|
||||
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
|
||||
Looks like you don't have any charts installed.
|
||||
"Repository" section may be a good place to start.
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NavLink, useLocation, useParams } from "react-router-dom";
|
||||
import { NavLink, useLocation, useParams } from "react-router";
|
||||
import { useAppContext } from "../context/AppContext";
|
||||
|
||||
const LinkWithSearchParams = ({
|
||||
@@ -12,7 +12,7 @@ const LinkWithSearchParams = ({
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const { search } = useLocation();
|
||||
const { context } = useParams();
|
||||
const { context = "" } = useParams();
|
||||
const { clusterMode } = useAppContext();
|
||||
|
||||
const params = new URLSearchParams(search);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
import { JSX } from "react";
|
||||
|
||||
// define the SelectMenuItem type:
|
||||
// This is an object with a label and id.
|
||||
|
||||
@@ -22,7 +22,7 @@ function ShutDownButton() {
|
||||
<button
|
||||
onClick={handleClick}
|
||||
title="Shut down the Helm Dashboard application"
|
||||
className="flex justify-center w-full mr-5 py-3 border border-transparent hover:border hover:border-gray-500 rounded hover:rounded-lg"
|
||||
className="flex justify-center w-full mr-5 py-3 border border-transparent hover:border hover:border-gray-500 rounded-sm hover:rounded-lg"
|
||||
>
|
||||
<BsPower className="w-6" />
|
||||
</button>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function Tabs({ tabs, selectedTab }: TabsProps) {
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.label}
|
||||
className={`cursor-pointer px-4 py-2 text-sm font-normal text-tab-color focus:outline-none"
|
||||
className={`cursor-pointer px-4 py-2 text-sm font-normal text-tab-color focus:outline-hidden"
|
||||
${
|
||||
selectedTab.value === tab.value &&
|
||||
"border-b-[3px] border-tab-color"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
import { JSX } from "react";
|
||||
|
||||
interface TabsBarProps {
|
||||
tabs: Array<{ name: string; component: JSX.Element }>;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* @return JSX.Element
|
||||
*
|
||||
*/
|
||||
import { JSX } from "react";
|
||||
|
||||
export interface TextInputProps {
|
||||
label: string;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ReactElement, cloneElement } from "react";
|
||||
import { type ReactElement, cloneElement, HTMLAttributes } from "react";
|
||||
|
||||
export default function Tooltip({
|
||||
id,
|
||||
@@ -11,11 +11,16 @@ export default function Tooltip({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{cloneElement(element, { "data-tooltip-target": id })}
|
||||
{cloneElement(
|
||||
element as ReactElement<HTMLAttributes<HTMLElement>>,
|
||||
{
|
||||
"data-tooltip-target": id,
|
||||
} as HTMLAttributes<HTMLElement>
|
||||
)}
|
||||
<div
|
||||
id={id}
|
||||
role="tooltip"
|
||||
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700"
|
||||
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-xs opacity-0 tooltip dark:bg-gray-700"
|
||||
>
|
||||
{title}
|
||||
<div className="tooltip-arrow" data-popper-arrow></div>
|
||||
@@ -24,14 +29,14 @@ export default function Tooltip({
|
||||
<button
|
||||
data-tooltip-target="tooltip-default"
|
||||
type="button"
|
||||
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
>
|
||||
Default tooltip
|
||||
</button>
|
||||
<div
|
||||
id="tooltip-default"
|
||||
role="tooltip"
|
||||
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700"
|
||||
className="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-xs opacity-0 tooltip dark:bg-gray-700"
|
||||
>
|
||||
Tooltip content
|
||||
<div className="tooltip-arrow" data-popper-arrow></div>
|
||||
|
||||
@@ -8,7 +8,7 @@ export const Troubleshoot = () => {
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<button className="bg-primary text-white p-2 flex items-center rounded text-sm font-medium font-roboto">
|
||||
<button className="bg-primary text-white p-2 flex items-center rounded-sm text-sm font-medium font-roboto">
|
||||
Troubleshoot in Komodor
|
||||
<RiExternalLinkLine className="ml-2 text-lg" />
|
||||
</button>
|
||||
|
||||
@@ -21,6 +21,7 @@ interface ButtonProps {
|
||||
* Optional click handler
|
||||
*/
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
|
||||
import ArrowDownIcon from "../../assets/arrow-down-icon.svg";
|
||||
|
||||
export type DropDownItem = {
|
||||
@@ -62,7 +62,7 @@ function DropDown({ items }: DropDownProps) {
|
||||
Y: e.pageY,
|
||||
}));
|
||||
}}
|
||||
className="flex items-center justify-between"
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
>
|
||||
Help
|
||||
<img src={ArrowDownIcon} className="ml-2 w-[10px] h-[10px]" />
|
||||
@@ -71,10 +71,10 @@ function DropDown({ items }: DropDownProps) {
|
||||
{popupState.isOpen && (
|
||||
<div
|
||||
ref={modalRef}
|
||||
className={`z-10 flex flex-col py-1 gap-1 bg-white mt-3 absolute rounded border top-[${popupState.Y}] left-[${popupState.X}] border-gray-200`}
|
||||
className={`z-10 flex flex-col py-1 gap-1 bg-white mt-3 absolute rounded-sm border top-[${popupState.Y}] left-[${popupState.X}] border-gray-200`}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<>
|
||||
<Fragment key={item.id}>
|
||||
{item.isSeparator ? (
|
||||
<div className="bg-gray-300 h-[1px]" />
|
||||
) : (
|
||||
@@ -96,7 +96,7 @@ function DropDown({ items }: DropDownProps) {
|
||||
<span>{item.text}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VFC, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Header } from "../Header/Header";
|
||||
import "./page.css";
|
||||
@@ -7,7 +7,7 @@ type User = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const Page: VFC = () => {
|
||||
export const Page = () => {
|
||||
const [user, setUser] = useState<User>();
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import useAlertError from "../../hooks/useAlertError";
|
||||
import useCustomSearchParams from "../../hooks/useCustomSearchParams";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import apiService from "../../API/apiService";
|
||||
|
||||
interface FormKeys {
|
||||
@@ -80,10 +80,10 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
bottomContent={
|
||||
<div className="flex justify-end p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<div className="flex justify-end p-6 gap-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<button
|
||||
data-cy="add-chart-repository-button"
|
||||
className="flex items-center text-white font-medium px-3 py-1.5 bg-primary hover:bg-add-repo focus:ring-4 focus:outline-none focus:ring-blue-300 disabled:bg-blue-300 rounded-lg text-base text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||
className="flex items-center text-white font-medium px-3 py-1.5 bg-primary hover:bg-add-repo focus:ring-4 focus:outline-hidden focus:ring-blue-300 disabled:bg-blue-300 rounded-lg text-base text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 cursor-pointer"
|
||||
onClick={addRepository}
|
||||
disabled={isLoading}
|
||||
>
|
||||
@@ -109,7 +109,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
||||
data-cy="add-chart-name"
|
||||
type="text"
|
||||
placeholder="Komodorio"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
|
||||
/>
|
||||
</label>
|
||||
<label className="flex-1" htmlFor="url">
|
||||
@@ -127,7 +127,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
||||
data-cy="add-chart-url"
|
||||
type="text"
|
||||
placeholder="https://helm-charts.komodor.io"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
||||
required
|
||||
id="username"
|
||||
type="text"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
|
||||
/>
|
||||
</label>
|
||||
<label className="flex-1" htmlFor="password">
|
||||
@@ -159,7 +159,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
||||
required
|
||||
id="password"
|
||||
type="text"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow"
|
||||
className="rounded-lg p-2 w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function ErrorModal({
|
||||
);
|
||||
|
||||
const bottomContent = (
|
||||
<div className="flex py-6 px-4 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<div className="flex py-6 px-4 gap-2 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<span className="text-sm text-muted fs-80 text-gray-500">
|
||||
Hint: Komodor has the same HELM capabilities, with enterprise features
|
||||
and support.{" "}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const ChartValues = ({
|
||||
Chart Value Reference:
|
||||
</label>
|
||||
<pre
|
||||
className="text-base bg-chart-values p-2 rounded font-medium w-full max-h-[330px] block overflow-y-auto font-sf-mono"
|
||||
className="text-base bg-chart-values p-2 rounded-sm font-medium w-full max-h-[330px] block overflow-y-auto font-sf-mono"
|
||||
dangerouslySetInnerHTML={
|
||||
chartValues && !loading
|
||||
? {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router";
|
||||
import useDebounce from "../../../hooks/useDebounce";
|
||||
|
||||
export const GeneralDetails = ({
|
||||
@@ -27,7 +27,7 @@ export const GeneralDetails = ({
|
||||
const { context } = useParams();
|
||||
const inputClassName = ` text-lg py-1 px-2 border border-1 border-gray-300 ${
|
||||
disabled ? "bg-gray-200" : "bg-white "
|
||||
} rounded`;
|
||||
} rounded-sm`;
|
||||
return (
|
||||
<div className="flex gap-8">
|
||||
<div>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
||||
import {
|
||||
useChartReleaseValues,
|
||||
useGetReleaseManifest,
|
||||
useGetVersions,
|
||||
useVersionData,
|
||||
VersionData,
|
||||
} from "../../../API/releases";
|
||||
import Modal, { ModalButtonStyle } from "../Modal";
|
||||
import { GeneralDetails } from "./GeneralDetails";
|
||||
@@ -12,7 +13,7 @@ import { ManifestDiff } from "./ManifestDiff";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import useNavigateWithSearchParams from "../../../hooks/useNavigateWithSearchParams";
|
||||
import { VersionToInstall } from "./VersionToInstall";
|
||||
import { isNewerVersion, isNoneEmptyArray } from "../../../utils";
|
||||
import { isNoneEmptyArray } from "../../../utils";
|
||||
import useCustomSearchParams from "../../../hooks/useCustomSearchParams";
|
||||
import { useChartRepoValues } from "../../../API/repositories";
|
||||
import { useDiffData } from "../../../API/shared";
|
||||
@@ -20,18 +21,18 @@ import { InstallChartModalProps } from "../../../data/types";
|
||||
import { DefinedValues } from "./DefinedValues";
|
||||
import apiService from "../../../API/apiService";
|
||||
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
|
||||
import { LatestChartVersion } from "../../../API/interfaces";
|
||||
|
||||
export const InstallReleaseChartModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
chartName,
|
||||
currentlyInstalledChartVersion,
|
||||
latestVersion,
|
||||
isUpgrade = false,
|
||||
latestRevision,
|
||||
}: InstallChartModalProps) => {
|
||||
const navigate = useNavigateWithSearchParams();
|
||||
const [userValues, setUserValues] = useState<string>();
|
||||
const [userValues, setUserValues] = useState<string>("");
|
||||
const [installError, setInstallError] = useState("");
|
||||
|
||||
const {
|
||||
@@ -44,38 +45,37 @@ export const InstallReleaseChartModal = ({
|
||||
const [namespace, setNamespace] = useState(queryNamespace || "");
|
||||
const [releaseName, setReleaseName] = useState(_releaseName || "");
|
||||
|
||||
const { error: versionsError, data: _versions } = useGetVersions(chartName, {
|
||||
select: (data) => {
|
||||
return data?.sort((a, b) =>
|
||||
isNewerVersion(a.version, b.version) ? 1 : -1
|
||||
);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
const empty = { version: "", repository: "", urls: [] };
|
||||
return setSelectedVersionData(data[0] ?? empty);
|
||||
},
|
||||
const {
|
||||
error: versionsError,
|
||||
data: _versions = [],
|
||||
isSuccess,
|
||||
} = useGetVersions(chartName);
|
||||
|
||||
const [selectedVersionData, setSelectedVersionData] = useState<VersionData>();
|
||||
|
||||
const [versions, setVersions] = useState<
|
||||
Array<LatestChartVersion & { isChartVersion: boolean }>
|
||||
>([]);
|
||||
|
||||
const onSuccess = useEffectEvent(() => {
|
||||
const empty = { version: "", repository: "", urls: [] };
|
||||
setSelectedVersionData(_versions[0] ?? empty);
|
||||
setVersions(
|
||||
_versions?.map((v) => ({
|
||||
...v,
|
||||
isChartVersion: v.version === currentlyInstalledChartVersion,
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
const versions = _versions?.map((v) => ({
|
||||
...v,
|
||||
isChartVersion: v.version === currentlyInstalledChartVersion,
|
||||
}));
|
||||
useEffect(() => {
|
||||
if (isSuccess && _versions.length) {
|
||||
onSuccess();
|
||||
}
|
||||
}, [isSuccess, _versions]);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
latestVersion = latestVersion ?? currentlyInstalledChartVersion; // a guard for typescript, latestVersion is always defined
|
||||
const [selectedVersionData, setSelectedVersionData] = useState<{
|
||||
version: string;
|
||||
repository?: string;
|
||||
urls: string[];
|
||||
}>();
|
||||
|
||||
const selectedVersion = useMemo(() => {
|
||||
return selectedVersionData?.version;
|
||||
}, [selectedVersionData]);
|
||||
|
||||
const selectedRepo = useMemo(() => {
|
||||
return selectedVersionData?.repository || "";
|
||||
}, [selectedVersionData]);
|
||||
const selectedVersion = selectedVersionData?.version || "";
|
||||
const selectedRepo = selectedVersionData?.repository || "";
|
||||
|
||||
const chartAddress = useMemo(() => {
|
||||
if (!selectedVersionData || !selectedVersionData.repository) return "";
|
||||
@@ -86,13 +86,13 @@ export const InstallReleaseChartModal = ({
|
||||
}, [selectedVersionData, chartName]);
|
||||
|
||||
// the original chart values
|
||||
const { data: chartValues } = useChartRepoValues({
|
||||
version: selectedVersion || "",
|
||||
const { data: chartValues = "" } = useChartRepoValues({
|
||||
version: selectedVersion,
|
||||
chart: chartAddress,
|
||||
});
|
||||
|
||||
// The user defined values (if any we're set)
|
||||
const { data: releaseValues, isLoading: loadingReleaseValues } =
|
||||
const { data: releaseValues = "", isLoading: loadingReleaseValues } =
|
||||
useChartReleaseValues({
|
||||
namespace,
|
||||
release: String(releaseName),
|
||||
@@ -100,16 +100,15 @@ export const InstallReleaseChartModal = ({
|
||||
});
|
||||
|
||||
// This hold the selected version manifest, we use it for the diff
|
||||
const { data: selectedVerData, error: selectedVerDataError } = useVersionData(
|
||||
{
|
||||
version: selectedVersion || "",
|
||||
userValues: userValues || "",
|
||||
const { data: selectedVerData = {}, error: selectedVerDataError } =
|
||||
useVersionData({
|
||||
version: selectedVersion,
|
||||
userValues,
|
||||
chartAddress,
|
||||
releaseValues,
|
||||
namespace,
|
||||
releaseName,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const { data: currentVerManifest, error: currentVerManifestError } =
|
||||
useGetReleaseManifest({
|
||||
@@ -123,14 +122,14 @@ export const InstallReleaseChartModal = ({
|
||||
error: diffError,
|
||||
} = useDiffData({
|
||||
selectedRepo,
|
||||
versionsError: versionsError as string,
|
||||
currentVerManifest,
|
||||
versionsError: versionsError as unknown as string, // TODO fix it
|
||||
currentVerManifest: currentVerManifest as unknown as string, // TODO fix it
|
||||
selectedVerData,
|
||||
chart: chartAddress,
|
||||
});
|
||||
|
||||
// Confirm method (install)
|
||||
const setReleaseVersionMutation = useMutation({
|
||||
const setReleaseVersionMutation = useMutation<VersionData>({
|
||||
mutationKey: [
|
||||
"setVersion",
|
||||
namespace,
|
||||
@@ -149,8 +148,7 @@ export const InstallReleaseChartModal = ({
|
||||
}
|
||||
formData.append("version", selectedVersion || "");
|
||||
formData.append("values", userValues || releaseValues || ""); // if userValues is empty, we use the release values
|
||||
|
||||
const data = await apiService.fetchWithDefaults(
|
||||
return await apiService.fetchWithDefaults(
|
||||
`/api/helm/releases/${
|
||||
namespace ? namespace : "default"
|
||||
}${`/${releaseName}`}`,
|
||||
@@ -159,7 +157,6 @@ export const InstallReleaseChartModal = ({
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
return data;
|
||||
},
|
||||
onSuccess: async (response) => {
|
||||
onClose();
|
||||
@@ -187,7 +184,7 @@ export const InstallReleaseChartModal = ({
|
||||
title={
|
||||
<InstallUpgradeTitle
|
||||
isUpgrade={isUpgrade}
|
||||
releaseValues={isUpgrade || releaseValues}
|
||||
releaseValues={isUpgrade || !!releaseValues}
|
||||
chartName={chartName}
|
||||
/>
|
||||
}
|
||||
@@ -197,11 +194,11 @@ export const InstallReleaseChartModal = ({
|
||||
id: "1",
|
||||
callback: setReleaseVersionMutation.mutate,
|
||||
variant: ModalButtonStyle.info,
|
||||
isLoading: setReleaseVersionMutation.isLoading,
|
||||
isLoading: setReleaseVersionMutation.isPending,
|
||||
disabled:
|
||||
loadingReleaseValues ||
|
||||
isLoadingDiff ||
|
||||
setReleaseVersionMutation.isLoading,
|
||||
setReleaseVersionMutation.isPending,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -233,11 +230,11 @@ export const InstallReleaseChartModal = ({
|
||||
diff={diffData as string}
|
||||
isLoading={isLoadingDiff}
|
||||
error={
|
||||
(currentVerManifestError as string) ||
|
||||
(selectedVerDataError as string) ||
|
||||
(diffError as string) ||
|
||||
(currentVerManifestError as unknown as string) || // TODO fix it
|
||||
(selectedVerDataError as unknown as string) ||
|
||||
(diffError as unknown as string) ||
|
||||
installError ||
|
||||
(versionsError as string)
|
||||
(versionsError as unknown as string)
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useParams } from "react-router";
|
||||
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
||||
import { useGetVersions, useVersionData } from "../../../API/releases";
|
||||
import Modal, { ModalButtonStyle } from "../Modal";
|
||||
import { GeneralDetails } from "./GeneralDetails";
|
||||
@@ -8,19 +8,19 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { useChartRepoValues } from "../../../API/repositories";
|
||||
import useNavigateWithSearchParams from "../../../hooks/useNavigateWithSearchParams";
|
||||
import { VersionToInstall } from "./VersionToInstall";
|
||||
import { isNewerVersion, isNoneEmptyArray } from "../../../utils";
|
||||
import { isNoneEmptyArray } from "../../../utils";
|
||||
import { useDiffData } from "../../../API/shared";
|
||||
import { InstallChartModalProps } from "../../../data/types";
|
||||
import { DefinedValues } from "./DefinedValues";
|
||||
import apiService from "../../../API/apiService";
|
||||
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
|
||||
import { LatestChartVersion } from "../../../API/interfaces";
|
||||
|
||||
export const InstallRepoChartModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
chartName,
|
||||
currentlyInstalledChartVersion,
|
||||
latestVersion,
|
||||
}: InstallChartModalProps) => {
|
||||
const navigate = useNavigateWithSearchParams();
|
||||
const [userValues, setUserValues] = useState("");
|
||||
@@ -31,42 +31,46 @@ export const InstallRepoChartModal = ({
|
||||
const [namespace, setNamespace] = useState("");
|
||||
const [releaseName, setReleaseName] = useState(chartName);
|
||||
|
||||
const { error: versionsError, data: _versions } = useGetVersions(chartName, {
|
||||
select: (data) => {
|
||||
return data?.sort((a, b) =>
|
||||
isNewerVersion(a.version, b.version) ? 1 : -1
|
||||
);
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
const empty = { version: "", repository: "", urls: [] };
|
||||
const versionsToRepo = data.filter(
|
||||
(v) => v.repository === currentRepoCtx
|
||||
);
|
||||
const {
|
||||
error: versionsError,
|
||||
data: _versions = [],
|
||||
isSuccess,
|
||||
} = useGetVersions(chartName);
|
||||
|
||||
return setSelectedVersionData(versionsToRepo[0] ?? empty);
|
||||
},
|
||||
});
|
||||
const [versions, setVersions] = useState<
|
||||
Array<LatestChartVersion & { isChartVersion: boolean }>
|
||||
>([]);
|
||||
|
||||
const versions = _versions?.map((v) => ({
|
||||
...v,
|
||||
isChartVersion: v.version === currentlyInstalledChartVersion,
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
latestVersion = latestVersion ?? currentlyInstalledChartVersion; // a guard for typescript, latestVersion is always defined
|
||||
const [selectedVersionData, setSelectedVersionData] = useState<{
|
||||
version: string;
|
||||
repository?: string;
|
||||
urls: string[];
|
||||
}>();
|
||||
|
||||
const selectedVersion = useMemo(() => {
|
||||
return selectedVersionData?.version;
|
||||
}, [selectedVersionData]);
|
||||
const onSuccess = useEffectEvent(() => {
|
||||
const empty = { version: "", repository: "", urls: [] };
|
||||
const versionsToRepo = _versions.filter(
|
||||
(v) => v.repository === currentRepoCtx
|
||||
);
|
||||
|
||||
const selectedRepo = useMemo(() => {
|
||||
return selectedVersionData?.repository;
|
||||
}, [selectedVersionData]);
|
||||
setSelectedVersionData(versionsToRepo[0] ?? empty);
|
||||
setVersions(
|
||||
_versions?.map((v) => ({
|
||||
...v,
|
||||
isChartVersion: v.version === currentlyInstalledChartVersion,
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess && _versions.length) {
|
||||
onSuccess();
|
||||
}
|
||||
}, [isSuccess, _versions]);
|
||||
|
||||
const selectedVersion = selectedVersionData?.version;
|
||||
|
||||
const selectedRepo = selectedVersionData?.repository;
|
||||
|
||||
const chartAddress = useMemo(() => {
|
||||
if (!selectedVersionData || !selectedVersionData?.repository) {
|
||||
@@ -77,15 +81,15 @@ export const InstallRepoChartModal = ({
|
||||
: `${selectedVersionData?.repository}/${chartName}`;
|
||||
}, [selectedVersionData, chartName]);
|
||||
|
||||
const { data: chartValues, isLoading: loadingChartValues } =
|
||||
const { data: chartValues = "", isLoading: loadingChartValues } =
|
||||
useChartRepoValues({
|
||||
version: selectedVersion || "",
|
||||
chart: chartAddress,
|
||||
});
|
||||
|
||||
// This hold the selected version manifest, we use it for the diff
|
||||
const { data: selectedVerData, error: selectedVerDataError } = useVersionData(
|
||||
{
|
||||
const { data: selectedVerData = {}, error: selectedVerDataError } =
|
||||
useVersionData({
|
||||
version: selectedVersion || "",
|
||||
userValues,
|
||||
chartAddress,
|
||||
@@ -93,11 +97,8 @@ export const InstallRepoChartModal = ({
|
||||
namespace,
|
||||
releaseName,
|
||||
isInstallRepoChart: true,
|
||||
options: {
|
||||
enabled: Boolean(chartAddress),
|
||||
},
|
||||
}
|
||||
);
|
||||
enabled: Boolean(chartAddress),
|
||||
});
|
||||
|
||||
const {
|
||||
data: diffData,
|
||||
@@ -105,14 +106,17 @@ export const InstallRepoChartModal = ({
|
||||
error: diffError,
|
||||
} = useDiffData({
|
||||
selectedRepo: selectedRepo || "",
|
||||
versionsError: versionsError as string,
|
||||
versionsError: versionsError as unknown as string, // TODO fix it
|
||||
currentVerManifest: "", // current version manifest should always be empty since its a fresh install
|
||||
selectedVerData,
|
||||
chart: chartAddress,
|
||||
});
|
||||
|
||||
// Confirm method (install)
|
||||
const setReleaseVersionMutation = useMutation({
|
||||
const setReleaseVersionMutation = useMutation<{
|
||||
namespace: string;
|
||||
name: string;
|
||||
}>({
|
||||
mutationKey: [
|
||||
"setVersion",
|
||||
namespace,
|
||||
@@ -130,17 +134,17 @@ export const InstallRepoChartModal = ({
|
||||
formData.append("version", selectedVersion || "");
|
||||
formData.append("values", userValues);
|
||||
formData.append("name", releaseName || "");
|
||||
const data = await apiService.fetchWithDefaults(
|
||||
|
||||
return await apiService.fetchWithDefaults(
|
||||
`/api/helm/releases/${namespace ? namespace : "default"}`,
|
||||
{
|
||||
method: "post",
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
return data;
|
||||
},
|
||||
|
||||
onSuccess: async (response) => {
|
||||
onSuccess: async (response: { namespace: string; name: string }) => {
|
||||
onClose();
|
||||
navigate(`/${response.namespace}/${response.name}/installed/revision/1`);
|
||||
},
|
||||
@@ -169,11 +173,11 @@ export const InstallRepoChartModal = ({
|
||||
id: "1",
|
||||
callback: setReleaseVersionMutation.mutate,
|
||||
variant: ModalButtonStyle.info,
|
||||
isLoading: setReleaseVersionMutation.isLoading,
|
||||
isLoading: setReleaseVersionMutation.isPending,
|
||||
disabled:
|
||||
loadingChartValues ||
|
||||
isLoadingDiff ||
|
||||
setReleaseVersionMutation.isLoading,
|
||||
setReleaseVersionMutation.isPending,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -205,10 +209,10 @@ export const InstallRepoChartModal = ({
|
||||
diff={diffData as string}
|
||||
isLoading={isLoadingDiff}
|
||||
error={
|
||||
(selectedVerDataError as string) ||
|
||||
(diffError as string) ||
|
||||
(selectedVerDataError as unknown as string) || // TODO fix it
|
||||
(diffError as unknown as string) ||
|
||||
installError ||
|
||||
(versionsError as string)
|
||||
(versionsError as unknown as string)
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import Select, { components } from "react-select";
|
||||
import { FC, useMemo, useState } from "react";
|
||||
import Select, { components, GroupBase, SingleValueProps } from "react-select";
|
||||
import { BsCheck2 } from "react-icons/bs";
|
||||
import { NonEmptyArray } from "../../../data/types";
|
||||
|
||||
@@ -10,7 +10,19 @@ interface Version {
|
||||
urls: string[];
|
||||
}
|
||||
|
||||
export const VersionToInstall: React.FC<{
|
||||
type VersionOptionType = {
|
||||
value: Omit<Version, "isChartVersion">;
|
||||
label: string;
|
||||
check: boolean;
|
||||
};
|
||||
|
||||
type SpecificSingleValueProps = SingleValueProps<
|
||||
VersionOptionType,
|
||||
false, // IsMulti
|
||||
GroupBase<VersionOptionType>
|
||||
>;
|
||||
|
||||
export const VersionToInstall: FC<{
|
||||
versions: NonEmptyArray<Version>;
|
||||
initialVersion?: {
|
||||
repository?: string;
|
||||
@@ -78,14 +90,19 @@ export const VersionToInstall: React.FC<{
|
||||
}}
|
||||
value={selectedOption ?? initOpt}
|
||||
components={{
|
||||
SingleValue: ({ children, ...props }) => (
|
||||
<components.SingleValue {...props}>
|
||||
<span className="text-green-700 font-bold">{children}</span>
|
||||
{props.data.check && showCurrentVersion && (
|
||||
<BsCheck2 className="inline-block ml-2 text-green-700 font-bold" />
|
||||
)}
|
||||
</components.SingleValue>
|
||||
),
|
||||
SingleValue: ({ children, ...props }) => {
|
||||
const OriginalSingleValue =
|
||||
components.SingleValue as FC<SpecificSingleValueProps>;
|
||||
|
||||
return (
|
||||
<OriginalSingleValue {...props}>
|
||||
<span className="text-green-700 font-bold">{children}</span>
|
||||
{props.data.check && showCurrentVersion && (
|
||||
<BsCheck2 className="inline-block ml-2 text-green-700 font-bold" />
|
||||
)}
|
||||
</OriginalSingleValue>
|
||||
);
|
||||
},
|
||||
Option: ({ children, innerProps, data }) => (
|
||||
<div
|
||||
className={
|
||||
|
||||
@@ -51,7 +51,7 @@ const customModalActions: ModalAction[] = [
|
||||
id: "1",
|
||||
text: "custom button 1",
|
||||
className:
|
||||
"text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800",
|
||||
"text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-hidden focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800",
|
||||
callback: () => {
|
||||
action("clickCustomButton")("confirmModal: clicked custom button 1");
|
||||
},
|
||||
|
||||
@@ -41,23 +41,23 @@ const Modal = ({
|
||||
const colorVariants = new Map<ModalButtonStyle, string>([
|
||||
[
|
||||
ModalButtonStyle.default,
|
||||
"text-base font-semibold text-gray-500 bg-white hover:bg-gray-100 disabled:bg-gray-200 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 font-medium px-5 py-1 hover:text-gray-900 focus:z-10 ",
|
||||
"text-base font-semibold text-gray-500 bg-white hover:bg-gray-100 disabled:bg-gray-200 focus:ring-4 focus:outline-hidden focus:ring-gray-200 rounded-lg border border-gray-200 font-medium px-5 py-1 hover:text-gray-900 focus:z-10 ",
|
||||
],
|
||||
[
|
||||
ModalButtonStyle.info,
|
||||
"font-semibold text-white bg-blue-700 hover:bg-blue-800 disabled:bg-blue-700/80 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
|
||||
"font-semibold text-white bg-blue-700 hover:bg-blue-800 disabled:bg-blue-700/80 focus:ring-4 focus:outline-hidden focus:ring-blue-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
|
||||
],
|
||||
[
|
||||
ModalButtonStyle.success,
|
||||
"font-semibold text-white bg-green-700 hover:bg-green-800 disabled:bg-green-700/80 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
|
||||
"font-semibold text-white bg-green-700 hover:bg-green-800 disabled:bg-green-700/80 focus:ring-4 focus:outline-hidden focus:ring-green-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
|
||||
],
|
||||
[
|
||||
ModalButtonStyle.error,
|
||||
"font-semibold text-white bg-red-700 hover:bg-red-800 disabled:bg-red-700/80 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
|
||||
"font-semibold text-white bg-red-700 hover:bg-red-800 disabled:bg-red-700/80 focus:ring-4 focus:outline-hidden focus:ring-red-300 font-medium rounded-lg text-base px-3 py-1.5 text-center ",
|
||||
],
|
||||
[
|
||||
ModalButtonStyle.disabled,
|
||||
"font-semibold text-gray-500 bg-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-base font-medium px-3 py-1.5 hover:text-gray-900 focus:z-10 ",
|
||||
"font-semibold text-gray-500 bg-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-hidden focus:ring-gray-200 rounded-lg border border-gray-200 text-base font-medium px-3 py-1.5 hover:text-gray-900 focus:z-10 ",
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -78,14 +78,14 @@ const Modal = ({
|
||||
return createPortal(
|
||||
<>
|
||||
{isOpen && (
|
||||
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity ">
|
||||
<div className="fixed inset-0 bg-gray-500 bg-black/75 transition-opacity ">
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
style={{
|
||||
maxHeight: "95vh",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={`relative rounded-lg shadow m-7 w-2/5 max-w-[1300px] ${
|
||||
className={`relative rounded-lg shadow-sm m-7 w-2/5 max-w-[1300px] ${
|
||||
!containerClassNames ||
|
||||
(containerClassNames && !containerClassNames.includes("bg-"))
|
||||
? "bg-white"
|
||||
@@ -98,7 +98,7 @@ const Modal = ({
|
||||
{onClose ? (
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white cursor-pointer"
|
||||
data-modal-hide="staticModal"
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
@@ -118,20 +118,20 @@ const Modal = ({
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
<div className="p-4 space-y-6 overflow-y-auto max-h-[calc(100vh_-_200px)]">
|
||||
<div className="p-4 gap-6 overflow-y-auto max-h-[calc(100vh_-_200px)]">
|
||||
{children}
|
||||
</div>
|
||||
{bottomContent ? (
|
||||
<div className="p-5 text-sm">{bottomContent}</div>
|
||||
) : (
|
||||
<div className="flex justify-end p-6 space-x-2 border-t border-gray-200 rounded-b ">
|
||||
<div className="flex justify-end p-6 gap-2 border-t border-gray-200 rounded-b ">
|
||||
{actions?.map((action) => (
|
||||
<button
|
||||
key={action.id}
|
||||
type="button"
|
||||
className={
|
||||
action.isLoading
|
||||
? `flex items-center font-bold justify-around space-x-1 ${getClassName(
|
||||
? `flex items-center font-bold justify-around gap-1 ${getClassName(
|
||||
action
|
||||
)}`
|
||||
: `${getClassName(action)} `
|
||||
|
||||
@@ -30,7 +30,7 @@ function RepositoriesList({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-fit bg-white w-72 flex flex-col p-3 rounded custom-shadow text-dark gap-3">
|
||||
<div className="h-fit bg-white w-72 flex flex-col p-3 rounded-sm custom-shadow text-dark gap-3">
|
||||
<label className="font-bold">Repositories</label>
|
||||
<div className="flex flex-col gap-1">
|
||||
{repositories?.map((repository) => (
|
||||
@@ -60,7 +60,7 @@ function RepositoriesList({
|
||||
data-cy="install-repository-button"
|
||||
type="button"
|
||||
style={{ marginTop: "10px" }}
|
||||
className="h-8 w-fit flex items-center gap-2 border rounded text-muted border-gray-300 px-3 py-1 text-sm font-semibold"
|
||||
className="h-8 w-fit flex items-center gap-2 border rounded-sm text-muted border-gray-300 px-3 py-1 text-sm font-semibold cursor-pointer"
|
||||
onClick={() => setShowAddRepositoryModal(true)}
|
||||
>
|
||||
+ Add Repository
|
||||
|
||||
@@ -6,7 +6,7 @@ import apiService from "../../API/apiService";
|
||||
import Spinner from "../Spinner";
|
||||
import { useUpdateRepo } from "../../API/repositories";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useAppContext } from "../../context/AppContext";
|
||||
|
||||
type RepositoryViewerProps = {
|
||||
@@ -22,7 +22,6 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: charts, isLoading } = useQuery<Chart[]>({
|
||||
//@ts-ignore
|
||||
queryKey: ["charts", repository?.name || ""],
|
||||
queryFn: apiService.getRepositoryCharts,
|
||||
refetchOnWindowFocus: false,
|
||||
@@ -76,7 +75,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
||||
|
||||
if (repository === undefined) {
|
||||
return (
|
||||
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
|
||||
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
|
||||
Looks like you don't have any repositories installed. You can add
|
||||
one with the "Add Repository" button on the left side bar.
|
||||
</div>
|
||||
@@ -98,8 +97,8 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
||||
update.mutate();
|
||||
}}
|
||||
>
|
||||
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded">
|
||||
{update.isLoading ? <Spinner size={4} /> : <BsArrowRepeat />}
|
||||
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded-sm">
|
||||
{update.isPending ? <Spinner size={4} /> : <BsArrowRepeat />}
|
||||
Update
|
||||
</span>
|
||||
</button>
|
||||
@@ -108,7 +107,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
||||
removeRepository();
|
||||
}}
|
||||
>
|
||||
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded">
|
||||
<span className="h-8 flex items-center gap-2 bg-white border border-gray-300 px-5 py-1 text-sm font-semibold rounded-sm">
|
||||
{isRemoveLoading ? <Spinner size={4} /> : <BsTrash3 />}
|
||||
Remove
|
||||
</span>
|
||||
@@ -119,7 +118,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
||||
value={searchValue}
|
||||
type="text"
|
||||
placeholder="Filter..."
|
||||
className="mt-2 h-8 p-2 text-sm w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow rounded"
|
||||
className="mt-2 h-8 p-2 text-sm w-full border border-gray-300 focus:outline-hidden focus:border-sky-500 input-box-shadow rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,7 +141,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
||||
)}
|
||||
|
||||
{showNoChartsAlert && (
|
||||
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
|
||||
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
|
||||
Looks like you don't have any repositories installed. You can add
|
||||
one with the "Add Repository" button on the left side bar.
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "react-icons/bs";
|
||||
import { Release, ReleaseRevision } from "../../data/types";
|
||||
import StatusLabel, { DeploymentStatus } from "../common/StatusLabel";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router";
|
||||
import {
|
||||
useGetReleaseInfoByType,
|
||||
useGetLatestVersion,
|
||||
@@ -91,14 +91,14 @@ export default function RevisionDetails({
|
||||
refetch: refetchLatestVersion,
|
||||
isLoading: isLoadingLatestVersion,
|
||||
isRefetching: isRefetchingLatestVersion,
|
||||
} = useGetLatestVersion(release.chart_name, { cacheTime: 0 });
|
||||
} = useGetLatestVersion(release.chart_name);
|
||||
|
||||
const [showTestsResults, setShowTestResults] = useState(false);
|
||||
|
||||
const { setShowErrorModal } = useAlertError();
|
||||
const {
|
||||
mutate: runTests,
|
||||
isLoading: isRunningTests,
|
||||
isPending: isRunningTests,
|
||||
data: testResults,
|
||||
} = useTestRelease({
|
||||
onError: (error) => {
|
||||
@@ -306,7 +306,7 @@ export default function RevisionDetails({
|
||||
|
||||
function RevisionTag({ caption, text }: RevisionTagProps) {
|
||||
return (
|
||||
<span className="bg-revision p-1 rounded px-2 text-sm">
|
||||
<span className="bg-revision p-1 rounded-sm px-2 text-sm">
|
||||
<span>{caption}:</span>
|
||||
<span className="font-bold"> {text}</span>
|
||||
</span>
|
||||
@@ -326,7 +326,7 @@ const Rollback = ({
|
||||
const [showRollbackDiff, setShowRollbackDiff] = useState(false);
|
||||
const revisionInt = parseInt(revision || "", 10);
|
||||
|
||||
const { mutate: rollbackRelease, isLoading: isRollingBackRelease } =
|
||||
const { mutate: rollbackRelease, isPending: isRollingBackRelease } =
|
||||
useRollbackRelease({
|
||||
onSuccess: () => {
|
||||
navigate(
|
||||
@@ -421,7 +421,7 @@ const Rollback = ({
|
||||
}, [data, isLoading, fetchedDataSuccessfully]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
{isLoading ? (
|
||||
<div className="flex gap-2 text-sm">
|
||||
<Spinner />
|
||||
@@ -454,9 +454,7 @@ const Rollback = ({
|
||||
const Uninstall = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { namespace = "", chart = "" } = useParams();
|
||||
const { data: resources } = useGetResources(namespace, chart, {
|
||||
enabled: isOpen,
|
||||
});
|
||||
const { data: resources } = useGetResources(namespace, chart, isOpen);
|
||||
|
||||
const uninstallMutation = useMutation({
|
||||
mutationKey: ["uninstall", namespace, chart],
|
||||
@@ -497,7 +495,7 @@ const Uninstall = () => {
|
||||
id: "1",
|
||||
callback: uninstallMutation.mutate,
|
||||
variant: ModalButtonStyle.info,
|
||||
isLoading: uninstallMutation.isLoading,
|
||||
isLoading: uninstallMutation.isPending,
|
||||
},
|
||||
]}
|
||||
containerClassNames="w-[800px]"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeEvent, useMemo, useState, useRef, useEffect } from "react";
|
||||
import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js";
|
||||
import { useGetReleaseInfoByType } from "../../API/releases";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router";
|
||||
import useCustomSearchParams from "../../hooks/useCustomSearchParams";
|
||||
|
||||
import parse from "html-react-parser";
|
||||
@@ -134,7 +134,7 @@ function RevisionDiff({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex mb-3 p-2 border border-revision flex-row items-center justify-between w-full bg-white rounded">
|
||||
<div className="flex mb-3 p-2 border border-gray-200 border-revision flex-row items-center justify-between w-full bg-white rounded-sm">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
checked={viewMode === "view"}
|
||||
@@ -186,7 +186,7 @@ function RevisionDiff({
|
||||
<div>
|
||||
Diff with specific revision:
|
||||
<input
|
||||
className="border ml-2 border-gray-500 w-10 p-1 rounded-sm"
|
||||
className="border ml-2 border-gray-500 w-10 p-1 rounded-xs"
|
||||
type="text"
|
||||
value={specificVersion}
|
||||
onChange={(e) => setSpecificVersion(Number(e.target.value))}
|
||||
@@ -201,7 +201,7 @@ function RevisionDiff({
|
||||
type="checkbox"
|
||||
onChange={handleUserDefinedCheckbox}
|
||||
checked={!!userDefinedValue}
|
||||
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
/>
|
||||
<label
|
||||
htmlFor="user-define-only-checkbox"
|
||||
@@ -215,7 +215,9 @@ function RevisionDiff({
|
||||
{isLoading ? <Spinner /> : ""}
|
||||
{viewMode === VIEW_MODE_VIEW_ONLY && content ? (
|
||||
<div className="bg-white overflow-x-auto w-full p-3 relative">
|
||||
<pre className="bg-white rounded font-sf-mono">{parse(content)}</pre>
|
||||
<pre className="bg-white rounded-sm font-sf-mono">
|
||||
{parse(content)}
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router";
|
||||
import hljs from "highlight.js";
|
||||
import { RiExternalLinkLine } from "react-icons/ri";
|
||||
|
||||
@@ -32,19 +32,19 @@ export default function RevisionResource({ isLatest }: Props) {
|
||||
cellPadding={6}
|
||||
className="border-spacing-y-2 font-semibold border-separate w-full text-xs "
|
||||
>
|
||||
<thead className="bg-zinc-200 font-bold h-8 rounded">
|
||||
<thead className="bg-zinc-200 font-bold h-8 rounded-sm">
|
||||
<tr>
|
||||
<td className="pl-6 rounded">RESOURCE TYPE</td>
|
||||
<td className="pl-6 rounded-sm">RESOURCE TYPE</td>
|
||||
<td>NAME</td>
|
||||
<td>STATUS</td>
|
||||
<td>STATUS MESSAGE</td>
|
||||
<td className="rounded"></td>
|
||||
<td className="rounded-sm"></td>
|
||||
</tr>
|
||||
</thead>
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<tbody className="bg-white mt-4 h-8 rounded w-full">
|
||||
<tbody className="bg-white mt-4 h-8 rounded-sm w-full">
|
||||
{resources?.length ? (
|
||||
resources
|
||||
.sort(function (a, b) {
|
||||
@@ -65,7 +65,7 @@ export default function RevisionResource({ isLatest }: Props) {
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<div className="bg-white rounded shadow display-none no-charts mt-3 text-sm p-4">
|
||||
<div className="bg-white rounded-sm shadow-sm display-none no-charts mt-3 text-sm p-4">
|
||||
Looks like you don't have any resources.{" "}
|
||||
<RiExternalLinkLine className="ml-2 text-lg" />
|
||||
</div>
|
||||
@@ -100,11 +100,11 @@ const ResourceRow = ({
|
||||
return (
|
||||
<>
|
||||
<tr className="min-w-[100%] min-h[70px] text-sm py-2">
|
||||
<td className="pl-6 rounded text-sm font-normal w-48">{kind}</td>
|
||||
<td className="pl-6 rounded-sm text-sm font-normal w-48">{kind}</td>
|
||||
<td className="font-bold text-sm w-56">{name}</td>
|
||||
<td>{reason ? <Badge type={badgeType}>{reason}</Badge> : null}</td>
|
||||
<td className="rounded text-gray-100">
|
||||
<div className="flex flex-col space-y-1 flex-start">
|
||||
<td className="rounded-sm text-gray-100">
|
||||
<div className="flex flex-col gap-1 flex-start">
|
||||
{message && (
|
||||
<div className="text-gray-500 font-thin">{message}</div>
|
||||
)}
|
||||
@@ -113,7 +113,7 @@ const ResourceRow = ({
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="rounded">
|
||||
<td className="rounded-sm">
|
||||
{isLatest && reason !== "NotFound" ? (
|
||||
<div className="flex justify-end items-center mr-36">
|
||||
<Button className="px-1 text-xs" onClick={toggleDrawer}>
|
||||
@@ -183,7 +183,7 @@ const DescribeResource = ({
|
||||
<div className="flex items-center gap-4 pr-4">
|
||||
<a
|
||||
href="https://www.komodor.com/helm-dash/?utm_campaign=Helm%20Dashboard%20%7C%20CTA&utm_source=helm-dash&utm_medium=cta&utm_content=helm-dash"
|
||||
className="bg-primary text-white p-1.5 text-sm flex items-center rounded"
|
||||
className="bg-primary text-white p-1.5 text-sm flex items-center rounded-sm"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
@@ -207,7 +207,7 @@ const DescribeResource = ({
|
||||
) : (
|
||||
<div className="h-full overflow-y-auto ">
|
||||
<pre
|
||||
className="bg-white rounded p-4 font-medium text-base font-sf-mono"
|
||||
className="bg-white rounded-sm p-4 font-medium text-base font-sf-mono"
|
||||
style={{ overflow: "unset" }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: yamlFormattedData,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BsArrowDownRight, BsArrowUpRight } from "react-icons/bs";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router";
|
||||
import { compare } from "compare-versions";
|
||||
|
||||
import { ReleaseRevision } from "../../data/types";
|
||||
@@ -40,7 +40,7 @@ export default function RevisionsList({
|
||||
}
|
||||
onClick={() => changeRelease(release.revision)}
|
||||
key={release.revision}
|
||||
className={`flex flex-col border rounded-md mx-5 p-2 gap-4 cursor-pointer ${
|
||||
className={`flex flex-col border border-gray-200 rounded-md mx-5 p-2 gap-4 cursor-pointer ${
|
||||
release.revision === selectedRevision
|
||||
? "border-revision-dark bg-white"
|
||||
: "border-revision-light bg-body-background"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
const useCustomSearchParams = () => {
|
||||
const [search, setSearch] = useSearchParams();
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
} from "react-router";
|
||||
import { useAppContext } from "../context/AppContext";
|
||||
|
||||
const useNavigateWithSearchParams = () => {
|
||||
@@ -19,7 +19,7 @@ const useNavigateWithSearchParams = () => {
|
||||
let prefixedUrl = url;
|
||||
|
||||
if (!clusterMode) {
|
||||
prefixedUrl = `/${encodeURIComponent(context)}${url}`;
|
||||
prefixedUrl = `/${encodeURIComponent(context ?? "")}${url}`;
|
||||
}
|
||||
navigate(`${prefixedUrl}${search}`, ...restArgs);
|
||||
};
|
||||
|
||||
@@ -1,74 +1,117 @@
|
||||
/* 1. Google Fonts */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Inter:wght@400;500&family=Poppins:wght@500;600&family=Roboto+Slab:wght@400;700");
|
||||
/* Google Fonts */
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Inter:wght@400;500&family=Poppins:wght@500;600&family=Roboto+Slab:wght@400;700")
|
||||
layer(base);
|
||||
|
||||
/* 2. Tailwind directives */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
/* Tailwind directives */
|
||||
@import "tailwindcss";
|
||||
|
||||
/* 3. Theme variables (CSS-first approach) */
|
||||
:root {
|
||||
/* Fonts */
|
||||
--font-roboto: "Roboto", serif;
|
||||
--font-roboto-slab: "Roboto Slab", serif;
|
||||
--font-inter: "Inter", serif;
|
||||
--font-poppins: "Poppins", sans-serif;
|
||||
--font-sf-mono:
|
||||
SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
||||
monospace;
|
||||
@plugin 'flowbite/plugin';
|
||||
@source "../node_modules/flowbite";
|
||||
@plugin "flowbite-react/plugin/tailwindcss";
|
||||
@source "../.flowbite-react/class-list.json";
|
||||
|
||||
/* Font weights */
|
||||
--font-weight-thin: 100;
|
||||
--font-weight-hairline: 100;
|
||||
--font-weight-extralight: 200;
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-semibold: 500;
|
||||
--font-weight-extrabold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Colors */
|
||||
--color-body: #3d4048;
|
||||
--color-white: #ffffff;
|
||||
--color-gray-100: #f3f4f6;
|
||||
--color-gray-200: #e5e7eb;
|
||||
--color-gray-300: #d1d5db;
|
||||
--color-cluster-list: #3d4048;
|
||||
--color-dark: #3d4048;
|
||||
--color-tab-color: #3b3d45;
|
||||
--color-primary: #1347ff;
|
||||
--color-secondary: #eceff2;
|
||||
--color-muted: #707583;
|
||||
--color-error-border: #dc3545;
|
||||
--color-error-background: #f9d7da;
|
||||
--color-error-color: #842029;
|
||||
--color-failed: #fc1683;
|
||||
--color-deployed: #1fa470;
|
||||
--color-superseded: #9195a1;
|
||||
--color-pending: #5ab0ff;
|
||||
--color-danger: #ff0072;
|
||||
--color-text-danger: #fc1683;
|
||||
--color-text-warning: #ffc107;
|
||||
--color-text-success: #a4f8d7;
|
||||
--color-upgradable: #0d6efd;
|
||||
--color-warning: #ffa800;
|
||||
--color-success: #00c2ab;
|
||||
--color-border-deployed: #1be99a;
|
||||
--color-revision-light: #dcdddf;
|
||||
--color-revision-dark: #007bff;
|
||||
--color-installed-charts-filter: #dad8ce;
|
||||
--color-dropdown: #e9ecef;
|
||||
--color-chart-values: #eceff2;
|
||||
--color-add-repo: #0b5ed7;
|
||||
--color-link-color: #0d6efd;
|
||||
--color-body-background: #f4f7fa;
|
||||
--color-repository: #d6effe;
|
||||
--color-revision: #d6effe;
|
||||
--color-header-install: #ebefff;
|
||||
--color-upgrade-color: #fc1683;
|
||||
/* Components layer */
|
||||
@utility card {
|
||||
background-color: var(--color-white);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--color-gray-200);
|
||||
box-shadow: var(--shadow-xl);
|
||||
padding: --spacing(12) --spacing(6) --spacing(6);
|
||||
}
|
||||
@utility card-hover {
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: --spacing(6);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
@utility card-active {
|
||||
background-color: var(--color-gray-200);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: --spacing(6);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
@utility card-disabled {
|
||||
background-color: var(--color-gray-300);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: --spacing(6);
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
@utility custom-shadow {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
@utility error-dialog {
|
||||
border: var(--color-error-border);
|
||||
background-color: var(--color-error-background);
|
||||
color: var(--color-error-color);
|
||||
}
|
||||
|
||||
/* 4. Base layer */
|
||||
@layer utilities {
|
||||
/* Theme variables (CSS-first approach) */
|
||||
:root {
|
||||
/* Fonts */
|
||||
--font-roboto: "Roboto", serif;
|
||||
--font-roboto-slab: "Roboto Slab", serif;
|
||||
--font-inter: "Inter", serif;
|
||||
--font-poppins: "Poppins", sans-serif;
|
||||
--font-sf-mono:
|
||||
SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
||||
monospace;
|
||||
|
||||
/* Font weights */
|
||||
--font-weight-thin: 100;
|
||||
--font-weight-hairline: 100;
|
||||
--font-weight-extralight: 200;
|
||||
--font-weight-light: 300;
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-semibold: 500;
|
||||
--font-weight-extrabold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Colors */
|
||||
--color-body: #3d4048;
|
||||
--color-white: #ffffff;
|
||||
--color-gray-100: #f3f4f6;
|
||||
--color-gray-200: #e5e7eb;
|
||||
--color-gray-300: #d1d5db;
|
||||
--color-cluster-list: #3d4048;
|
||||
--color-dark: #3d4048;
|
||||
--color-tab-color: #3b3d45;
|
||||
--color-primary: #1347ff;
|
||||
--color-secondary: #eceff2;
|
||||
--color-muted: #707583;
|
||||
--color-error-border: #dc3545;
|
||||
--color-error-background: #f9d7da;
|
||||
--color-error-color: #842029;
|
||||
--color-failed: #fc1683;
|
||||
--color-deployed: #1fa470;
|
||||
--color-superseded: #9195a1;
|
||||
--color-pending: #5ab0ff;
|
||||
--color-danger: #ff0072;
|
||||
--color-text-danger: #fc1683;
|
||||
--color-text-warning: #ffc107;
|
||||
--color-text-success: #a4f8d7;
|
||||
--color-upgradable: #0d6efd;
|
||||
--color-warning: #ffa800;
|
||||
--color-success: #00c2ab;
|
||||
--color-border-deployed: #1be99a;
|
||||
--color-revision-light: #dcdddf;
|
||||
--color-revision-dark: #007bff;
|
||||
--color-installed-charts-filter: #dad8ce;
|
||||
--color-dropdown: #e9ecef;
|
||||
--color-chart-values: #eceff2;
|
||||
--color-add-repo: #0b5ed7;
|
||||
--color-link-color: #0d6efd;
|
||||
--color-body-background: #f4f7fa;
|
||||
--color-repository: #d6effe;
|
||||
--color-revision: #d6effe;
|
||||
--color-header-install: #ebefff;
|
||||
--color-upgrade-color: #fc1683;
|
||||
}
|
||||
}
|
||||
|
||||
/* Base layer */
|
||||
@layer base {
|
||||
body {
|
||||
color: var(--color-body);
|
||||
@@ -82,58 +125,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 5. Components layer */
|
||||
@layer components {
|
||||
.card {
|
||||
background-color: var(--color-white);
|
||||
border-radius: theme("borderRadius.lg");
|
||||
border: 1px solid var(--color-gray-200);
|
||||
box-shadow: theme("boxShadow.xl");
|
||||
padding: theme("spacing.12") theme("spacing.6") theme("spacing.6");
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
background-color: var(--color-gray-100);
|
||||
border-radius: theme("borderRadius.lg");
|
||||
padding: theme("spacing.6");
|
||||
box-shadow: theme("boxShadow.xl");
|
||||
}
|
||||
|
||||
.card-active {
|
||||
background-color: var(--color-gray-200);
|
||||
border-radius: theme("borderRadius.lg");
|
||||
padding: theme("spacing.6");
|
||||
box-shadow: theme("boxShadow.xl");
|
||||
}
|
||||
|
||||
.card-disabled {
|
||||
background-color: var(--color-gray-300);
|
||||
border-radius: theme("borderRadius.lg");
|
||||
padding: theme("spacing.6");
|
||||
box-shadow: theme("boxShadow.xl");
|
||||
}
|
||||
|
||||
.custom-shadow {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.error-dialog {
|
||||
border: var(--color-error-border);
|
||||
background-color: var(--color-error-background);
|
||||
color: var(--color-error-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* 6. Code & pre fonts */
|
||||
/* Code & pre fonts */
|
||||
pre,
|
||||
code {
|
||||
font-family: var(--font-sf-mono);
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
/* 7. Portal positioning */
|
||||
/* Portal positioning */
|
||||
#portal {
|
||||
top: 0;
|
||||
width: 40%;
|
||||
@@ -141,13 +140,13 @@ code {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* 8. Required fields */
|
||||
/* Required fields */
|
||||
.require:after {
|
||||
content: " *";
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* 9. Input focus shadow */
|
||||
/* Input focus shadow */
|
||||
.input-box-shadow:focus {
|
||||
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocation } from "react-router";
|
||||
import LogoHeader from "../assets/logo-header.svg";
|
||||
import DropDown from "../components/common/DropDown";
|
||||
import WatcherIcon from "../assets/k8s-watcher.svg";
|
||||
@@ -13,15 +13,22 @@ import { useGetApplicationStatus } from "../API/other";
|
||||
import LinkWithSearchParams from "../components/LinkWithSearchParams";
|
||||
import apiService from "../API/apiService";
|
||||
import { useAppContext } from "../context/AppContext";
|
||||
import { useEffect, useEffectEvent } from "react";
|
||||
|
||||
export default function Header() {
|
||||
const { clusterMode, setClusterMode } = useAppContext();
|
||||
const { data: statusData } = useGetApplicationStatus({
|
||||
onSuccess: (data) => {
|
||||
setClusterMode(data.ClusterMode);
|
||||
},
|
||||
const { data: statusData, isSuccess } = useGetApplicationStatus();
|
||||
|
||||
const onSuccess = useEffectEvent(() => {
|
||||
setClusterMode(!!statusData?.ClusterMode);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess && statusData) {
|
||||
onSuccess();
|
||||
}
|
||||
}, [isSuccess, statusData]);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const openProjectPage = () => {
|
||||
@@ -46,7 +53,7 @@ export default function Header() {
|
||||
const getBtnStyle = (identifier: string) =>
|
||||
`text-md py-2.5 px-5 ${
|
||||
location.pathname.includes(`/${identifier}`)
|
||||
? " text-primary rounded-sm bg-header-install"
|
||||
? " text-primary rounded-xs bg-header-install"
|
||||
: ""
|
||||
}`;
|
||||
|
||||
@@ -129,7 +136,7 @@ export default function Header() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-16 flex items-center text-sm ">
|
||||
<div className="flex p-1 gap-2 border bottom-gray-200 rounded min-w-max">
|
||||
<div className="flex p-1 gap-2 border bottom-gray-200 rounded-sm min-w-max">
|
||||
<img src={WatcherIcon} width={40} height={40} />
|
||||
<div className="flex flex-col">
|
||||
<a
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "../App.css";
|
||||
import { JSX } from "react";
|
||||
|
||||
function Sidebar(): JSX.Element {
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { ThemeInit } from "../.flowbite-react/init";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<ThemeInit />
|
||||
<App />
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
@@ -2,10 +2,10 @@ import InstalledPackagesHeader from "../components/InstalledPackages/InstalledPa
|
||||
import InstalledPackagesList from "../components/InstalledPackages/InstalledPackagesList";
|
||||
import ClustersList from "../components/ClustersList";
|
||||
import { useGetInstalledReleases } from "../API/releases";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
||||
import Spinner from "../components/Spinner";
|
||||
import useAlertError from "../hooks/useAlertError";
|
||||
import { useParams, useNavigate } from "react-router-dom";
|
||||
import { useParams, useNavigate } from "react-router";
|
||||
import useCustomSearchParams from "../hooks/useCustomSearchParams";
|
||||
import { Release } from "../data/types";
|
||||
|
||||
@@ -27,18 +27,21 @@ function Installed() {
|
||||
|
||||
const [filterKey, setFilterKey] = useState<string>("");
|
||||
const alertError = useAlertError();
|
||||
const { data, isLoading, isRefetching } = useGetInstalledReleases(
|
||||
context ?? "",
|
||||
{
|
||||
retry: false,
|
||||
onError: (e) => {
|
||||
alertError.setShowErrorModal({
|
||||
title: "Failed to get list of charts",
|
||||
msg: (e as Error).message,
|
||||
});
|
||||
},
|
||||
const { data, isLoading, isRefetching, isError, error } =
|
||||
useGetInstalledReleases(context ?? "");
|
||||
|
||||
const onError = useEffectEvent(() => {
|
||||
alertError.setShowErrorModal({
|
||||
title: "Failed to get list of charts",
|
||||
msg: error?.message ?? "",
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) {
|
||||
onError();
|
||||
}
|
||||
);
|
||||
}, [isError]);
|
||||
|
||||
const filteredReleases = useMemo(() => {
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useMemo, useEffect } from "react";
|
||||
import { useMemo, useEffect, useEffectEvent } from "react";
|
||||
|
||||
import RepositoriesList from "../components/repository/RepositoriesList";
|
||||
import RepositoryViewer from "../components/repository/RepositoryViewer";
|
||||
import { Repository } from "../data/types";
|
||||
import { useGetRepositories } from "../API/repositories";
|
||||
import { HelmRepositories } from "../API/interfaces";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router";
|
||||
import { useAppContext } from "../context/AppContext";
|
||||
import useNavigateWithSearchParams from "../hooks/useNavigateWithSearchParams";
|
||||
|
||||
@@ -34,16 +33,25 @@ function RepositoryPage() {
|
||||
}
|
||||
}, [selectedRepo, repoFromParams, context, navigate]);
|
||||
|
||||
const { data: repositories = [] } = useGetRepositories({
|
||||
onSuccess: (data: HelmRepositories) => {
|
||||
const sortedData = data?.sort((a, b) => a.name.localeCompare(b.name));
|
||||
const { data: repositories = [], isSuccess } = useGetRepositories();
|
||||
|
||||
if (sortedData && sortedData.length > 0 && !repoFromParams) {
|
||||
handleRepositoryChanged(sortedData[0]);
|
||||
}
|
||||
},
|
||||
const onSuccess = useEffectEvent(() => {
|
||||
// TODO should we passe sorted to RepositoriesList as in ClustersList?
|
||||
const sortedData = [...repositories]?.sort((a, b) =>
|
||||
a.name.localeCompare(b.name)
|
||||
);
|
||||
|
||||
if (sortedData && sortedData.length > 0 && !repoFromParams) {
|
||||
handleRepositoryChanged(sortedData[0]);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (repositories.length && isSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}, [repositories, isSuccess]);
|
||||
|
||||
const selectedRepository = useMemo(() => {
|
||||
if (repoFromParams) {
|
||||
return repositories?.find((repo) => repo.name === repoFromParams);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useMemo } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useParams } from "react-router";
|
||||
import RevisionDetails from "../components/revision/RevisionDetails";
|
||||
import RevisionsList from "../components/revision/RevisionsList";
|
||||
import { ReleaseRevision } from "../data/types";
|
||||
import { Release, ReleaseRevision } from "../data/types";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import apiService from "../API/apiService";
|
||||
import Spinner from "../components/Spinner";
|
||||
@@ -15,18 +15,15 @@ function Revision() {
|
||||
|
||||
const selectedRevision = revision ? parseInt(revision, 10) : 0;
|
||||
|
||||
const { data: releaseRevisions, isLoading: isLoadingHistory } = useQuery<
|
||||
ReleaseRevision[]
|
||||
>({
|
||||
//eslint-ignore
|
||||
//@ts-ignore
|
||||
queryKey: ["releasesHistory", restParams],
|
||||
queryFn: apiService.getReleasesHistory,
|
||||
});
|
||||
const { data: releaseRevisions = [], isLoading: isLoadingHistory } = useQuery(
|
||||
{
|
||||
queryKey: ["releasesHistory", restParams],
|
||||
queryFn: apiService.getReleasesHistory,
|
||||
}
|
||||
);
|
||||
|
||||
const latestRevision = useMemo(
|
||||
() =>
|
||||
Array.isArray(releaseRevisions) &&
|
||||
releaseRevisions.reduce((max, revisionData) => {
|
||||
return Math.max(max, revisionData.revision);
|
||||
}, Number.MIN_SAFE_INTEGER),
|
||||
@@ -40,7 +37,7 @@ function Revision() {
|
||||
|
||||
const selectedRelease = useMemo(() => {
|
||||
if (selectedRevision && releaseRevisions) {
|
||||
return (releaseRevisions as ReleaseRevision[]).find(
|
||||
return releaseRevisions.find(
|
||||
(r: ReleaseRevision) => r.revision === selectedRevision
|
||||
);
|
||||
}
|
||||
@@ -70,14 +67,10 @@ function Revision() {
|
||||
</div>
|
||||
) : selectedRelease ? (
|
||||
<RevisionDetails
|
||||
//@ts-ignore
|
||||
release={selectedRelease}
|
||||
installedRevision={
|
||||
//@ts-ignore
|
||||
releaseRevisions?.[0] as ReleaseRevision
|
||||
}
|
||||
release={selectedRelease as Release} // TODO fix it
|
||||
installedRevision={releaseRevisions?.[0]}
|
||||
isLatest={selectedRelease.revision === latestRevision}
|
||||
latestRevision={latestRevision.revision}
|
||||
latestRevision={latestRevision}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -88,12 +81,12 @@ function Revision() {
|
||||
const RevisionSidebarSkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
<div className="border border-gray-200 rounded-md mx-5 p-2 gap-4 animate-pulse h-[74px] w-[88%] bg-gray-100" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DateTime, type DurationLikeObject } from "luxon";
|
||||
import { DateTime, DateTimeMaybeValid, type DurationLikeObject } from "luxon";
|
||||
import { ReleaseRevision } from "./data/types";
|
||||
|
||||
export function getAge(obj1: ReleaseRevision, obj2?: ReleaseRevision) {
|
||||
const date = DateTime.fromISO(obj1.updated);
|
||||
let dateNext = DateTime.now();
|
||||
let dateNext: DateTimeMaybeValid = DateTime.now();
|
||||
if (obj2) {
|
||||
dateNext = DateTime.fromISO(obj2.updated);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
],
|
||||
};
|
||||
@@ -10,7 +10,7 @@
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy';
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
@@ -8,38 +9,36 @@ export default defineConfig(({ mode }) => {
|
||||
return {
|
||||
plugins: [
|
||||
react(),
|
||||
tailwindcss(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: 'public/analytics.js',
|
||||
src: "public/analytics.js",
|
||||
dest: "assets/",
|
||||
},
|
||||
{
|
||||
src: 'public/openapi.json',
|
||||
src: "public/openapi.json",
|
||||
dest: "assets/",
|
||||
},
|
||||
{
|
||||
src: 'public/logo.svg',
|
||||
src: "public/logo.svg",
|
||||
dest: "assets/",
|
||||
},
|
||||
]
|
||||
})
|
||||
],
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
assetsDir: "./assets/",
|
||||
outDir: "../pkg/frontend/dist",
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
react: ['react', 'react-dom', 'react-router-dom'],
|
||||
vendors: ['luxon','highlight.js','diff2html','swagger-ui-react']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
css: {
|
||||
postcss: './postcss.config.cjs'
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
react: ["react", "react-dom", "react-router"],
|
||||
vendors: ["luxon", "highlight.js", "diff2html", "swagger-ui-react"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user