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