From 190a9ee213c7c1be89fb941d7101a6c10a1cf189 Mon Sep 17 00:00:00 2001 From: Nir Parisian Date: Wed, 26 Jul 2023 23:21:24 +0300 Subject: [PATCH] prettier fix --- dashboard/.prettierrc.yaml | 4 +- dashboard/src/API/apiService.ts | 268 ++-- dashboard/src/API/interfaces.ts | 90 +- dashboard/src/API/k8s.ts | 88 +- dashboard/src/API/other.ts | 46 +- dashboard/src/API/releases.ts | 518 ++++--- dashboard/src/API/repositories.ts | 194 ++- dashboard/src/API/scanners.ts | 72 +- dashboard/src/App.css | 102 +- dashboard/src/App.tsx | 149 +- dashboard/src/components/Badge.stories.tsx | 26 +- dashboard/src/components/Badge.tsx | 80 +- dashboard/src/components/Button.stories.tsx | 40 +- dashboard/src/components/Button.tsx | 28 +- .../src/components/ClustersList.stories.tsx | 32 +- dashboard/src/components/ClustersList.tsx | 260 ++-- .../InstalledPackages/HealthStatus.tsx | 58 +- .../InstalledPackageCard.stories.tsx | 52 +- .../InstalledPackageCard.tsx | 267 ++-- .../InstalledPackagesHeader.stories.tsx | 90 +- .../InstalledPackagesHeader.tsx | 82 +- .../InstalledPackagesList.stories.tsx | 90 +- .../InstalledPackagesList.tsx | 32 +- .../src/components/LinkWithSearchParams.tsx | 18 +- .../src/components/SelectMenu.stories.tsx | 40 +- dashboard/src/components/SelectMenu.tsx | 62 +- .../src/components/ShutDownButton.stories.tsx | 16 +- dashboard/src/components/ShutDownButton.tsx | 48 +- dashboard/src/components/Spinner/index.tsx | 40 +- dashboard/src/components/Tabs.stories.tsx | 44 +- dashboard/src/components/Tabs.tsx | 58 +- dashboard/src/components/TabsBar.stories.tsx | 46 +- dashboard/src/components/TabsBar.tsx | 48 +- .../src/components/TextInput.stories.tsx | 28 +- dashboard/src/components/TextInput.tsx | 42 +- dashboard/src/components/Tooltip.tsx | 70 +- .../src/components/Troubleshoot.stories.tsx | 8 +- dashboard/src/components/Troubleshoot.tsx | 30 +- .../components/common/DropDown.stories.tsx | 32 +- dashboard/src/components/common/DropDown.tsx | 176 ++- .../components/common/StatusLabel.stories.tsx | 26 +- .../src/components/common/StatusLabel.tsx | 54 +- .../modal/AddRepositoryModal.stories.tsx | 20 +- .../components/modal/AddRepositoryModal.tsx | 296 ++-- .../components/modal/ErrorModal.stories.tsx | 30 +- dashboard/src/components/modal/ErrorModal.tsx | 110 +- .../src/components/modal/GlobalErrorModal.tsx | 116 +- .../modal/InstallChartModal/ChartValues.tsx | 68 +- .../InstallChartModal/GeneralDetails.tsx | 84 +- .../InstallChartModal/InstallChartModal.tsx | 688 +++++---- .../modal/InstallChartModal/ManifestDiff.tsx | 102 +- .../InstallChartModal/UserDefinedValues.tsx | 70 +- .../InstallChartModal/VersionToInstall.tsx | 201 ++- .../src/components/modal/Modal.stories.tsx | 154 +- dashboard/src/components/modal/Modal.tsx | 284 ++-- .../repository/ChartViewer.stories.tsx | 28 +- .../src/components/repository/ChartViewer.tsx | 94 +- .../repository/RepositoriesList.stories.tsx | 30 +- .../repository/RepositoriesList.tsx | 153 +- .../repository/RepositoryViewer.stories.tsx | 18 +- .../repository/RepositoryViewer.tsx | 273 ++-- .../components/revision/RevisionDetails.tsx | 987 ++++++------ .../src/components/revision/RevisionDiff.tsx | 422 +++--- .../components/revision/RevisionResource.tsx | 411 +++-- .../src/components/revision/RevisionsList.tsx | 193 ++- dashboard/src/context/AppContext.tsx | 50 +- dashboard/src/context/ErrorModalContext.tsx | 18 +- dashboard/src/data/types.ts | 184 +-- dashboard/src/hooks/useAlertError.ts | 10 +- dashboard/src/hooks/useCustomSearchParams.ts | 52 +- .../src/hooks/useNavigateWithSearchParams.ts | 14 +- dashboard/src/index.css | 88 +- dashboard/src/layout/Header.tsx | 310 ++-- dashboard/src/layout/Sidebar.tsx | 42 +- dashboard/src/main.tsx | 16 +- dashboard/src/pages/DocsPage.tsx | 1333 ++++++++--------- dashboard/src/pages/Installed.tsx | 154 +- dashboard/src/pages/NotFound.tsx | 22 +- dashboard/src/pages/Repository.tsx | 110 +- dashboard/src/pages/Revision.tsx | 168 +-- dashboard/src/stories/Button.stories.tsx | 32 +- dashboard/src/stories/Button.tsx | 86 +- dashboard/src/stories/Header.stories.tsx | 24 +- dashboard/src/stories/Header.tsx | 124 +- .../src/stories/Introduction.stories.mdx | 192 ++- dashboard/src/stories/Page.stories.tsx | 26 +- dashboard/src/stories/Page.tsx | 167 +-- dashboard/src/stories/button.css | 36 +- dashboard/src/stories/header.css | 36 +- dashboard/src/stories/page.css | 80 +- dashboard/src/timeUtils.ts | 46 +- dashboard/src/utils.ts | 68 +- 92 files changed, 5834 insertions(+), 6040 deletions(-) diff --git a/dashboard/.prettierrc.yaml b/dashboard/.prettierrc.yaml index 6e8842f0..24ae9a99 100644 --- a/dashboard/.prettierrc.yaml +++ b/dashboard/.prettierrc.yaml @@ -1,4 +1,4 @@ trailingComma: "es5" -tabWidth: 4 +tabWidth: 2 semi: false -singleQuote: true +singleQuote: false diff --git a/dashboard/src/API/apiService.ts b/dashboard/src/API/apiService.ts index 02d9534f..02706a70 100644 --- a/dashboard/src/API/apiService.ts +++ b/dashboard/src/API/apiService.ts @@ -1,144 +1,140 @@ import { - Chart, - ChartVersion, - Release, - ReleaseHealthStatus, - ReleaseRevision, - Repository, -} from '../data/types' -import { QueryFunctionContext } from '@tanstack/react-query' + Chart, + ChartVersion, + Release, + ReleaseHealthStatus, + ReleaseRevision, + Repository, +} from "../data/types" +import { QueryFunctionContext } from "@tanstack/react-query" interface ClustersResponse { - AuthInfo: string - Cluster: string - IsCurrent: boolean - Name: string - Namespace: string + AuthInfo: string + Cluster: string + IsCurrent: boolean + Name: string + Namespace: string } class ApiService { - currentCluster = '' - constructor(protected readonly isMockMode: boolean = false) {} - - setCluster = (cluster: string) => { - this.currentCluster = cluster - } - - public fetchWithDefaults = async (url: string, options?: RequestInit) => { - if (this.currentCluster) { - const headers = new Headers(options?.headers) - if (!headers.has('X-Kubecontext')) { - headers.set('X-Kubecontext', this.currentCluster) - } - return fetch(url, { ...options, headers }) - } - return fetch(url, options) - } - getToolVersion = async () => { - const response = await fetch(`/status`) - const data = await response.json() - return data - } - - getRepositoryLatestVersion = async (repositoryName: string) => { - const response = await this.fetchWithDefaults( - `/api/helm/repositories/latestver?name=${repositoryName}` - ) - const data = await response.json() - return data - } - - getInstalledReleases = async () => { - const response = await this.fetchWithDefaults(`/api/helm/releases`) - const data = await response.json() - return data - } - - getClusters = async () => { - const response = await fetch(`/api/k8s/contexts`) - const data = (await response.json()) as ClustersResponse[] - return data - } - - getNamespaces = async () => { - const response = await this.fetchWithDefaults( - `/api/k8s/namespaces/list` - ) - const data = await response.json() - return data - } - - getRepositories = async () => { - const response = await this.fetchWithDefaults(`/api/helm/repositories`) - const data = await response.json() - return data - } - - getRepositoryCharts = async ({ - queryKey, - }: QueryFunctionContext) => { - const [_, repository] = queryKey - const response = await this.fetchWithDefaults( - `/api/helm/repositories/${repository}` - ) - const data = await response.json() - return data - } - - getChartVersions = async ({ - queryKey, - }: QueryFunctionContext) => { - const [_, chart] = queryKey - - const response = await this.fetchWithDefaults( - `/api/helm/repositories/versions?name=${chart.name}` - ) - const data = await response.json() - return data - } - - getResourceStatus = async ({ - release, - }: { - release: Release - }): Promise => { - if (!release) return null - - const response = await this.fetchWithDefaults( - `/api/helm/releases/${release.namespace}/${release.name}/resources?health=true` - ) - const data = await response.json() - return data - } - - getReleasesHistory = async ({ - queryKey, - }: QueryFunctionContext): Promise< - ReleaseRevision[] - > => { - const [_, params] = queryKey - - if (!params.namespace || !params.chart) return [] - - const response = await this.fetchWithDefaults( - `/api/helm/releases/${params.namespace}/${params.chart}/history` - ) - const data = await response.json() - - return data - } - - getValues = async ({ queryKey }: any) => { - const [_, params] = queryKey - const { namespace, chart, version } = params - - if (!namespace || !chart || !chart.name || version === undefined) - return Promise.reject(new Error('missing parameters')) - - const url = `/api/helm/repositories/values?chart=${namespace}/${chart.name}&version=${version}` - const response = await this.fetchWithDefaults(url) - const data = await response.text() - - return data + currentCluster = "" + constructor(protected readonly isMockMode: boolean = false) {} + + setCluster = (cluster: string) => { + this.currentCluster = cluster + } + + public fetchWithDefaults = async (url: string, options?: RequestInit) => { + if (this.currentCluster) { + const headers = new Headers(options?.headers) + if (!headers.has("X-Kubecontext")) { + headers.set("X-Kubecontext", this.currentCluster) + } + return fetch(url, { ...options, headers }) } + return fetch(url, options) + } + getToolVersion = async () => { + const response = await fetch(`/status`) + const data = await response.json() + return data + } + + getRepositoryLatestVersion = async (repositoryName: string) => { + const response = await this.fetchWithDefaults( + `/api/helm/repositories/latestver?name=${repositoryName}` + ) + const data = await response.json() + return data + } + + getInstalledReleases = async () => { + const response = await this.fetchWithDefaults(`/api/helm/releases`) + const data = await response.json() + return data + } + + getClusters = async () => { + const response = await fetch(`/api/k8s/contexts`) + const data = (await response.json()) as ClustersResponse[] + return data + } + + getNamespaces = async () => { + const response = await this.fetchWithDefaults(`/api/k8s/namespaces/list`) + const data = await response.json() + return data + } + + getRepositories = async () => { + const response = await this.fetchWithDefaults(`/api/helm/repositories`) + const data = await response.json() + return data + } + + getRepositoryCharts = async ({ + queryKey, + }: QueryFunctionContext) => { + const [_, repository] = queryKey + const response = await this.fetchWithDefaults( + `/api/helm/repositories/${repository}` + ) + const data = await response.json() + return data + } + + getChartVersions = async ({ + queryKey, + }: QueryFunctionContext) => { + const [_, chart] = queryKey + + const response = await this.fetchWithDefaults( + `/api/helm/repositories/versions?name=${chart.name}` + ) + const data = await response.json() + return data + } + + getResourceStatus = async ({ + release, + }: { + release: Release + }): Promise => { + if (!release) return null + + const response = await this.fetchWithDefaults( + `/api/helm/releases/${release.namespace}/${release.name}/resources?health=true` + ) + const data = await response.json() + return data + } + + getReleasesHistory = async ({ + queryKey, + }: QueryFunctionContext): Promise => { + const [_, params] = queryKey + + if (!params.namespace || !params.chart) return [] + + const response = await this.fetchWithDefaults( + `/api/helm/releases/${params.namespace}/${params.chart}/history` + ) + const data = await response.json() + + return data + } + + getValues = async ({ queryKey }: any) => { + const [_, params] = queryKey + const { namespace, chart, version } = params + + if (!namespace || !chart || !chart.name || version === undefined) + return Promise.reject(new Error("missing parameters")) + + const url = `/api/helm/repositories/values?chart=${namespace}/${chart.name}&version=${version}` + const response = await this.fetchWithDefaults(url) + const data = await response.text() + + return data + } } const apiService = new ApiService() diff --git a/dashboard/src/API/interfaces.ts b/dashboard/src/API/interfaces.ts index 980eac70..1044d9e8 100644 --- a/dashboard/src/API/interfaces.ts +++ b/dashboard/src/API/interfaces.ts @@ -1,97 +1,97 @@ export interface HelmRepository { - name: string - url: string + name: string + url: string } export interface ChartVersion { - name: string - version: string + name: string + version: string } export interface K8sContext { - name: string + name: string } export interface K8sResource { - kind: string - name: string - namespace: string + kind: string + name: string + namespace: string } export interface Scanner { - id: string - name: string - type: string + id: string + name: string + type: string } export interface ScanResult { - scannerType: string - result: any + scannerType: string + result: any } export interface ScannersList { - scanners: Scanner[] + scanners: Scanner[] } export interface ScanResults { - [scannerType: string]: ScanResult + [scannerType: string]: ScanResult } export interface ApplicationStatus { - Analytics: boolean - CacheHitRatio: number - ClusterMode: boolean - CurVer: string - LatestVer: string + Analytics: boolean + CacheHitRatio: number + ClusterMode: boolean + CurVer: string + LatestVer: string } export interface KubectlContexts { - contexts: string[] + contexts: string[] } export interface K8sResourceList { - items: K8sResource[] + items: K8sResource[] } export type HelmRepositories = Repository[] export interface ChartList { - charts: Chart[] + charts: Chart[] } export interface LatestChartVersion { - name: string - version: string - app_version: string - description: string - installed_namespace: string - installed_name: string - repository: string - urls: string[] - isSuggestedRepo: boolean + name: string + version: string + app_version: string + description: string + installed_namespace: string + installed_name: string + repository: string + urls: string[] + isSuggestedRepo: boolean } export interface ChartVersions { - versions: string[] + versions: string[] } export interface ValuesYamlText { - content: string + content: string } export interface Repository { - name: string - url: string + name: string + url: string } export interface Chart { - name: string - repo: string - version: string - appVersion: string - description: string - created: string - digest: string - urls: string[] - icon: string + name: string + repo: string + version: string + appVersion: string + description: string + created: string + digest: string + urls: string[] + icon: string } diff --git a/dashboard/src/API/k8s.ts b/dashboard/src/API/k8s.ts index 94301b6e..b99d300e 100644 --- a/dashboard/src/API/k8s.ts +++ b/dashboard/src/API/k8s.ts @@ -1,63 +1,63 @@ -import { UseQueryOptions, useQuery } from '@tanstack/react-query' -import { callApi } from './releases' -import { K8sResource, K8sResourceList, KubectlContexts } from './interfaces' +import { UseQueryOptions, useQuery } from "@tanstack/react-query" +import { callApi } from "./releases" +import { K8sResource, K8sResourceList, KubectlContexts } from "./interfaces" // Get list of kubectl contexts configured locally function useGetKubectlContexts(options?: UseQueryOptions) { - return useQuery( - ['k8s', 'contexts'], - () => callApi('/api/k8s/contexts'), - options - ) + return useQuery( + ["k8s", "contexts"], + () => callApi("/api/k8s/contexts"), + options + ) } // Get resources information function useGetK8sResource( - kind: string, - name: string, - namespace: string, - options?: UseQueryOptions + kind: string, + name: string, + namespace: string, + options?: UseQueryOptions ) { - return useQuery( - ['k8s', kind, 'get', name, namespace], - () => - callApi( - `/api/k8s/${kind}/get?name=${name}&namespace=${namespace}` - ), - options - ) + return useQuery( + ["k8s", kind, "get", name, namespace], + () => + callApi( + `/api/k8s/${kind}/get?name=${name}&namespace=${namespace}` + ), + options + ) } // Get list of resources function useGetK8sResourceList( - kind: string, - options?: UseQueryOptions + kind: string, + options?: UseQueryOptions ) { - return useQuery( - ['k8s', kind, 'list'], - () => callApi(`/api/k8s/${kind}/list`), - options - ) + return useQuery( + ["k8s", kind, "list"], + () => callApi(`/api/k8s/${kind}/list`), + options + ) } // Get describe text for kubernetes resource function useGetK8sResourceDescribe( - kind: string, - name: string, - namespace: string, - options?: UseQueryOptions + kind: string, + name: string, + namespace: string, + options?: UseQueryOptions ) { - return useQuery( - ['k8s', kind, 'describe', name, namespace], - () => - callApi( - `/api/k8s/${kind}/describe?name=${name}&namespace=${namespace}`, - { - headers: { - Accept: 'text/plain', - }, - } - ), - options - ) + return useQuery( + ["k8s", kind, "describe", name, namespace], + () => + callApi( + `/api/k8s/${kind}/describe?name=${name}&namespace=${namespace}`, + { + headers: { + Accept: "text/plain", + }, + } + ), + options + ) } diff --git a/dashboard/src/API/other.ts b/dashboard/src/API/other.ts index 4727eb81..435ecedd 100644 --- a/dashboard/src/API/other.ts +++ b/dashboard/src/API/other.ts @@ -1,34 +1,34 @@ import { - UseMutationOptions, - UseQueryOptions, - useMutation, - useQuery, -} from '@tanstack/react-query' -import { callApi } from './releases' -import { ApplicationStatus } from './interfaces' + UseMutationOptions, + UseQueryOptions, + useMutation, + useQuery, +} from "@tanstack/react-query" +import { callApi } from "./releases" +import { ApplicationStatus } from "./interfaces" // Shuts down the Helm Dashboard application export function useShutdownHelmDashboard( - options?: UseMutationOptions + options?: UseMutationOptions ) { - return useMutation( - () => - callApi('/', { - method: 'DELETE', - }), - options - ) + return useMutation( + () => + callApi("/", { + method: "DELETE", + }), + options + ) } // Gets application status export function useGetApplicationStatus( - options?: UseQueryOptions + options?: UseQueryOptions ) { - return useQuery( - ['status'], - () => callApi('/status'), - { - ...options, - } - ) + return useQuery( + ["status"], + () => callApi("/status"), + { + ...options, + } + ) } diff --git a/dashboard/src/API/releases.ts b/dashboard/src/API/releases.ts index 5a327732..0f077228 100644 --- a/dashboard/src/API/releases.ts +++ b/dashboard/src/API/releases.ts @@ -1,84 +1,84 @@ import { - useQuery, - UseQueryOptions, - useMutation, - UseMutationOptions, -} from '@tanstack/react-query' -import { ChartVersion, Release } from '../data/types' -import { LatestChartVersion } from './interfaces' -import ApiService from './apiService' -import apiService from './apiService' -export const HD_RESOURCE_CONDITION_TYPE = 'hdHealth' // it's our custom condition type, only one exists + useQuery, + UseQueryOptions, + useMutation, + UseMutationOptions, +} from "@tanstack/react-query" +import { ChartVersion, Release } from "../data/types" +import { LatestChartVersion } from "./interfaces" +import ApiService from "./apiService" +import apiService from "./apiService" +export const HD_RESOURCE_CONDITION_TYPE = "hdHealth" // it's our custom condition type, only one exists export function useGetInstalledReleases( - context: string, - options?: UseQueryOptions + context: string, + options?: UseQueryOptions ) { - return useQuery( - ['installedReleases', context], - () => - callApi('/api/helm/releases', { - headers: { - 'X-Kubecontext': context, - }, - }), - options - ) + return useQuery( + ["installedReleases", context], + () => + callApi("/api/helm/releases", { + headers: { + "X-Kubecontext": context, + }, + }), + options + ) } // Install new release function useInstallRelease( - options?: UseMutationOptions + options?: UseMutationOptions ) { - return useMutation((request) => { - const formData = new FormData() - Object.entries(request).forEach(([key, value]) => { - if (value !== undefined) { - formData.append(key, value.toString()) - } - }) + return useMutation((request) => { + const formData = new FormData() + Object.entries(request).forEach(([key, value]) => { + if (value !== undefined) { + formData.append(key, value.toString()) + } + }) - return callApi('/api/helm/releases/{ns}', { - method: 'POST', - body: formData, - }) - }, options) + return callApi("/api/helm/releases/{ns}", { + method: "POST", + body: formData, + }) + }, options) } // Upgrade/reconfigure existing release function useUpgradeRelease( - options?: UseMutationOptions + options?: UseMutationOptions ) { - return useMutation((request) => { - const formData = new FormData() - Object.entries(request).forEach(([key, value]) => { - if (value !== undefined) { - formData.append(key, value.toString()) - } - }) + return useMutation((request) => { + const formData = new FormData() + Object.entries(request).forEach(([key, value]) => { + if (value !== undefined) { + formData.append(key, value.toString()) + } + }) - return callApi('/api/helm/releases/{ns}/{name}', { - method: 'POST', - body: formData, - }) - }, options) + return callApi("/api/helm/releases/{ns}/{name}", { + method: "POST", + body: formData, + }) + }, options) } export function useGetReleaseManifest( - ns: string, - name: string, - formData: FormData, - options?: UseQueryOptions + ns: string, + name: string, + formData: FormData, + options?: UseQueryOptions ) { - return useQuery( - ['manifest', ns, name], - () => - callApi(`/api/helm/releases/${ns}/${name}`, { - method: 'post', - body: formData, - }), - options - ) + return useQuery( + ["manifest", ns, name], + () => + callApi(`/api/helm/releases/${ns}/${name}`, { + method: "post", + body: formData, + }), + options + ) } /** @@ -101,274 +101,270 @@ function useGetNotes(ns: string, name: string, revision?: string, revisionDiff?: */ // List of installed k8s resources for this release export function useGetResources( - ns: string, - name: string, - options?: UseQueryOptions + ns: string, + name: string, + options?: UseQueryOptions ) { - const { data, ...rest } = useQuery( - ['resources', ns, name], - () => - callApi( - `/api/helm/releases/${ns}/${name}/resources?health=true` - ), - options - ) + const { data, ...rest } = useQuery( + ["resources", ns, name], + () => + callApi( + `/api/helm/releases/${ns}/${name}/resources?health=true` + ), + options + ) - return { - data: data - ?.map((resource) => ({ - ...resource, - status: { - ...resource.status, - conditions: resource.status.conditions.filter( - (c) => c.type === HD_RESOURCE_CONDITION_TYPE - ), - }, - })) - .sort((a, b) => { - const interestingResources = [ - 'STATEFULSET', - 'DEAMONSET', - 'DEPLOYMENT', - ] - return ( - interestingResources.indexOf(b.kind.toUpperCase()) - - interestingResources.indexOf(a.kind.toUpperCase()) - ) - }), - ...rest, - } + return { + data: data + ?.map((resource) => ({ + ...resource, + status: { + ...resource.status, + conditions: resource.status.conditions.filter( + (c) => c.type === HD_RESOURCE_CONDITION_TYPE + ), + }, + })) + .sort((a, b) => { + const interestingResources = ["STATEFULSET", "DEAMONSET", "DEPLOYMENT"] + return ( + interestingResources.indexOf(b.kind.toUpperCase()) - + interestingResources.indexOf(a.kind.toUpperCase()) + ) + }), + ...rest, + } } export function useGetResourceDescription( - type: string, - ns: string, - name: string, - options?: UseQueryOptions + type: string, + ns: string, + name: string, + options?: UseQueryOptions ) { - return useQuery( - ['describe', type, ns, name], - () => - callApi( - `/api/k8s/${type}/describe?name=${name}&namespace=${ns}`, - { - headers: { 'Content-Type': 'text/plain; charset=utf-8' }, - } - ), - options - ) + return useQuery( + ["describe", type, ns, name], + () => + callApi( + `/api/k8s/${type}/describe?name=${name}&namespace=${ns}`, + { + headers: { "Content-Type": "text/plain; charset=utf-8" }, + } + ), + options + ) } export function useGetLatestVersion( - chartName: string, - options?: UseQueryOptions + chartName: string, + options?: UseQueryOptions ) { - return useQuery( - ['latestver', chartName], - () => - callApi( - `/api/helm/repositories/latestver?name=${chartName}` - ), - options - ) + return useQuery( + ["latestver", chartName], + () => + callApi( + `/api/helm/repositories/latestver?name=${chartName}` + ), + options + ) } export function useGetVersions( - chartName: string, - options?: UseQueryOptions + chartName: string, + options?: UseQueryOptions ) { - return useQuery( - ['versions', chartName], - () => - callApi( - `/api/helm/repositories/versions?name=${chartName}` - ), - options - ) + return useQuery( + ["versions", chartName], + () => + callApi( + `/api/helm/repositories/versions?name=${chartName}` + ), + options + ) } export function useGetReleaseInfoByType( - params: ReleaseInfoParams, - additionalParams = '', - options?: UseQueryOptions + params: ReleaseInfoParams, + additionalParams = "", + options?: UseQueryOptions ) { - const { chart, namespace, tab, revision } = params - return useQuery( - [tab, namespace, chart, revision, additionalParams], - () => - callApi( - `/api/helm/releases/${namespace}/${chart}/${tab}?revision=${revision}${additionalParams}`, - { - headers: { 'Content-Type': 'text/plain; charset=utf-8' }, - } - ), - options - ) + const { chart, namespace, tab, revision } = params + return useQuery( + [tab, namespace, chart, revision, additionalParams], + () => + callApi( + `/api/helm/releases/${namespace}/${chart}/${tab}?revision=${revision}${additionalParams}`, + { + headers: { "Content-Type": "text/plain; charset=utf-8" }, + } + ), + options + ) } export function useGetDiff( - formData: FormData, - options?: UseQueryOptions + formData: FormData, + options?: UseQueryOptions ) { - return useQuery( - ['diff', formData], - () => { - return callApi(`/diff`, { - body: formData, + return useQuery( + ["diff", formData], + () => { + return callApi(`/diff`, { + body: formData, - method: 'POST', - }) - }, - options - ) + method: "POST", + }) + }, + options + ) } // Rollback the release to a previous revision export function useRollbackRelease( - options?: UseMutationOptions< - void, - unknown, - { ns: string; name: string; revision: number } - > + options?: UseMutationOptions< + void, + unknown, + { ns: string; name: string; revision: number } + > ) { - return useMutation< - void, - unknown, - { ns: string; name: string; revision: number } - >(({ ns, name, revision }) => { - const formData = new FormData() - formData.append('revision', revision.toString()) + return useMutation< + void, + unknown, + { ns: string; name: string; revision: number } + >(({ ns, name, revision }) => { + const formData = new FormData() + formData.append("revision", revision.toString()) - return callApi(`/api/helm/releases/${ns}/${name}/rollback`, { - method: 'POST', - body: formData, - }) - }, options) + return callApi(`/api/helm/releases/${ns}/${name}/rollback`, { + method: "POST", + body: formData, + }) + }, options) } // Run the tests on a release export function useTestRelease( - options?: UseMutationOptions + options?: UseMutationOptions ) { - return useMutation( - ({ ns, name }) => { - return callApi(`/api/helm/releases/${ns}/${name}/test`, { - method: 'POST', - }) - }, - options - ) + return useMutation( + ({ ns, name }) => { + return callApi(`/api/helm/releases/${ns}/${name}/test`, { + method: "POST", + }) + }, + options + ) } export function useChartReleaseValues({ - namespace = 'default', - release, - userDefinedValue, - revision, - options, - version, + namespace = "default", + release, + userDefinedValue, + revision, + options, + version, }: { - namespace?: string - release: string - userDefinedValue?: string - revision?: number - version?: string - options?: UseQueryOptions + namespace?: string + release: string + userDefinedValue?: string + revision?: number + version?: string + options?: UseQueryOptions }) { - return useQuery( - ['values', namespace, release, userDefinedValue, version], - () => - callApi( - `/api/helm/releases/${namespace}/${release}/values?${'userDefined=true'}${ - revision ? `&revision=${revision}` : '' - }`, - { - headers: { 'Content-Type': 'text/plain; charset=utf-8' }, - } - ), - options - ) + return useQuery( + ["values", namespace, release, userDefinedValue, version], + () => + callApi( + `/api/helm/releases/${namespace}/${release}/values?${"userDefined=true"}${ + revision ? `&revision=${revision}` : "" + }`, + { + headers: { "Content-Type": "text/plain; charset=utf-8" }, + } + ), + options + ) } // Request objects interface ReleaseInfoParams { - chart?: string - tab: string - namespace?: string - revision?: string + chart?: string + tab: string + namespace?: string + revision?: string } interface InstallReleaseRequest { - name: string - chart: string - version?: string - values?: string - preview?: boolean + name: string + chart: string + version?: string + values?: string + preview?: boolean } interface InstallReleaseRequest { - name: string - chart: string - version?: string - values?: string - preview?: boolean + name: string + chart: string + version?: string + values?: string + preview?: boolean } interface UpgradeReleaseRequest { - name: string - chart: string - version?: string - values?: string - preview?: boolean + name: string + chart: string + version?: string + values?: string + preview?: boolean } export interface StructuredResources { - kind: string - apiVersion: string - metadata: Metadata - spec: Spec - status: Status + kind: string + apiVersion: string + metadata: Metadata + spec: Spec + status: Status } export interface Metadata { - name: string - namespace: string - creationTimestamp: any - labels: any + name: string + namespace: string + creationTimestamp: any + labels: any } export interface Spec { - [key: string]: any + [key: string]: any } export interface Status { - conditions: Condition[] + conditions: Condition[] } export interface Condition { - type: string - status: string - lastProbeTime: any - lastTransitionTime: any - reason: string - message: string + type: string + status: string + lastProbeTime: any + lastTransitionTime: any + reason: string + message: string } export async function callApi( - url: string, - options?: RequestInit + url: string, + options?: RequestInit ): Promise { - const response = await apiService.fetchWithDefaults(url, options) + const response = await apiService.fetchWithDefaults(url, options) - if (!response.ok) { - const error = await response.text() - throw new Error(error) - } + if (!response.ok) { + const error = await response.text() + throw new Error(error) + } - let data - if (!response.headers.get('Content-Type')) { - return {} as T - } else if (response.headers.get('Content-Type')?.includes('text/plain')) { - data = await response.text() - } else { - data = await response.json() - } - return data + let data + if (!response.headers.get("Content-Type")) { + return {} as T + } else if (response.headers.get("Content-Type")?.includes("text/plain")) { + data = await response.text() + } else { + data = await response.json() + } + return data } diff --git a/dashboard/src/API/repositories.ts b/dashboard/src/API/repositories.ts index 8b79ed52..2cdf045d 100644 --- a/dashboard/src/API/repositories.ts +++ b/dashboard/src/API/repositories.ts @@ -1,145 +1,143 @@ import { - UseMutationOptions, - UseQueryOptions, - useMutation, - useQuery, -} from '@tanstack/react-query' -import { callApi } from './releases' + UseMutationOptions, + UseQueryOptions, + useMutation, + useQuery, +} from "@tanstack/react-query" +import { callApi } from "./releases" import { - ChartList, - ChartVersions, - HelmRepositories, - LatestChartVersion, - ValuesYamlText, -} from './interfaces' + ChartList, + ChartVersions, + HelmRepositories, + LatestChartVersion, + ValuesYamlText, +} from "./interfaces" // Get list of Helm repositories export function useGetRepositories( - options?: UseQueryOptions + options?: UseQueryOptions ) { - return useQuery( - ['helm', 'repositories'], - () => callApi('/api/helm/repositories'), - options - ) + return useQuery( + ["helm", "repositories"], + () => callApi("/api/helm/repositories"), + options + ) } // Add new repository function useAddRepository( - options?: UseMutationOptions + options?: UseMutationOptions ) { - return useMutation( - ({ name, url }) => { - const formData = new FormData() - formData.append('name', name) - formData.append('url', url) + return useMutation( + ({ name, url }) => { + const formData = new FormData() + formData.append("name", name) + formData.append("url", url) - return callApi('/api/helm/repositories', { - method: 'POST', - body: formData, - }) - }, - options - ) + return callApi("/api/helm/repositories", { + method: "POST", + body: formData, + }) + }, + options + ) } // Get list of charts in repository function useGetChartsInRepo( - repo: string, - options?: UseQueryOptions + repo: string, + options?: UseQueryOptions ) { - return useQuery( - ['helm', 'repositories', repo], - () => callApi(`/api/helm/repositories/${repo}`), - options - ) + return useQuery( + ["helm", "repositories", repo], + () => callApi(`/api/helm/repositories/${repo}`), + options + ) } // Update repository from remote export function useUpdateRepo( - repo: string, - options?: UseMutationOptions + repo: string, + options?: UseMutationOptions ) { - return useMutation(() => { - return callApi(`/api/helm/repositories/${repo}`, { - method: 'POST', - }) - }, options) + return useMutation(() => { + return callApi(`/api/helm/repositories/${repo}`, { + method: "POST", + }) + }, options) } // Remove repository export function useDeleteRepo( - repo: string, - options?: UseMutationOptions + repo: string, + options?: UseMutationOptions ) { - return useMutation(() => { - return callApi(`/api/helm/repositories/${repo}`, { - method: 'DELETE', - }) - }, options) + return useMutation(() => { + return callApi(`/api/helm/repositories/${repo}`, { + method: "DELETE", + }) + }, options) } // Find the latest available version of specified chart through all the repositories function useGetLatestChartVersion( - name: string, - options?: UseQueryOptions + name: string, + options?: UseQueryOptions ) { - return useQuery( - ['helm', 'repositories', 'latestver', name], - () => - callApi( - `/api/helm/repositories/latestver?name=${name}` - ), - options - ) + return useQuery( + ["helm", "repositories", "latestver", name], + () => + callApi( + `/api/helm/repositories/latestver?name=${name}` + ), + options + ) } // Get the list of versions for specified chart across the repositories function useGetChartVersions( - name: string, - options?: UseQueryOptions + name: string, + options?: UseQueryOptions ) { - return useQuery( - ['helm', 'repositories', 'versions', name], - () => - callApi( - `/api/helm/repositories/versions?name=${name}` - ), - options - ) + return useQuery( + ["helm", "repositories", "versions", name], + () => + callApi(`/api/helm/repositories/versions?name=${name}`), + options + ) } // Get the original values.yaml file for the chart function useGetChartValues( - chart: string, - version: string, - options?: UseQueryOptions + chart: string, + version: string, + options?: UseQueryOptions ) { - return useQuery( - ['helm', 'repositories', 'values', chart, version], - () => - callApi( - `/api/helm/repositories/values?chart=${chart}&version=${version}` - ), - options - ) + return useQuery( + ["helm", "repositories", "values", chart, version], + () => + callApi( + `/api/helm/repositories/values?chart=${chart}&version=${version}` + ), + options + ) } export function useChartRepoValues( - namespace: string, - version: string, - chart: string, - options?: UseQueryOptions + namespace: string, + version: string, + chart: string, + options?: UseQueryOptions ) { - return useQuery( - ['values', namespace, chart], - () => - callApi( - `/api/helm/repositories/values?chart=${chart}&version=${version}`, - { - headers: { 'Content-Type': 'text/plain; charset=utf-8' }, - } - ), - options - ) + return useQuery( + ["values", namespace, chart], + () => + callApi( + `/api/helm/repositories/values?chart=${chart}&version=${version}`, + { + headers: { "Content-Type": "text/plain; charset=utf-8" }, + } + ), + options + ) } diff --git a/dashboard/src/API/scanners.ts b/dashboard/src/API/scanners.ts index 9e059107..2801e899 100644 --- a/dashboard/src/API/scanners.ts +++ b/dashboard/src/API/scanners.ts @@ -1,51 +1,51 @@ import { - UseMutationOptions, - UseQueryOptions, - useMutation, - useQuery, -} from '@tanstack/react-query' -import { callApi } from './releases' -import { ScanResult, ScanResults, ScannersList } from './interfaces' + UseMutationOptions, + UseQueryOptions, + useMutation, + useQuery, +} from "@tanstack/react-query" +import { callApi } from "./releases" +import { ScanResult, ScanResults, ScannersList } from "./interfaces" // Get list of discovered scanners function useGetDiscoveredScanners(options?: UseQueryOptions) { - return useQuery( - ['scanners'], - () => callApi('/api/scanners'), - options - ) + return useQuery( + ["scanners"], + () => callApi("/api/scanners"), + options + ) } // Scan manifests using all applicable scanners function useScanManifests( - manifest: string, - options?: UseMutationOptions + manifest: string, + options?: UseMutationOptions ) { - const formData = new FormData() - formData.append('manifest', manifest) - return useMutation( - () => - callApi('/api/scanners/manifests', { - method: 'POST', - body: formData, - }), - options - ) + const formData = new FormData() + formData.append("manifest", manifest) + return useMutation( + () => + callApi("/api/scanners/manifests", { + method: "POST", + body: formData, + }), + options + ) } // Scan specified k8s resource in cluster function useScanK8sResource( - kind: string, - namespace: string, - name: string, - options?: UseQueryOptions + kind: string, + namespace: string, + name: string, + options?: UseQueryOptions ) { - return useQuery( - ['scanners', 'resource', kind, namespace, name], - () => - callApi( - `/api/scanners/resource/${kind}?namespace=${namespace}&name=${name}` - ), - options - ) + return useQuery( + ["scanners", "resource", kind, namespace, name], + () => + callApi( + `/api/scanners/resource/${kind}?namespace=${namespace}&name=${name}` + ), + options + ) } diff --git a/dashboard/src/App.css b/dashboard/src/App.css index 8c9a918a..7d11607f 100644 --- a/dashboard/src/App.css +++ b/dashboard/src/App.css @@ -1,96 +1,96 @@ .app-header { - display: flex; - justify-content: space-between; - margin: 5px; - padding: 10px; + display: flex; + justify-content: space-between; + margin: 5px; + padding: 10px; } .header-left { - display: flex; - align-items: center; - justify-content: space-evenly; - flex: 0.6; + display: flex; + align-items: center; + justify-content: space-evenly; + flex: 0.6; } .header-items { - display: flex; - flex: 0.8; - justify-content: space-evenly; + display: flex; + flex: 0.8; + justify-content: space-evenly; } .header-right { - display: flex; - align-items: center; - flex: 0.2; - justify-content: space-around; + display: flex; + align-items: center; + flex: 0.2; + justify-content: space-around; } .redirect { - display: flex; - flex: 0.8; + display: flex; + flex: 0.8; } .redirect > img { - margin-right: 5px; + margin-right: 5px; } .signout-btn { - display: flex; - align-items: center; + display: flex; + align-items: center; } .signout-btn:hover { - cursor: pointer; + cursor: pointer; } .signout-btn > span { - font-weight: bolder; - font-size: x-large; - color: gray; + font-weight: bolder; + font-size: x-large; + color: gray; } .card { - display: flex; - height: 100vh; + display: flex; + height: 100vh; } .card-left { - flex: 0.2; - margin-top: 5px; - margin-left: 4px; - margin-right: 4px; + flex: 0.2; + margin-top: 5px; + margin-left: 4px; + margin-right: 4px; } .card-left > h2, form { - margin-bottom: 10px; + margin-bottom: 10px; } .btn { - margin-bottom: 10px; + margin-bottom: 10px; } .card-right { - flex: 0.8; - margin-top: 5px; - margin-left: 4px; - margin-right: 1px; + flex: 0.8; + margin-top: 5px; + margin-left: 4px; + margin-right: 1px; } .card-right-header { - display: flex; - justify-content: space-between; - margin-bottom: 20px; + display: flex; + justify-content: space-between; + margin-bottom: 20px; } .card-right-header-right-btn { - display: flex; - justify-content: space-between; - margin-bottom: 10px; + display: flex; + justify-content: space-between; + margin-bottom: 10px; } .content-header { - display: flex; - justify-content: space-between; - margin-bottom: 10px; + display: flex; + justify-content: space-between; + margin-bottom: 10px; } .title { - flex: 0.2; + flex: 0.2; } .description { - flex: 0.6; + flex: 0.6; } .version { - flex: 0.2; + flex: 0.2; } .charts { - display: flex; - justify-content: space-between; + display: flex; + justify-content: space-between; } .charts > h3 { - flex: 0.2; + flex: 0.2; } diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index 0af895f8..33b6086d 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -1,94 +1,85 @@ -import Header from './layout/Header' -import { HashRouter, Outlet, Route, Routes, useParams } from 'react-router-dom' -import './index.css' -import Installed from './pages/Installed' -import RepositoryPage from './pages/Repository' -import Revision from './pages/Revision' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { useState } from 'react' -import { ErrorAlert, ErrorModalContext } from './context/ErrorModalContext' -import GlobalErrorModal from './components/modal/GlobalErrorModal' -import { AppContextProvider } from './context/AppContext' -import apiService from './API/apiService' -import DocsPage from './pages/DocsPage' +import Header from "./layout/Header" +import { HashRouter, Outlet, Route, Routes, useParams } from "react-router-dom" +import "./index.css" +import Installed from "./pages/Installed" +import RepositoryPage from "./pages/Repository" +import Revision from "./pages/Revision" +import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import { useState } from "react" +import { ErrorAlert, ErrorModalContext } from "./context/ErrorModalContext" +import GlobalErrorModal from "./components/modal/GlobalErrorModal" +import { AppContextProvider } from "./context/AppContext" +import apiService from "./API/apiService" +import DocsPage from "./pages/DocsPage" const queryClient = new QueryClient({ - defaultOptions: { - queries: { - refetchOnWindowFocus: false, - retry: false, - }, + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: false, }, + }, }) const PageLayout = () => { - return ( -
-
-
- -
-
- ) + return ( +
+
+
+ +
+
+ ) } const SyncContext: React.FC = () => { - const { context } = useParams() - if (context) { - apiService.setCluster(context) - } + const { context } = useParams() + if (context) { + apiService.setCluster(context) + } - return + return } export default function App() { - const [shouldShowErrorModal, setShowErrorModal] = useState< - ErrorAlert | undefined - >(undefined) - const value = { shouldShowErrorModal, setShowErrorModal } + const [shouldShowErrorModal, setShowErrorModal] = useState< + ErrorAlert | undefined + >(undefined) + const value = { shouldShowErrorModal, setShowErrorModal } - return ( - - - - - - } /> - }> - } - > - } - /> - } - /> - } - /> - } - /> - } /> - - } /> - - - setShowErrorModal(undefined)} - titleText={shouldShowErrorModal?.title || ''} - contentText={shouldShowErrorModal?.msg || ''} - /> - - - - - ) + return ( + + + + + + } /> + }> + }> + } /> + } + /> + } /> + } + /> + } /> + + } /> + + + setShowErrorModal(undefined)} + titleText={shouldShowErrorModal?.title || ""} + contentText={shouldShowErrorModal?.msg || ""} + /> + + + + + ) } diff --git a/dashboard/src/components/Badge.stories.tsx b/dashboard/src/components/Badge.stories.tsx index 3742ebd8..c5cd1aa4 100644 --- a/dashboard/src/components/Badge.stories.tsx +++ b/dashboard/src/components/Badge.stories.tsx @@ -14,15 +14,15 @@ * @see https://storybook.js.org/docs/react/writing-stories/introduction */ -import React from 'react' -import { ComponentStory, ComponentMeta } from '@storybook/react' -import Badge from './Badge' -import { BadgeProps } from './Badge' +import React from "react" +import { ComponentStory, ComponentMeta } from "@storybook/react" +import Badge from "./Badge" +import { BadgeProps } from "./Badge" // We create a generic template for the component. const Template: ComponentStory = (args: BadgeProps) => ( - + ) // We export the story, and we pass the template to it. For now, // we are only going to use the default story. @@ -30,17 +30,17 @@ export const Default = Template.bind({}) // We set the props for the story. Recall that the props are the same as the // ones in BadgeProps, which we impoted. Default.args = { - type: 'success', - children: 'Success', + type: "success", + children: "Success", } // We set the metadata for the story. // Refer to https://storybook.js.org/docs/react/writing-stories/introduction // for more information. export default { - title: 'Badge', - component: Badge, - args: { - type: 'success', - children: 'Success', - }, + title: "Badge", + component: Badge, + args: { + type: "success", + children: "Success", + }, } diff --git a/dashboard/src/components/Badge.tsx b/dashboard/src/components/Badge.tsx index 02e74188..9f74d16a 100644 --- a/dashboard/src/components/Badge.tsx +++ b/dashboard/src/components/Badge.tsx @@ -18,56 +18,56 @@ * */ -import React from 'react' -export type BadgeCode = 'success' | 'warning' | 'error' | "unknown" +import React from "react" +export type BadgeCode = "success" | "warning" | "error" | "unknown" export const BadgeCodes = Object.freeze({ - ERROR: 'error', - WARNING: 'warning', - SUCCESS: 'success', - UNKNOWN: 'unknown', + ERROR: "error", + WARNING: "warning", + SUCCESS: "success", + UNKNOWN: "unknown", }) export interface BadgeProps { - type: BadgeCode - children: React.ReactNode - additionalClassNames?: string + type: BadgeCode + children: React.ReactNode + additionalClassNames?: string } export default function Badge(props: BadgeProps): JSX.Element { - const colorVariants = { - [BadgeCodes.SUCCESS]: 'bg-text-success text-black-800', - [BadgeCodes.WARNING]: 'bg-text-warning text-white', - [BadgeCodes.ERROR]: 'bg-text-danger text-white', - [BadgeCodes.UNKNOWN]: 'bg-secondary text-danger', - } + const colorVariants = { + [BadgeCodes.SUCCESS]: "bg-text-success text-black-800", + [BadgeCodes.WARNING]: "bg-text-warning text-white", + [BadgeCodes.ERROR]: "bg-text-danger text-white", + [BadgeCodes.UNKNOWN]: "bg-secondary text-danger", + } - const badgeBase = - 'inline-flex items-center px-1 py-1 rounded text-xs font-light' + const badgeBase = + "inline-flex items-center px-1 py-1 rounded text-xs font-light" - const badgeElem = ( - - {props.children} - - ) - return badgeElem + const badgeElem = ( + + {props.children} + + ) + return badgeElem } export const getBadgeType = (status: string): BadgeCode => { - if (status === 'Unknown') { - return BadgeCodes.UNKNOWN - } else if ( - status === 'Healthy' || - status.toLowerCase().includes('exists') || - status === 'available' - ) { - return BadgeCodes.SUCCESS - } else if (status === 'Progressing') { - return BadgeCodes.WARNING - } else { - return BadgeCodes.ERROR - } + if (status === "Unknown") { + return BadgeCodes.UNKNOWN + } else if ( + status === "Healthy" || + status.toLowerCase().includes("exists") || + status === "available" + ) { + return BadgeCodes.SUCCESS + } else if (status === "Progressing") { + return BadgeCodes.WARNING + } else { + return BadgeCodes.ERROR + } } diff --git a/dashboard/src/components/Button.stories.tsx b/dashboard/src/components/Button.stories.tsx index 28538f4e..990dbc31 100644 --- a/dashboard/src/components/Button.stories.tsx +++ b/dashboard/src/components/Button.stories.tsx @@ -1,18 +1,18 @@ // Status.stories.ts|tsx -import React from 'react' +import React from "react" -import { ComponentStory, ComponentMeta, Story } from '@storybook/react' -import Button from './Button' -import { ButtonProps } from './Button' +import { ComponentStory, ComponentMeta, Story } from "@storybook/react" +import Button from "./Button" +import { ButtonProps } from "./Button" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'Button', - component: Button, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "Button", + component: Button, } as ComponentMeta // Recall that Button has 'props' which is of type ButtonProps @@ -21,17 +21,17 @@ export default { // We want to declare default values for the props, so we create a // default args object. const Template: ComponentStory = (args: ButtonProps) => ( - - - ) + return ( + <> + + + ) } diff --git a/dashboard/src/components/ClustersList.stories.tsx b/dashboard/src/components/ClustersList.stories.tsx index 2945eed1..b2a69f98 100644 --- a/dashboard/src/components/ClustersList.stories.tsx +++ b/dashboard/src/components/ClustersList.stories.tsx @@ -1,28 +1,28 @@ // ClustersListBar.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import ClustersList from './ClustersList' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import ClustersList from "./ClustersList" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'ClustersList', - component: ClustersList, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "ClustersList", + component: ClustersList, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = () => ( - { - console.log('onClusterChange called') - }} - selectedCluster={''} - /> + { + console.log("onClusterChange called") + }} + selectedCluster={""} + /> ) export const Default = Template.bind({}) diff --git a/dashboard/src/components/ClustersList.tsx b/dashboard/src/components/ClustersList.tsx index dbde4a93..7876579d 100644 --- a/dashboard/src/components/ClustersList.tsx +++ b/dashboard/src/components/ClustersList.tsx @@ -1,158 +1,148 @@ -import { useMemo } from 'react' -import { Cluster, Release } from '../data/types' -import apiService from '../API/apiService' -import { useQuery } from '@tanstack/react-query' -import useCustomSearchParams from '../hooks/useCustomSearchParams' -import { useAppContext } from '../context/AppContext' +import { useMemo } from "react" +import { Cluster, Release } from "../data/types" +import apiService from "../API/apiService" +import { useQuery } from "@tanstack/react-query" +import useCustomSearchParams from "../hooks/useCustomSearchParams" +import { useAppContext } from "../context/AppContext" type ClustersListProps = { - onClusterChange: (clusterName: string) => void - selectedCluster: string - filteredNamespaces: string[] - installedReleases?: Release[] + onClusterChange: (clusterName: string) => void + selectedCluster: string + filteredNamespaces: string[] + installedReleases?: Release[] } function getCleanClusterName(rawClusterName: string) { - if (rawClusterName.indexOf('arn') === 0) { - // AWS cluster - const clusterSplit = rawClusterName.split(':') - const clusterName = clusterSplit.slice(-1)[0].replace('cluster/', '') - const region = clusterSplit.at(-3) - return region + '/' + clusterName + ' [AWS]' - } + if (rawClusterName.indexOf("arn") === 0) { + // AWS cluster + const clusterSplit = rawClusterName.split(":") + const clusterName = clusterSplit.slice(-1)[0].replace("cluster/", "") + const region = clusterSplit.at(-3) + return region + "/" + clusterName + " [AWS]" + } - if (rawClusterName.indexOf('gke') === 0) { - // GKE cluster - return ( - rawClusterName.split('_').at(-2) + - '/' + - rawClusterName.split('_').at(-1) + - ' [GKE]' - ) - } + if (rawClusterName.indexOf("gke") === 0) { + // GKE cluster + return ( + rawClusterName.split("_").at(-2) + + "/" + + rawClusterName.split("_").at(-1) + + " [GKE]" + ) + } - return rawClusterName + return rawClusterName } function ClustersList({ - installedReleases, - selectedCluster, - filteredNamespaces, - onClusterChange, + installedReleases, + selectedCluster, + filteredNamespaces, + onClusterChange, }: ClustersListProps) { - const { upsertSearchParams, removeSearchParam } = useCustomSearchParams() - const { clusterMode } = useAppContext() + const { upsertSearchParams, removeSearchParam } = useCustomSearchParams() + const { clusterMode } = useAppContext() - const { data: clusters } = useQuery({ - queryKey: ['clusters', selectedCluster], - queryFn: apiService.getClusters, - onSuccess(data) { - const sortedData = data?.sort((a, b) => - getCleanClusterName(a.Name).localeCompare( - getCleanClusterName(b.Name) - ) - ) + const { data: clusters } = useQuery({ + queryKey: ["clusters", selectedCluster], + queryFn: apiService.getClusters, + onSuccess(data) { + const sortedData = data?.sort((a, b) => + getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name)) + ) - if (sortedData && sortedData.length > 0 && !selectedCluster) { - onClusterChange(sortedData[0].Name) - } - }, - }) + if (sortedData && sortedData.length > 0 && !selectedCluster) { + onClusterChange(sortedData[0].Name) + } + }, + }) - const namespaces = useMemo(() => { - const mapNamespaces = new Map() + const namespaces = useMemo(() => { + const mapNamespaces = new Map() - installedReleases?.forEach((release) => { - const amount = mapNamespaces.get(release.namespace) ?? 1 - mapNamespaces.set(release.namespace, amount) - }) + installedReleases?.forEach((release) => { + const amount = mapNamespaces.get(release.namespace) ?? 1 + mapNamespaces.set(release.namespace, amount) + }) - return Array.from(mapNamespaces, ([key, value]) => ({ - id: crypto.randomUUID(), - name: key, - amount: value, - })) - }, [installedReleases]) + return Array.from(mapNamespaces, ([key, value]) => ({ + id: crypto.randomUUID(), + name: key, + amount: value, + })) + }, [installedReleases]) - const onNamespaceChange = (namespace: string) => { - const newSelectedNamespaces = filteredNamespaces?.includes(namespace) - ? filteredNamespaces?.filter((ns) => ns !== namespace) - : [...(filteredNamespaces ?? []), namespace] - removeSearchParam('filteredNamespace') - if (newSelectedNamespaces.length > 0) { - upsertSearchParams( - 'filteredNamespace', - newSelectedNamespaces.map((ns) => ns).join('+') - ) - } + const onNamespaceChange = (namespace: string) => { + const newSelectedNamespaces = filteredNamespaces?.includes(namespace) + ? filteredNamespaces?.filter((ns) => ns !== namespace) + : [...(filteredNamespaces ?? []), namespace] + removeSearchParam("filteredNamespace") + if (newSelectedNamespaces.length > 0) { + upsertSearchParams( + "filteredNamespace", + newSelectedNamespaces.map((ns) => ns).join("+") + ) } + } - return ( -
- {!clusterMode ? ( - <> - - {clusters - ?.sort((a, b) => - getCleanClusterName(a.Name).localeCompare( - getCleanClusterName(b.Name) - ) - ) - ?.map((cluster) => { - return ( - - { - onClusterChange(e.target.value) - }} - type="radio" - id={cluster.Name} - value={cluster.Name} - checked={ - cluster.Name == selectedCluster - } - name="clusters" - /> - - - ) - })} - - ) : null} + return ( +
+ {!clusterMode ? ( + <> + + {clusters + ?.sort((a, b) => + getCleanClusterName(a.Name).localeCompare( + getCleanClusterName(b.Name) + ) + ) + ?.map((cluster) => { + return ( + + { + onClusterChange(e.target.value) + }} + type="radio" + id={cluster.Name} + value={cluster.Name} + checked={cluster.Name == selectedCluster} + name="clusters" + /> + + + ) + })} + + ) : null} - - {namespaces - ?.sort((a, b) => a.name.localeCompare(b.name)) - ?.map((namespace) => ( - - { - onNamespaceChange(event.target.value) - }} - value={namespace.name} - /> - - - ))} -
- ) + + {namespaces + ?.sort((a, b) => a.name.localeCompare(b.name)) + ?.map((namespace) => ( + + { + onNamespaceChange(event.target.value) + }} + value={namespace.name} + /> + + + ))} +
+ ) } export default ClustersList diff --git a/dashboard/src/components/InstalledPackages/HealthStatus.tsx b/dashboard/src/components/InstalledPackages/HealthStatus.tsx index 412319e6..5517af05 100644 --- a/dashboard/src/components/InstalledPackages/HealthStatus.tsx +++ b/dashboard/src/components/InstalledPackages/HealthStatus.tsx @@ -1,40 +1,40 @@ -import { HD_RESOURCE_CONDITION_TYPE } from '../../API/releases' -import { Tooltip } from 'flowbite-react' -import { ReleaseHealthStatus } from '../../data/types' +import { HD_RESOURCE_CONDITION_TYPE } from "../../API/releases" +import { Tooltip } from "flowbite-react" +import { ReleaseHealthStatus } from "../../data/types" interface Props { - statusData: ReleaseHealthStatus[] + statusData: ReleaseHealthStatus[] } const HealthStatus = ({ statusData }: Props) => { - const statuses = statusData.map((item) => { - for (let i = 0; i < item.status.conditions.length; i++) { - const cond = item.status.conditions[i] + const statuses = statusData.map((item) => { + for (let i = 0; i < item.status.conditions.length; i++) { + const cond = item.status.conditions[i] - if (cond.type !== HD_RESOURCE_CONDITION_TYPE) { - continue - } + if (cond.type !== HD_RESOURCE_CONDITION_TYPE) { + continue + } - return ( - - - - ) - } - }) + return ( + + + + ) + } + }) - return
{statuses}
+ return
{statuses}
} export default HealthStatus diff --git a/dashboard/src/components/InstalledPackages/InstalledPackageCard.stories.tsx b/dashboard/src/components/InstalledPackages/InstalledPackageCard.stories.tsx index dfecf02b..188d3692 100644 --- a/dashboard/src/components/InstalledPackages/InstalledPackageCard.stories.tsx +++ b/dashboard/src/components/InstalledPackages/InstalledPackageCard.stories.tsx @@ -1,41 +1,41 @@ // InstalledPackageCard.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import InstalledPackageCard from './InstalledPackageCard' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import InstalledPackageCard from "./InstalledPackageCard" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'InstalledPackageCard', - component: InstalledPackageCard, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "InstalledPackageCard", + component: InstalledPackageCard, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - + ) export const Default = Template.bind({}) Default.args = { - release: { - id: "", - name: "", - namespace: "", - revision: 1, - updated: "", - status: "", - chart: "", - chart_name: "", - chart_ver: "", - app_version: "", - icon: "", - description: "", - has_tests: false, - chartName: "", // duplicated in some cases in the backend, we need to resolve this - chartVersion: "", // duplicated in some cases in the backend, we need to resolve this - }, + release: { + id: "", + name: "", + namespace: "", + revision: 1, + updated: "", + status: "", + chart: "", + chart_name: "", + chart_ver: "", + app_version: "", + icon: "", + description: "", + has_tests: false, + chartName: "", // duplicated in some cases in the backend, we need to resolve this + chartVersion: "", // duplicated in some cases in the backend, we need to resolve this + }, } diff --git a/dashboard/src/components/InstalledPackages/InstalledPackageCard.tsx b/dashboard/src/components/InstalledPackages/InstalledPackageCard.tsx index 23e4f3bc..fc3d6de7 100644 --- a/dashboard/src/components/InstalledPackages/InstalledPackageCard.tsx +++ b/dashboard/src/components/InstalledPackages/InstalledPackageCard.tsx @@ -1,159 +1,154 @@ -import { useState } from 'react' -import { Release } from '../../data/types' -import { BsArrowUpCircleFill, BsPlusCircleFill } from 'react-icons/bs' -import { getAge } from '../../timeUtils' +import { useState } from "react" +import { Release } from "../../data/types" +import { BsArrowUpCircleFill, BsPlusCircleFill } from "react-icons/bs" +import { getAge } from "../../timeUtils" import StatusLabel, { - DeploymentStatus, - getStatusColor, -} from '../common/StatusLabel' -import { useQuery } from '@tanstack/react-query' -import apiService from '../../API/apiService' -import HealthStatus from './HealthStatus' -import HelmGrayIcon from '../../assets/helm-gray-50.svg' -import Spinner from '../Spinner' -import { useGetLatestVersion } from '../../API/releases' -import { isNewerVersion } from '../../utils' -import { LatestChartVersion } from '../../API/interfaces' -import useNavigateWithSearchParams from '../../hooks/useNavigateWithSearchParams' -import { useParams } from 'react-router-dom' + DeploymentStatus, + getStatusColor, +} from "../common/StatusLabel" +import { useQuery } from "@tanstack/react-query" +import apiService from "../../API/apiService" +import HealthStatus from "./HealthStatus" +import HelmGrayIcon from "../../assets/helm-gray-50.svg" +import Spinner from "../Spinner" +import { useGetLatestVersion } from "../../API/releases" +import { isNewerVersion } from "../../utils" +import { LatestChartVersion } from "../../API/interfaces" +import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams" +import { useParams } from "react-router-dom" type InstalledPackageCardProps = { - release: Release + release: Release } export default function InstalledPackageCard({ - release, + release, }: InstalledPackageCardProps) { - const navigate = useNavigateWithSearchParams() + const navigate = useNavigateWithSearchParams() - const { context: selectedCluster } = useParams() - const [isMouseOver, setIsMouseOver] = useState(false) + const { context: selectedCluster } = useParams() + const [isMouseOver, setIsMouseOver] = useState(false) - const { data: latestVersionResult } = useGetLatestVersion( - release.chartName, - { - queryKey: ['chartName', release.chartName], - cacheTime: 0, - } - ) + const { data: latestVersionResult } = useGetLatestVersion(release.chartName, { + queryKey: ["chartName", release.chartName], + cacheTime: 0, + }) + + const { data: statusData } = useQuery({ + queryKey: ["resourceStatus", release], + queryFn: () => apiService.getResourceStatus({ release }), + }) - const { data: statusData } = useQuery({ - queryKey: ['resourceStatus', release], - queryFn: () => apiService.getResourceStatus({ release }), - }) + const latestVersionData: LatestChartVersion | undefined = + latestVersionResult?.[0] - const latestVersionData: LatestChartVersion | undefined = - latestVersionResult?.[0] + const canUpgrade = + !latestVersionData?.version || !release.chartVersion + ? false + : isNewerVersion(release.chartVersion, latestVersionData?.version) - const canUpgrade = - !latestVersionData?.version || !release.chartVersion - ? false - : isNewerVersion(release.chartVersion, latestVersionData?.version) + const installRepoSuggestion = latestVersionData?.isSuggestedRepo + ? latestVersionData.repository + : null - const installRepoSuggestion = latestVersionData?.isSuggestedRepo - ? latestVersionData.repository - : null + const handleMouseOver = () => { + setIsMouseOver(true) + } - const handleMouseOver = () => { - setIsMouseOver(true) - } + const handleMouseOut = () => { + setIsMouseOver(false) + } - const handleMouseOut = () => { - setIsMouseOver(false) - } + const handleOnClick = () => { + const { name, namespace } = release - const handleOnClick = () => { - const { name, namespace } = release + navigate( + `/${selectedCluster}/${namespace}/${name}/installed/revision/${release.revision}`, + { state: release } + ) + } - navigate( - `/${selectedCluster}/${namespace}/${name}/installed/revision/${release.revision}`, - { state: release } - ) - } + const statusColor = getStatusColor(release.status as DeploymentStatus) + const borderLeftColor: { [key: string]: string } = { + [DeploymentStatus.DEPLOYED]: "border-l-[#1BE99A]", + [DeploymentStatus.FAILED]: "border-l-text-danger", + [DeploymentStatus.PENDING]: "border-l-[#5AB0FF]", + } - const statusColor = getStatusColor(release.status as DeploymentStatus) - const borderLeftColor: { [key: string]: string } = { - [DeploymentStatus.DEPLOYED]: 'border-l-[#1BE99A]', - [DeploymentStatus.FAILED]: 'border-l-text-danger', - [DeploymentStatus.PENDING]: 'border-l-[#5AB0FF]', - } + return ( +
+ helm release icon - return ( +
+
+
+ {release.name} +
+
+ +
+
{release.chart}
+
+ #{release.revision} +
+
+ {release.namespace} +
+
{getAge(release)}
+
- helm release icon - -
-
-
- {release.name} -
-
- -
-
{release.chart}
-
- #{release.revision} -
-
- {release.namespace} -
-
- {getAge(release)} -
-
-
-
- {release.description} -
-
- {statusData ? ( - - ) : ( - - )} -
-
- CHART VERSION - {(canUpgrade || installRepoSuggestion) && ( -
- {canUpgrade && !installRepoSuggestion ? ( - <> - - UPGRADE - - ) : ( - <> - - ADD REPO - - )} -
- )} -
-
REVISION
-
NAMESPACE
-
UPDATED
-
-
+
+ {release.description} +
+
+ {statusData ? ( + + ) : ( + + )} +
+
+ CHART VERSION + {(canUpgrade || installRepoSuggestion) && ( +
+ {canUpgrade && !installRepoSuggestion ? ( + <> + + UPGRADE + + ) : ( + <> + + ADD REPO + + )} +
+ )} +
+
REVISION
+
NAMESPACE
+
UPDATED
- ) +
+
+ ) } diff --git a/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.stories.tsx b/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.stories.tsx index 1ab7900b..e87e41df 100644 --- a/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.stories.tsx +++ b/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.stories.tsx @@ -1,60 +1,60 @@ // InstalledPackagesHeader.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import InstalledPackagesHeader from './InstalledPackagesHeader' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import InstalledPackagesHeader from "./InstalledPackagesHeader" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'InstalledPackagesHeader', - component: InstalledPackagesHeader, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "InstalledPackagesHeader", + component: InstalledPackagesHeader, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - + ) export const Default = Template.bind({}) Default.args = { - filteredReleases: [ - { - id: "", - name: "", - namespace: "", - revision: 1, - updated: "", - status: "", - chart: "", - chart_name: "", - chart_ver: "", - app_version: "", - icon: "", - description: "", - has_tests: false, - chartName: "", // duplicated in some cases in the backend, we need to resolve this - chartVersion: "", // duplicated in some cases in the - }, - { - id: "", - name: "", - namespace: "", - revision: 1, - updated: "", - status: "", - chart: "", - chart_name: "", - chart_ver: "", - app_version: "", - icon: "", - description: "", - has_tests: false, - chartName: "", // duplicated in some cases in the backend, we need to resolve this - chartVersion: "", // duplicated in some cases in the - }, - ], + filteredReleases: [ + { + id: "", + name: "", + namespace: "", + revision: 1, + updated: "", + status: "", + chart: "", + chart_name: "", + chart_ver: "", + app_version: "", + icon: "", + description: "", + has_tests: false, + chartName: "", // duplicated in some cases in the backend, we need to resolve this + chartVersion: "", // duplicated in some cases in the + }, + { + id: "", + name: "", + namespace: "", + revision: 1, + updated: "", + status: "", + chart: "", + chart_name: "", + chart_ver: "", + app_version: "", + icon: "", + description: "", + has_tests: false, + chartName: "", // duplicated in some cases in the backend, we need to resolve this + chartVersion: "", // duplicated in some cases in the + }, + ], } diff --git a/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.tsx b/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.tsx index d53162ee..6f72747b 100644 --- a/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.tsx +++ b/dashboard/src/components/InstalledPackages/InstalledPackagesHeader.tsx @@ -1,51 +1,51 @@ -import HeaderLogo from '../../assets/packges-header.svg' -import { Release } from '../../data/types' +import HeaderLogo from "../../assets/packges-header.svg" +import { Release } from "../../data/types" type InstalledPackagesHeaderProps = { - filteredReleases?: Release[] - setFilterKey: React.Dispatch> - isLoading: boolean + filteredReleases?: Release[] + setFilterKey: React.Dispatch> + isLoading: boolean } export default function InstalledPackagesHeader({ - filteredReleases, - setFilterKey, - isLoading, + filteredReleases, + setFilterKey, + isLoading, }: InstalledPackagesHeaderProps) { - const numOfPackages = filteredReleases?.length - const showNoPackageAlert = Boolean( - !isLoading && (numOfPackages == undefined || numOfPackages == 0) - ) - return ( -
-
-
- Helm-DashBoard -

{`Installed Charts (${ - numOfPackages || '0' - })`}

-
+ const numOfPackages = filteredReleases?.length + const showNoPackageAlert = Boolean( + !isLoading && (numOfPackages == undefined || numOfPackages == 0) + ) + return ( +
+
+
+ Helm-DashBoard +

{`Installed Charts (${ + numOfPackages || "0" + })`}

+
-
- setFilterKey(ev.target.value)} - /> -
-
+
+ setFilterKey(ev.target.value)} + /> +
+
- {showNoPackageAlert && ( -
- Looks like you don't have any charts installed. "Repository" - section may be a good place to start. -
- )} + {showNoPackageAlert && ( +
+ Looks like you don't have any charts installed. "Repository" section + may be a good place to start.
- ) + )} +
+ ) } diff --git a/dashboard/src/components/InstalledPackages/InstalledPackagesList.stories.tsx b/dashboard/src/components/InstalledPackages/InstalledPackagesList.stories.tsx index 013927b3..83d56062 100644 --- a/dashboard/src/components/InstalledPackages/InstalledPackagesList.stories.tsx +++ b/dashboard/src/components/InstalledPackages/InstalledPackagesList.stories.tsx @@ -1,60 +1,60 @@ // InstalledPackagesList.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import InstalledPackagesList from './InstalledPackagesList' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import InstalledPackagesList from "./InstalledPackagesList" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'InstalledPackagesList', - component: InstalledPackagesList, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "InstalledPackagesList", + component: InstalledPackagesList, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - + ) export const Default = Template.bind({}) Default.args = { - installedReleases: [ - { - id: "", - name: "", - namespace: "", - revision: 1, - updated: "", - status: "", - chart: "", - chart_name: "", - chart_ver: "", - app_version: "", - icon: "", - description: "", - has_tests: false, - chartName: "", // duplicated in some cases in the backend, we need to resolve this - chartVersion: "", // duplicated in some cases in the - }, - { - id: "", - name: "", - namespace: "", - revision: 1, - updated: "", - status: "", - chart: "", - chart_name: "", - chart_ver: "", - app_version: "", - icon: "", - description: "", - has_tests: false, - chartName: "", // duplicated in some cases in the backend, we need to resolve this - chartVersion: "", // duplicated in some cases in the - }, - ], + installedReleases: [ + { + id: "", + name: "", + namespace: "", + revision: 1, + updated: "", + status: "", + chart: "", + chart_name: "", + chart_ver: "", + app_version: "", + icon: "", + description: "", + has_tests: false, + chartName: "", // duplicated in some cases in the backend, we need to resolve this + chartVersion: "", // duplicated in some cases in the + }, + { + id: "", + name: "", + namespace: "", + revision: 1, + updated: "", + status: "", + chart: "", + chart_name: "", + chart_ver: "", + app_version: "", + icon: "", + description: "", + has_tests: false, + chartName: "", // duplicated in some cases in the backend, we need to resolve this + chartVersion: "", // duplicated in some cases in the + }, + ], } diff --git a/dashboard/src/components/InstalledPackages/InstalledPackagesList.tsx b/dashboard/src/components/InstalledPackages/InstalledPackagesList.tsx index 76533e3e..7be557ed 100644 --- a/dashboard/src/components/InstalledPackages/InstalledPackagesList.tsx +++ b/dashboard/src/components/InstalledPackages/InstalledPackagesList.tsx @@ -1,23 +1,23 @@ -import InstalledPackageCard from './InstalledPackageCard' -import { Release } from '../../data/types' +import InstalledPackageCard from "./InstalledPackageCard" +import { Release } from "../../data/types" type InstalledPackagesListProps = { - filteredReleases: Release[] + filteredReleases: Release[] } export default function InstalledPackagesList({ - filteredReleases, + filteredReleases, }: InstalledPackagesListProps) { - return ( -
- {filteredReleases.map((installedPackage: Release) => { - return ( - - ) - })} -
- ) + return ( +
+ {filteredReleases.map((installedPackage: Release) => { + return ( + + ) + })} +
+ ) } diff --git a/dashboard/src/components/LinkWithSearchParams.tsx b/dashboard/src/components/LinkWithSearchParams.tsx index 2928a53c..da6d3d35 100644 --- a/dashboard/src/components/LinkWithSearchParams.tsx +++ b/dashboard/src/components/LinkWithSearchParams.tsx @@ -1,16 +1,16 @@ -import { NavLink, useLocation, useSearchParams } from 'react-router-dom' +import { NavLink, useLocation, useSearchParams } from "react-router-dom" const LinkWithSearchParams = ({ - to, - ...props + to, + ...props }: { - to: string - end?: boolean - className?: string - children: React.ReactNode + to: string + end?: boolean + className?: string + children: React.ReactNode }) => { - const { search } = useLocation() - return + const { search } = useLocation() + return } export default LinkWithSearchParams diff --git a/dashboard/src/components/SelectMenu.stories.tsx b/dashboard/src/components/SelectMenu.stories.tsx index cf34364e..8576f7fd 100644 --- a/dashboard/src/components/SelectMenu.stories.tsx +++ b/dashboard/src/components/SelectMenu.stories.tsx @@ -5,35 +5,35 @@ * currently there is only the default story. * The default story renders the component with the default props. */ -import React from 'react' -import { ComponentStory, ComponentMeta } from '@storybook/react' -import SelectMenu, { SelectMenuItem } from './SelectMenu' -import { SelectMenuProps } from './SelectMenu' -import { SelectMenuItemProps } from './SelectMenu' +import React from "react" +import { ComponentStory, ComponentMeta } from "@storybook/react" +import SelectMenu, { SelectMenuItem } from "./SelectMenu" +import { SelectMenuProps } from "./SelectMenu" +import { SelectMenuItemProps } from "./SelectMenu" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'SelectMenu', - component: SelectMenu, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "SelectMenu", + component: SelectMenu, } as ComponentMeta //👇 We create a "template" of how args map to rendering const Template: ComponentStory = (args: SelectMenuProps) => ( - + ) export const Default = Template.bind({}) Default.args = { - header: 'Header', - children: [ - , - , - , - ], - selected: 1, - onSelect: (id: number) => console.log(id), + header: "Header", + children: [ + , + , + , + ], + selected: 1, + onSelect: (id: number) => console.log(id), } diff --git a/dashboard/src/components/SelectMenu.tsx b/dashboard/src/components/SelectMenu.tsx index ddeab1ef..3fee583a 100644 --- a/dashboard/src/components/SelectMenu.tsx +++ b/dashboard/src/components/SelectMenu.tsx @@ -25,24 +25,24 @@ * */ -import React from 'react' +import React from "react" // define the SelectMenuItem type: // This is an object with a label and id. // The label is a string, and the id is a number. // The id is used to identify the selected menu item. export interface SelectMenuItemProps { - label: string - id: number + label: string + id: number } // define the SelectMenuProps interface: export interface SelectMenuProps { - header: string - children: React.ReactNode - selected: number - onSelect: (id: number) => void + header: string + children: React.ReactNode + selected: number + onSelect: (id: number) => void } // define the SelectMenu component: @@ -53,32 +53,32 @@ export interface SelectMenuProps { // the onSelect function is passed the id of the selected menu item export function SelectMenuItem({ - label, - id, + label, + id, }: SelectMenuItemProps): JSX.Element { - return ( -
- { - return - }} - /> - -
- ) + return ( +
+ { + return + }} + /> + +
+ ) } export default function SelectMenu(props: SelectMenuProps): JSX.Element { - const { header, children, selected, onSelect } = props - return ( -
-

{header}

-
{children}
-
- ) + const { header, children, selected, onSelect } = props + return ( +
+

{header}

+
{children}
+
+ ) } diff --git a/dashboard/src/components/ShutDownButton.stories.tsx b/dashboard/src/components/ShutDownButton.stories.tsx index 6db1c5d2..28403edf 100644 --- a/dashboard/src/components/ShutDownButton.stories.tsx +++ b/dashboard/src/components/ShutDownButton.stories.tsx @@ -1,16 +1,16 @@ // TabsBar.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import ShutDownButton from './ShutDownButton' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import ShutDownButton from "./ShutDownButton" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'ShutDownButton', - component: ShutDownButton, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "ShutDownButton", + component: ShutDownButton, } as ComponentMeta //👇 We create a “template” of how args map to rendering diff --git a/dashboard/src/components/ShutDownButton.tsx b/dashboard/src/components/ShutDownButton.tsx index 11bfa1c0..9648bd22 100644 --- a/dashboard/src/components/ShutDownButton.tsx +++ b/dashboard/src/components/ShutDownButton.tsx @@ -1,33 +1,33 @@ -import { BsPower } from 'react-icons/bs' +import { BsPower } from "react-icons/bs" -import Modal from './modal/Modal' -import { useShutdownHelmDashboard } from '../API/other' +import Modal from "./modal/Modal" +import { useShutdownHelmDashboard } from "../API/other" function ShutDownButton() { - const { mutate: signOut, status } = useShutdownHelmDashboard() + const { mutate: signOut, status } = useShutdownHelmDashboard() - const handleClick = async () => { - signOut() - } + const handleClick = async () => { + signOut() + } - return ( -
- -

- The Helm Dashboard application has been shut down. You can - now close the browser tab. -

-
+ return ( +
+ +

+ The Helm Dashboard application has been shut down. You can now close + the browser tab. +

+
- -
- ) + +
+ ) } export default ShutDownButton diff --git a/dashboard/src/components/Spinner/index.tsx b/dashboard/src/components/Spinner/index.tsx index 8e82d17b..c0378976 100644 --- a/dashboard/src/components/Spinner/index.tsx +++ b/dashboard/src/components/Spinner/index.tsx @@ -1,22 +1,22 @@ export default function Spinner({ size = 8 }: { size?: number }) { - return ( -
- -
- ) + return ( +
+ +
+ ) } diff --git a/dashboard/src/components/Tabs.stories.tsx b/dashboard/src/components/Tabs.stories.tsx index 757c67d9..2499a045 100644 --- a/dashboard/src/components/Tabs.stories.tsx +++ b/dashboard/src/components/Tabs.stories.tsx @@ -1,16 +1,16 @@ // TabsBar.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import Tabs from './Tabs' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import Tabs from "./Tabs" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'Tabs', - component: Tabs, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "Tabs", + component: Tabs, } as ComponentMeta //👇 We create a “template” of how args map to rendering @@ -19,20 +19,20 @@ const Template: ComponentStory = (args) => export const Default = Template.bind({}) const defaultArgs = { - tabs: [ - { - label: 'tab1', - content:
tab1
, - }, - { - label: 'tab2', - content:
tab2
, - }, - { - label: 'tab3', - content:
tab3
, - }, - ], + tabs: [ + { + label: "tab1", + content:
tab1
, + }, + { + label: "tab2", + content:
tab2
, + }, + { + label: "tab3", + content:
tab3
, + }, + ], } //@ts-ignore diff --git a/dashboard/src/components/Tabs.tsx b/dashboard/src/components/Tabs.tsx index a154c78c..328dbe01 100644 --- a/dashboard/src/components/Tabs.tsx +++ b/dashboard/src/components/Tabs.tsx @@ -1,43 +1,43 @@ -import { ReactNode } from 'react' -import useCustomSearchParams from '../hooks/useCustomSearchParams' +import { ReactNode } from "react" +import useCustomSearchParams from "../hooks/useCustomSearchParams" export interface Tab { - value: string - label: string - content: ReactNode + value: string + label: string + content: ReactNode } interface TabsProps { - tabs: Tab[] - selectedTab: Tab + tabs: Tab[] + selectedTab: Tab } export default function Tabs({ tabs, selectedTab }: TabsProps) { - const { upsertSearchParams } = useCustomSearchParams() + const { upsertSearchParams } = useCustomSearchParams() - const moveTab = (tab: Tab) => { - upsertSearchParams('tab', tab.value) - } + const moveTab = (tab: Tab) => { + upsertSearchParams("tab", tab.value) + } - return ( -
-
- {tabs.map((tab) => ( - - ))} -
-
{selectedTab.content}
-
- ) + onClick={() => moveTab(tab)} + > + {tab.label} + + ))} +
+
{selectedTab.content}
+ + ) } diff --git a/dashboard/src/components/TabsBar.stories.tsx b/dashboard/src/components/TabsBar.stories.tsx index db6ab873..89875c29 100644 --- a/dashboard/src/components/TabsBar.stories.tsx +++ b/dashboard/src/components/TabsBar.stories.tsx @@ -1,16 +1,16 @@ // TabsBar.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import TabsBar from './TabsBar' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import TabsBar from "./TabsBar" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'TabsBar', - component: TabsBar, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "TabsBar", + component: TabsBar, } as ComponentMeta //👇 We create a “template” of how args map to rendering @@ -19,19 +19,19 @@ const Template: ComponentStory = (args) => export const Default = Template.bind({}) Default.args = { - tabs: [ - { - name: 'tab1', - component:
tab1
, - }, - { - name: 'tab2', - component:
tab2
, - }, - { - name: 'tab3', - component:
tab3
, - }, - ], - activeTab: 'tab1', + tabs: [ + { + name: "tab1", + component:
tab1
, + }, + { + name: "tab2", + component:
tab2
, + }, + { + name: "tab3", + component:
tab3
, + }, + ], + activeTab: "tab1", } diff --git a/dashboard/src/components/TabsBar.tsx b/dashboard/src/components/TabsBar.tsx index 543d6dad..3b042bc7 100644 --- a/dashboard/src/components/TabsBar.tsx +++ b/dashboard/src/components/TabsBar.tsx @@ -15,35 +15,35 @@ * */ -import React from 'react' +import React from "react" interface TabsBarProps { - tabs: Array<{ name: string; component: JSX.Element }> - activeTab: string - setActiveTab: (tab: string) => void - setTabContent: (tab: string) => void + tabs: Array<{ name: string; component: JSX.Element }> + activeTab: string + setActiveTab: (tab: string) => void + setTabContent: (tab: string) => void } export default function TabsBar({ - tabs, - activeTab, - setActiveTab, - setTabContent, + tabs, + activeTab, + setActiveTab, + setTabContent, }: TabsBarProps): JSX.Element { - return ( -
- {tabs.map((tab) => ( -
{ - setActiveTab(tab.name) - setTabContent(tab.name) - }} - key={tab.name} - > - {tab.name} -
- ))} + return ( +
+ {tabs.map((tab) => ( +
{ + setActiveTab(tab.name) + setTabContent(tab.name) + }} + key={tab.name} + > + {tab.name}
- ) + ))} +
+ ) } diff --git a/dashboard/src/components/TextInput.stories.tsx b/dashboard/src/components/TextInput.stories.tsx index 6c134eb0..ea61e329 100644 --- a/dashboard/src/components/TextInput.stories.tsx +++ b/dashboard/src/components/TextInput.stories.tsx @@ -4,29 +4,29 @@ * the first story simply renders the component with the default props. */ -import React from 'react' -import { ComponentStory, ComponentMeta } from '@storybook/react' -import TextInput from './TextInput' -import { TextInputProps } from './TextInput' +import React from "react" +import { ComponentStory, ComponentMeta } from "@storybook/react" +import TextInput from "./TextInput" +import { TextInputProps } from "./TextInput" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'TextInput', - component: TextInput, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "TextInput", + component: TextInput, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args: TextInputProps) => ( - + ) export const Default = Template.bind({}) Default.args = { - label: 'Label', - placeholder: 'Placeholder', - isMandatory: false, + label: "Label", + placeholder: "Placeholder", + isMandatory: false, } diff --git a/dashboard/src/components/TextInput.tsx b/dashboard/src/components/TextInput.tsx index ac30f5d3..96860a61 100644 --- a/dashboard/src/components/TextInput.tsx +++ b/dashboard/src/components/TextInput.tsx @@ -13,32 +13,28 @@ * */ -import React from 'react' +import React from "react" export interface TextInputProps { - label: string - placeholder: string - isMandatory?: boolean - onChange: (event: React.ChangeEvent) => void + label: string + placeholder: string + isMandatory?: boolean + onChange: (event: React.ChangeEvent) => void } export default function TextInput(props: TextInputProps): JSX.Element { - return ( -
- - -
- ) + return ( +
+ + +
+ ) } diff --git a/dashboard/src/components/Tooltip.tsx b/dashboard/src/components/Tooltip.tsx index 5383c3a2..750c5d73 100644 --- a/dashboard/src/components/Tooltip.tsx +++ b/dashboard/src/components/Tooltip.tsx @@ -1,41 +1,41 @@ -import { type ReactElement, cloneElement } from 'react' +import { type ReactElement, cloneElement } from "react" export default function Tooltip({ - id, - title, - element, + id, + title, + element, }: { - title: string - id: string - element: ReactElement + title: string + id: string + element: ReactElement }) { - return ( - <> - {cloneElement(element, { 'data-tooltip-target': id })} - + return ( + <> + {cloneElement(element, { "data-tooltip-target": id })} + - - - - ) + + + + ) } diff --git a/dashboard/src/components/Troubleshoot.stories.tsx b/dashboard/src/components/Troubleshoot.stories.tsx index 3da04066..a168ee52 100644 --- a/dashboard/src/components/Troubleshoot.stories.tsx +++ b/dashboard/src/components/Troubleshoot.stories.tsx @@ -1,9 +1,9 @@ -import { Meta, StoryFn } from '@storybook/react' -import { Troubleshoot } from './Troubleshoot' +import { Meta, StoryFn } from "@storybook/react" +import { Troubleshoot } from "./Troubleshoot" export default { - title: 'Troubleshoot', - component: Troubleshoot, + title: "Troubleshoot", + component: Troubleshoot, } as Meta const Template: StoryFn = () => diff --git a/dashboard/src/components/Troubleshoot.tsx b/dashboard/src/components/Troubleshoot.tsx index fffd048b..8f6891cf 100644 --- a/dashboard/src/components/Troubleshoot.tsx +++ b/dashboard/src/components/Troubleshoot.tsx @@ -1,18 +1,18 @@ -import { RiExternalLinkLine } from 'react-icons/ri' +import { RiExternalLinkLine } from "react-icons/ri" export const Troubleshoot = () => { - return ( - - ) + return ( + + ) } diff --git a/dashboard/src/components/common/DropDown.stories.tsx b/dashboard/src/components/common/DropDown.stories.tsx index 8e4024a0..e3f35f86 100644 --- a/dashboard/src/components/common/DropDown.stories.tsx +++ b/dashboard/src/components/common/DropDown.stories.tsx @@ -1,34 +1,34 @@ // DropDown.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import DropDown from './DropDown' -import { BsSlack, BsGithub } from 'react-icons/bs' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import DropDown from "./DropDown" +import { BsSlack, BsGithub } from "react-icons/bs" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'DropDown', - component: DropDown, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "DropDown", + component: DropDown, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - + ) export const Default = Template.bind({}) const onClick = () => { - console.log('drop down clicked') + console.log("drop down clicked") } Default.args = { - items: [ - { id: '1', text: 'Menu Item 1', onClick: onClick, icon: }, - { id: '2 ', isSeparator: true }, - { id: '3', text: 'Menu Item 3', isDisabled: true, icon: }, - ], + items: [ + { id: "1", text: "Menu Item 1", onClick: onClick, icon: }, + { id: "2 ", isSeparator: true }, + { id: "3", text: "Menu Item 3", isDisabled: true, icon: }, + ], } diff --git a/dashboard/src/components/common/DropDown.tsx b/dashboard/src/components/common/DropDown.tsx index b37cb820..cee3778d 100644 --- a/dashboard/src/components/common/DropDown.tsx +++ b/dashboard/src/components/common/DropDown.tsx @@ -1,115 +1,107 @@ -import { ReactNode, useEffect, useRef, useState } from 'react' -import ArrowDownIcon from '../../assets/arrow-down-icon.svg' +import { ReactNode, useEffect, useRef, useState } from "react" +import ArrowDownIcon from "../../assets/arrow-down-icon.svg" export type DropDownItem = { - id: string - text?: string - icon?: ReactNode - onClick?: () => void - isSeparator?: boolean - isDisabled?: boolean + id: string + text?: string + icon?: ReactNode + onClick?: () => void + isSeparator?: boolean + isDisabled?: boolean } export type DropDownProps = { - items: DropDownItem[] + items: DropDownItem[] } type PopupState = { - isOpen: boolean - X: number - Y: number + isOpen: boolean + X: number + Y: number } function DropDown({ items }: DropDownProps) { - const [popupState, setPopupState] = useState({ - isOpen: false, - X: 0, - Y: 0, - }) + const [popupState, setPopupState] = useState({ + isOpen: false, + X: 0, + Y: 0, + }) - const modalRef = useRef(null) + const modalRef = useRef(null) - useEffect(() => { - if (popupState.isOpen) { - document.addEventListener('mousedown', handleClickOutside) - } else { - document.removeEventListener('mousedown', handleClickOutside) - } + useEffect(() => { + if (popupState.isOpen) { + document.addEventListener("mousedown", handleClickOutside) + } else { + document.removeEventListener("mousedown", handleClickOutside) + } - return () => { - document.removeEventListener('mousedown', handleClickOutside) - } - }, [popupState.isOpen]) + return () => { + document.removeEventListener("mousedown", handleClickOutside) + } + }, [popupState.isOpen]) - const handleClickOutside = (event: MouseEvent) => { - if ( - modalRef.current && - !modalRef.current.contains(event.target as Node) - ) { - setPopupState((prev) => ({ - ...prev, - isOpen: false, - })) - } + const handleClickOutside = (event: MouseEvent) => { + if (modalRef.current && !modalRef.current.contains(event.target as Node)) { + setPopupState((prev) => ({ + ...prev, + isOpen: false, + })) } + } - return ( - <> -
- -
- {popupState.isOpen && ( + return ( + <> +
+ +
+ {popupState.isOpen && ( +
+ {items.map((item) => ( + <> + {item.isSeparator ? ( +
+ ) : (
{ + item.onClick?.() + setPopupState((prev) => ({ + ...prev, + isOpen: false, + })) + }} + className={`cursor-pointer font-normal flex items-center gap-2 py-1 pl-3 pr-7 hover:bg-[#E9ECEF] ${ + item.isDisabled + ? "cursor-default hover:bg-transparent text-gray-400" + : "" + }`} > - {items.map((item) => ( - <> - {item.isSeparator ? ( -
- ) : ( -
{ - item.onClick?.() - setPopupState((prev) => ({ - ...prev, - isOpen: false, - })) - }} - className={`cursor-pointer font-normal flex items-center gap-2 py-1 pl-3 pr-7 hover:bg-[#E9ECEF] ${ - item.isDisabled - ? 'cursor-default hover:bg-transparent text-gray-400' - : '' - }`} - > - {item.icon && ( - {item.icon ?? null} - )} - {item.text} -
- )} - - ))} + {item.icon && {item.icon ?? null}} + {item.text}
- )} - - ) + )} + + ))} +
+ )} + + ) } export default DropDown diff --git a/dashboard/src/components/common/StatusLabel.stories.tsx b/dashboard/src/components/common/StatusLabel.stories.tsx index 451078d6..60483d96 100644 --- a/dashboard/src/components/common/StatusLabel.stories.tsx +++ b/dashboard/src/components/common/StatusLabel.stories.tsx @@ -1,39 +1,39 @@ -import { ComponentStory } from '@storybook/react' -import StatusLabel, { DeploymentStatus } from './StatusLabel' +import { ComponentStory } from "@storybook/react" +import StatusLabel, { DeploymentStatus } from "./StatusLabel" export default { - title: 'StatusLabel', - component: StatusLabel, + title: "StatusLabel", + component: StatusLabel, } const Template: ComponentStory = (args) => ( - + ) export const Deployed = Template.bind({}) Deployed.args = { - status: DeploymentStatus.DEPLOYED, - isRollback: false, + status: DeploymentStatus.DEPLOYED, + isRollback: false, } export const Failed = Template.bind({}) Failed.args = { - status: DeploymentStatus.FAILED, - isRollback: false, + status: DeploymentStatus.FAILED, + isRollback: false, } export const Pending = Template.bind({}) Pending.args = { - status: DeploymentStatus.PENDING, - isRollback: false, + status: DeploymentStatus.PENDING, + isRollback: false, } export const Superseded = Template.bind({}) Superseded.args = { - status: DeploymentStatus.SUPERSEDED, - isRollback: false, + status: DeploymentStatus.SUPERSEDED, + isRollback: false, } diff --git a/dashboard/src/components/common/StatusLabel.tsx b/dashboard/src/components/common/StatusLabel.tsx index 8eb3ef1f..630a5d97 100644 --- a/dashboard/src/components/common/StatusLabel.tsx +++ b/dashboard/src/components/common/StatusLabel.tsx @@ -1,42 +1,42 @@ -import { AiOutlineReload } from 'react-icons/ai' +import { AiOutlineReload } from "react-icons/ai" type StatusLabelProps = { - status: string - isRollback?: boolean + status: string + isRollback?: boolean } export enum DeploymentStatus { - DEPLOYED = 'deployed', - FAILED = 'failed', - PENDING = 'pending-install', - SUPERSEDED = 'superseded', + DEPLOYED = "deployed", + FAILED = "failed", + PENDING = "pending-install", + SUPERSEDED = "superseded", } export function getStatusColor(status: DeploymentStatus) { - if (status === DeploymentStatus.DEPLOYED) return 'text-deployed' - if (status === DeploymentStatus.FAILED) return 'text-failed' - if (status === DeploymentStatus.PENDING) return 'text-pending' - else return 'text-superseded' + if (status === DeploymentStatus.DEPLOYED) return "text-deployed" + if (status === DeploymentStatus.FAILED) return "text-failed" + if (status === DeploymentStatus.PENDING) return "text-pending" + else return "text-superseded" } function StatusLabel({ status, isRollback }: StatusLabelProps) { - const statusColor = getStatusColor(status as DeploymentStatus) + const statusColor = getStatusColor(status as DeploymentStatus) - return ( -
- - ● {status.toUpperCase()} - - {isRollback && } -
- ) + return ( +
+ + ● {status.toUpperCase()} + + {isRollback && } +
+ ) } export default StatusLabel diff --git a/dashboard/src/components/modal/AddRepositoryModal.stories.tsx b/dashboard/src/components/modal/AddRepositoryModal.stories.tsx index 6814a3b5..c4fdae90 100644 --- a/dashboard/src/components/modal/AddRepositoryModal.stories.tsx +++ b/dashboard/src/components/modal/AddRepositoryModal.stories.tsx @@ -1,25 +1,25 @@ // Modal.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import AddRepositoryModal from './AddRepositoryModal' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import AddRepositoryModal from "./AddRepositoryModal" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'AddRepositoryModal', - component: AddRepositoryModal, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "AddRepositoryModal", + component: AddRepositoryModal, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - + ) export const Default = Template.bind({}) Default.args = { - isOpen: true, + isOpen: true, } diff --git a/dashboard/src/components/modal/AddRepositoryModal.tsx b/dashboard/src/components/modal/AddRepositoryModal.tsx index 9ce0d3dc..02164d3f 100644 --- a/dashboard/src/components/modal/AddRepositoryModal.tsx +++ b/dashboard/src/components/modal/AddRepositoryModal.tsx @@ -1,166 +1,166 @@ -import React, { useEffect, useState } from 'react' -import Modal from './Modal' -import { callApi } from '../../API/releases' -import Spinner from '../Spinner' -import useAlertError from '../../hooks/useAlertError' -import useCustomSearchParams from '../../hooks/useCustomSearchParams' -import { useAppContext } from '../../context/AppContext' -import { useQueryClient } from '@tanstack/react-query' -import { useNavigate, useParams } from 'react-router-dom' +import React, { useEffect, useState } from "react" +import Modal from "./Modal" +import { callApi } from "../../API/releases" +import Spinner from "../Spinner" +import useAlertError from "../../hooks/useAlertError" +import useCustomSearchParams from "../../hooks/useCustomSearchParams" +import { useAppContext } from "../../context/AppContext" +import { useQueryClient } from "@tanstack/react-query" +import { useNavigate, useParams } from "react-router-dom" interface FormKeys { - name: string - url: string - username: string - password: string + name: string + url: string + username: string + password: string } type AddRepositoryModalProps = { - isOpen: boolean - onClose: () => void + isOpen: boolean + onClose: () => void } function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) { - const [formData, setFormData] = useState({} as FormKeys) - const [isLoading, setIsLoading] = useState(false) - const alertError = useAlertError() - const { searchParamsObject } = useCustomSearchParams() - const { repo_url, repo_name } = searchParamsObject - const { setSelectedRepo } = useAppContext() - const { context } = useParams() - const navigate = useNavigate() - const queryClient = useQueryClient() + const [formData, setFormData] = useState({} as FormKeys) + const [isLoading, setIsLoading] = useState(false) + const alertError = useAlertError() + const { searchParamsObject } = useCustomSearchParams() + const { repo_url, repo_name } = searchParamsObject + const { setSelectedRepo } = useAppContext() + const { context } = useParams() + const navigate = useNavigate() + const queryClient = useQueryClient() - useEffect(() => { - if (!repo_url || !repo_name) return - setFormData({ ...formData, name: repo_name, url: repo_url }) - }, [repo_url, repo_name]) + useEffect(() => { + if (!repo_url || !repo_name) return + setFormData({ ...formData, name: repo_name, url: repo_url }) + }, [repo_url, repo_name]) - const addRepository = () => { - const body = new FormData() - body.append('name', formData.name ?? '') - body.append('url', formData.url ?? '') - body.append('username', formData.username ?? '') - body.append('password', formData.password ?? '') + const addRepository = () => { + const body = new FormData() + body.append("name", formData.name ?? "") + body.append("url", formData.url ?? "") + body.append("username", formData.username ?? "") + body.append("password", formData.password ?? "") - setIsLoading(true) + setIsLoading(true) - callApi('/api/helm/repositories', { - method: 'POST', - body, - }) - .then(() => { - setIsLoading(false) - onClose() + callApi("/api/helm/repositories", { + method: "POST", + body, + }) + .then(() => { + setIsLoading(false) + onClose() - queryClient.invalidateQueries({ - queryKey: ['helm', 'repositories'], - }) - setSelectedRepo(formData.name || '') - navigate(`/${context}/repository/${formData.name}`, { - replace: true, - }) - }) - .catch((error) => { - alertError.setShowErrorModal({ - title: 'Failed to add repo', - msg: error.message, - }) - }) - .finally(() => { - setIsLoading(false) - }) - } + queryClient.invalidateQueries({ + queryKey: ["helm", "repositories"], + }) + setSelectedRepo(formData.name || "") + navigate(`/${context}/repository/${formData.name}`, { + replace: true, + }) + }) + .catch((error) => { + alertError.setShowErrorModal({ + title: "Failed to add repo", + msg: error.message, + }) + }) + .finally(() => { + setIsLoading(false) + }) + } - return ( - - -
+ return ( + + +
+ } + > +
+ + +
+
+ + +
+ + ) } export default AddRepositoryModal diff --git a/dashboard/src/components/modal/ErrorModal.stories.tsx b/dashboard/src/components/modal/ErrorModal.stories.tsx index 0f69a1ba..2e41824b 100644 --- a/dashboard/src/components/modal/ErrorModal.stories.tsx +++ b/dashboard/src/components/modal/ErrorModal.stories.tsx @@ -1,30 +1,30 @@ // Modal.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import ErrorModal from './ErrorModal' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import ErrorModal from "./ErrorModal" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'ErrorModal', - component: ErrorModal, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "ErrorModal", + component: ErrorModal, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - + ) export const Default = Template.bind({}) Default.args = { - onClose: () => { - console.log('on Close clicked') - }, - titleText: 'Failed to get list of charts', - contentText: - 'failed to get list of releases, cause: Kubernetes cluster unreachable: Get "https://kubernetes.docker.internal:6443/version": dial tcp 127.0.0.1:6443: connectex: No connection could be made because the target machine actively refused it.', + onClose: () => { + console.log("on Close clicked") + }, + titleText: "Failed to get list of charts", + contentText: + "failed to get list of releases, cause: Kubernetes cluster unreachable: Get "https://kubernetes.docker.internal:6443/version": dial tcp 127.0.0.1:6443: connectex: No connection could be made because the target machine actively refused it.", } diff --git a/dashboard/src/components/modal/ErrorModal.tsx b/dashboard/src/components/modal/ErrorModal.tsx index 33cfbae7..8acf851c 100644 --- a/dashboard/src/components/modal/ErrorModal.tsx +++ b/dashboard/src/components/modal/ErrorModal.tsx @@ -1,65 +1,63 @@ -import Modal from './Modal' +import Modal from "./Modal" interface ErrorModalProps { - isOpen: boolean - titleText: string - contentText: string - onClose: () => void + isOpen: boolean + titleText: string + contentText: string + onClose: () => void } export default function ErrorModal({ - isOpen, - onClose, - titleText, - contentText, + isOpen, + onClose, + titleText, + contentText, }: ErrorModalProps) { - const ErrorTitle = ( -
-
- - - - {titleText} -
-

-

- ) - - const bottomContent = ( -
- - Hint: Komodor has the same HELM capabilities, with enterprise - features and support.{' '} - - - Sign up for free. - - - -
- ) + const ErrorTitle = ( +
+
+ + + + {titleText} +
+

+

+ ) - return ( - + + Hint: Komodor has the same HELM capabilities, with enterprise features + and support.{" "} + -

{contentText}

-
- ) + Sign up for free. + + +
+ ) + + return ( + +

{contentText}

+
+ ) } diff --git a/dashboard/src/components/modal/GlobalErrorModal.tsx b/dashboard/src/components/modal/GlobalErrorModal.tsx index db389a57..4c88b6ca 100644 --- a/dashboard/src/components/modal/GlobalErrorModal.tsx +++ b/dashboard/src/components/modal/GlobalErrorModal.tsx @@ -1,66 +1,66 @@ -import Modal from './Modal' +import Modal from "./Modal" interface ErrorModalProps { - isOpen: boolean - titleText: string - contentText: string - onClose: () => void + isOpen: boolean + titleText: string + contentText: string + onClose: () => void } export default function GlobalErrorModal({ - isOpen, - onClose, - titleText, - contentText, + isOpen, + onClose, + titleText, + contentText, }: ErrorModalProps) { - const ErrorTitle = ( -
-
- - - - {titleText} -
-

-

- ) - - return ( - - Hint: Komodor has the same HELM capabilities, with - enterprise features and support.{' '} - - Sign up for free. - - - } + const ErrorTitle = ( +
+
+ -

- {contentText} -

- - ) + + + {titleText} +
+

+

+ ) + + return ( + + Hint: Komodor has the same HELM capabilities, with enterprise features + and support.{" "} + + Sign up for free. + + + } + > +

+ {contentText} +

+
+ ) } diff --git a/dashboard/src/components/modal/InstallChartModal/ChartValues.tsx b/dashboard/src/components/modal/InstallChartModal/ChartValues.tsx index 34c48eaa..d2ea1974 100644 --- a/dashboard/src/components/modal/InstallChartModal/ChartValues.tsx +++ b/dashboard/src/components/modal/InstallChartModal/ChartValues.tsx @@ -1,39 +1,39 @@ -import hljs from 'highlight.js' -import Spinner from '../../Spinner' +import hljs from "highlight.js" +import Spinner from "../../Spinner" export const ChartValues = ({ - chartValues, - loading, + chartValues, + loading, }: { - chartValues: string - loading: boolean + chartValues: string + loading: boolean }) => { - return ( -
- -
-                {loading ? (
-                    
-                ) : !chartValues && !loading ? (
-                    'No original values information found'
-                ) : null}
-            
-
- ) + return ( +
+ +
+        {loading ? (
+          
+        ) : !chartValues && !loading ? (
+          "No original values information found"
+        ) : null}
+      
+
+ ) } diff --git a/dashboard/src/components/modal/InstallChartModal/GeneralDetails.tsx b/dashboard/src/components/modal/InstallChartModal/GeneralDetails.tsx index 53fd1096..0da5f1c8 100644 --- a/dashboard/src/components/modal/InstallChartModal/GeneralDetails.tsx +++ b/dashboard/src/components/modal/InstallChartModal/GeneralDetails.tsx @@ -1,49 +1,49 @@ -import { useParams } from 'react-router-dom' +import { useParams } from "react-router-dom" export const GeneralDetails = ({ - releaseName, - namespace = '', - disabled, - onNamespaceInput, - onReleaseNameInput, + releaseName, + namespace = "", + disabled, + onNamespaceInput, + onReleaseNameInput, }: { - releaseName: string - namespace?: string - disabled: boolean + releaseName: string + namespace?: string + disabled: boolean - onNamespaceInput: (namespace: string) => void - onReleaseNameInput: (chartName: string) => void + onNamespaceInput: (namespace: string) => void + onReleaseNameInput: (chartName: string) => void }) => { - const { context } = useParams() - const inputClassName = ` text-lg py-1 px-2 border border-1 border-gray-300 ${ - disabled ? 'bg-gray-200' : 'bg-white ' - } rounded` - return ( -
-
-

Release name:

- onReleaseNameInput(e.target.value)} - > -
-
-

Namespace (optional):

- onNamespaceInput(e.target.value)} - > -
- {context ? ( -
-

Cluster:

-

{context}

-
- ) : null} + const { context } = useParams() + const inputClassName = ` text-lg py-1 px-2 border border-1 border-gray-300 ${ + disabled ? "bg-gray-200" : "bg-white " + } rounded` + return ( +
+
+

Release name:

+ onReleaseNameInput(e.target.value)} + > +
+
+

Namespace (optional):

+ onNamespaceInput(e.target.value)} + > +
+ {context ? ( +
+

Cluster:

+

{context}

- ) + ) : null} +
+ ) } diff --git a/dashboard/src/components/modal/InstallChartModal/InstallChartModal.tsx b/dashboard/src/components/modal/InstallChartModal/InstallChartModal.tsx index 1f840612..04156846 100644 --- a/dashboard/src/components/modal/InstallChartModal/InstallChartModal.tsx +++ b/dashboard/src/components/modal/InstallChartModal/InstallChartModal.tsx @@ -1,390 +1,372 @@ -import { useParams } from 'react-router-dom' -import useAlertError from '../../../hooks/useAlertError' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useParams } from "react-router-dom" +import useAlertError from "../../../hooks/useAlertError" +import { useCallback, useEffect, useMemo, useState } from "react" import { - callApi, - useChartReleaseValues, - useGetVersions, -} from '../../../API/releases' -import Modal, { ModalButtonStyle } from '../Modal' -import { GeneralDetails } from './GeneralDetails' -import { UserDefinedValues } from './UserDefinedValues' -import { ChartValues } from './ChartValues' -import { ManifestDiff } from './ManifestDiff' -import { useMutation } from '@tanstack/react-query' -import { useChartRepoValues } from '../../../API/repositories' -import useNavigateWithSearchParams from '../../../hooks/useNavigateWithSearchParams' -import { VersionToInstall } from './VersionToInstall' -import apiService from '../../../API/apiService' -import { isNewerVersion, isNoneEmptyArray } from '../../../utils' -import useCustomSearchParams from '../../../hooks/useCustomSearchParams' + callApi, + useChartReleaseValues, + useGetVersions, +} from "../../../API/releases" +import Modal, { ModalButtonStyle } from "../Modal" +import { GeneralDetails } from "./GeneralDetails" +import { UserDefinedValues } from "./UserDefinedValues" +import { ChartValues } from "./ChartValues" +import { ManifestDiff } from "./ManifestDiff" +import { useMutation } from "@tanstack/react-query" +import { useChartRepoValues } from "../../../API/repositories" +import useNavigateWithSearchParams from "../../../hooks/useNavigateWithSearchParams" +import { VersionToInstall } from "./VersionToInstall" +import apiService from "../../../API/apiService" +import { isNewerVersion, isNoneEmptyArray } from "../../../utils" +import useCustomSearchParams from "../../../hooks/useCustomSearchParams" interface InstallChartModalProps { - isOpen: boolean - onClose: () => void - chartName: string - currentlyInstalledChartVersion?: string - latestVersion?: string - isUpgrade?: boolean - isInstall?: boolean - latestRevision?: number + isOpen: boolean + onClose: () => void + chartName: string + currentlyInstalledChartVersion?: string + latestVersion?: string + isUpgrade?: boolean + isInstall?: boolean + latestRevision?: number } export const InstallChartModal = ({ - isOpen, - onClose, - chartName, - currentlyInstalledChartVersion, - latestVersion, - isUpgrade = false, - isInstall = false, - latestRevision, + isOpen, + onClose, + chartName, + currentlyInstalledChartVersion, + latestVersion, + isUpgrade = false, + isInstall = false, + latestRevision, }: InstallChartModalProps) => { - const navigate = useNavigateWithSearchParams() - const { setShowErrorModal } = useAlertError() - const [userValues, setUserValues] = useState('') - const [errorMessage, setErrorMessage] = useState('') - const [isLoadingDiff, setIsLoadingDiff] = useState(false) - const [diff, setDiff] = useState('') + const navigate = useNavigateWithSearchParams() + const { setShowErrorModal } = useAlertError() + const [userValues, setUserValues] = useState("") + const [errorMessage, setErrorMessage] = useState("") + const [isLoadingDiff, setIsLoadingDiff] = useState(false) + const [diff, setDiff] = useState("") - const { - namespace: queryNamespace, - chart: _releaseName, - context: selectedCluster, - selectedRepo: currentRepoCtx, - } = useParams() - const { searchParamsObject } = useCustomSearchParams() - const { filteredNamespace } = searchParamsObject - const [namespace, setNamespace] = useState(queryNamespace) - const [releaseName, setReleaseName] = useState( - isInstall ? chartName : _releaseName - ) + const { + namespace: queryNamespace, + chart: _releaseName, + context: selectedCluster, + selectedRepo: currentRepoCtx, + } = useParams() + const { searchParamsObject } = useCustomSearchParams() + const { filteredNamespace } = searchParamsObject + const [namespace, setNamespace] = useState(queryNamespace) + const [releaseName, setReleaseName] = useState( + isInstall ? chartName : _releaseName + ) - const { error: versionsError, data: _versions } = useGetVersions( - chartName, - { - select: (data) => { - return data?.sort((a, b) => - isNewerVersion(a.version, b.version) ? 1 : -1 - ) - }, - onSuccess: (data) => { - const empty = { version: '', repository: '', urls: [] } - if (!isInstall) { - return setSelectedVersionData(data[0] ?? empty) - } - const versionsToRepo = data.filter( - (v) => v.repository === currentRepoCtx - ) - return setSelectedVersionData(versionsToRepo[0] ?? empty) - }, - } - ) + const { error: versionsError, data: _versions } = useGetVersions(chartName, { + select: (data) => { + return data?.sort((a, b) => + isNewerVersion(a.version, b.version) ? 1 : -1 + ) + }, + onSuccess: (data) => { + const empty = { version: "", repository: "", urls: [] } + if (!isInstall) { + return setSelectedVersionData(data[0] ?? empty) + } + const versionsToRepo = data.filter((v) => v.repository === currentRepoCtx) + return setSelectedVersionData(versionsToRepo[0] ?? empty) + }, + }) + + const versions = _versions?.map((v) => ({ + ...v, + isChartVersion: v.version === currentlyInstalledChartVersion, + })) - const versions = _versions?.map((v) => ({ - ...v, - isChartVersion: v.version === currentlyInstalledChartVersion, - })) + latestVersion = latestVersion ?? currentlyInstalledChartVersion // a guard for typescript, latestVersion is always defined + const [selectedVersionData, setSelectedVersionData] = useState<{ + version: string + repository?: string + urls: string[] + }>() - latestVersion = latestVersion ?? currentlyInstalledChartVersion // a guard for typescript, latestVersion is always defined - const [selectedVersionData, setSelectedVersionData] = useState<{ - version: string - repository?: string - urls: string[] - }>() + const selectedVersion = useMemo(() => { + return selectedVersionData?.version + }, [selectedVersionData]) - const selectedVersion = useMemo(() => { - return selectedVersionData?.version - }, [selectedVersionData]) + const selectedRepo = useMemo(() => { + return selectedVersionData?.repository + }, [selectedVersionData]) - const selectedRepo = useMemo(() => { - return selectedVersionData?.repository - }, [selectedVersionData]) + const chart = useMemo(() => { + return selectedVersionData?.urls?.[0]?.startsWith("file://") + ? selectedVersionData?.urls[0] + : `${selectedVersionData?.repository}/${chartName}` + }, [selectedVersionData, chartName]) + + const { + data: chartValues, + isLoading: loadingChartValues, + refetch: refetchChartValues, + } = useChartRepoValues(namespace || "default", selectedVersion || "", chart, { + enabled: isInstall && Boolean(selectedRepo) && selectedRepo !== "", + onSuccess: (data) => { + fetchDiff({ userValues: "" }) + }, + }) + + const { data: releaseValues, isLoading: loadingReleaseValues } = + useChartReleaseValues({ + namespace, + release: String(releaseName), + // userDefinedValue: userValues, // for key only + revision: latestRevision ? latestRevision : undefined, + options: { + onSuccess: (data: string) => { + if (data) { + fetchDiff({ userValues: "" }) + setUserValues(data) + } + }, + }, + }) - const chart = useMemo(() => { - return selectedVersionData?.urls?.[0]?.startsWith('file://') - ? selectedVersionData?.urls[0] - : `${selectedVersionData?.repository}/${chartName}` - }, [selectedVersionData, chartName]) + useEffect(() => { + if (selectedRepo) { + refetchChartValues() + } + }, [selectedRepo, selectedVersion, namespace, chart]) - const { - data: chartValues, - isLoading: loadingChartValues, - refetch: refetchChartValues, - } = useChartRepoValues( - namespace || 'default', - selectedVersion || '', - chart, + // Confirm method (install) + const setReleaseVersionMutation = useMutation( + [ + "setVersion", + namespace, + releaseName, + selectedVersion, + selectedRepo, + selectedCluster, + chart, + ], + async () => { + setErrorMessage("") + const formData = new FormData() + formData.append("preview", "false") + formData.append("chart", chart) + formData.append("version", selectedVersion || "") + formData.append("values", userValues) + if (isInstall) { + formData.append("name", releaseName || "") + } + const res = await fetch( + // Todo: Change to BASE_URL from env + `/api/helm/releases/${namespace ? namespace : "default"}${ + !isInstall ? `/${releaseName}` : `/${releaseValues ? chartName : ""}` // if there is no release we don't provide anything, and we dont display version + }`, { - enabled: isInstall && Boolean(selectedRepo) && selectedRepo !== '', - onSuccess: (data) => { - fetchDiff({ userValues: '' }) - }, + method: "post", + body: formData, + headers: { + "X-Kubecontext": selectedCluster as string, + }, } - ) + ) - const { data: releaseValues, isLoading: loadingReleaseValues } = - useChartReleaseValues({ - namespace, - release: String(releaseName), - // userDefinedValue: userValues, // for key only - revision: latestRevision ? latestRevision : undefined, - options: { - onSuccess: (data: string) => { - if (data) { - fetchDiff({ userValues: '' }) - setUserValues(data) - } - }, - }, + if (!res.ok) { + setShowErrorModal({ + title: `Failed to ${isInstall ? "install" : "upgrade"} the chart`, + msg: String(await res.text()), }) + } - useEffect(() => { - if (selectedRepo) { - refetchChartValues() + return res.json() + }, + { + onSuccess: async (response) => { + onClose() + if (isInstall) { + navigate( + `/${selectedCluster}/${response.namespace}/${response.name}/installed/revision/1` + ) + } else { + setSelectedVersionData({ version: "", urls: [] }) //cleanup + navigate( + `/${selectedCluster}/${ + namespace ? namespace : "default" + }/${releaseName}/installed/revision/${response.version}` + ) + window.location.reload() } - }, [selectedRepo, selectedVersion, namespace, chart]) - - // Confirm method (install) - const setReleaseVersionMutation = useMutation( - [ - 'setVersion', - namespace, - releaseName, - selectedVersion, - selectedRepo, - selectedCluster, - chart, - ], - async () => { - setErrorMessage('') - const formData = new FormData() - formData.append('preview', 'false') - formData.append('chart', chart) - formData.append('version', selectedVersion || '') - formData.append('values', userValues) - if (isInstall) { - formData.append('name', releaseName || '') - } - const res = await fetch( - // Todo: Change to BASE_URL from env - `/api/helm/releases/${namespace ? namespace : 'default'}${ - !isInstall - ? `/${releaseName}` - : `/${releaseValues ? chartName : ''}` // if there is no release we don't provide anything, and we dont display version - }`, - { - method: 'post', - body: formData, - headers: { - 'X-Kubecontext': selectedCluster as string, - }, - } - ) + }, + onError: (error) => { + setErrorMessage((error as Error)?.message || "Failed to update") + }, + } + ) - if (!res.ok) { - setShowErrorModal({ - title: `Failed to ${ - isInstall ? 'install' : 'upgrade' - } the chart`, - msg: String(await res.text()), - }) - } + const getVersionManifestFormData = useCallback( + ({ version, userValues }: { version: string; userValues?: string }) => { + const formData = new FormData() + // preview needs to come first, for some reason it has a meaning at the backend + formData.append("preview", "true") + formData.append("chart", chart) + formData.append("version", version) + formData.append( + "values", + userValues ? userValues : releaseValues ? releaseValues : "" + ) + if (isInstall) { + formData.append("name", chartName) + } + return formData + }, + [userValues, chart, chartName, isInstall] + ) - return res.json() - }, - { - onSuccess: async (response) => { - onClose() - if (isInstall) { - navigate( - `/${selectedCluster}/${response.namespace}/${response.name}/installed/revision/1` - ) - } else { - setSelectedVersionData({ version: '', urls: [] }) //cleanup - navigate( - `/${selectedCluster}/${ - namespace ? namespace : 'default' - }/${releaseName}/installed/revision/${response.version}` - ) - window.location.reload() - } - }, - onError: (error) => { - setErrorMessage((error as Error)?.message || 'Failed to update') - }, - } - ) - - const getVersionManifestFormData = useCallback( - ({ version, userValues }: { version: string; userValues?: string }) => { - const formData = new FormData() - // preview needs to come first, for some reason it has a meaning at the backend - formData.append('preview', 'true') - formData.append('chart', chart) - formData.append('version', version) - formData.append( - 'values', - userValues ? userValues : releaseValues ? releaseValues : '' - ) - if (isInstall) { - formData.append('name', chartName) - } - return formData - }, - [userValues, chart, chartName, isInstall] - ) + // It actually fetches the manifest for the diffs + const fetchVersionData = async ({ + version, + userValues, + }: { + version: string + userValues?: string + }) => { + const formData = getVersionManifestFormData({ version, userValues }) + const fetchUrl = `/api/helm/releases/${ + namespace ? namespace : isInstall ? "" : "[empty]" + }${!isInstall ? `/${releaseName}` : `${!namespace ? "default" : ""}`}` // if there is no release we don't provide anything, and we dont display version; + try { + setErrorMessage("") + const data = await callApi(fetchUrl, { + method: "post", + body: formData, + }) + return data + } catch (e) { + setErrorMessage((e as Error).message as string) + } + } - // It actually fetches the manifest for the diffs - const fetchVersionData = async ({ - version, - userValues, - }: { - version: string - userValues?: string - }) => { - const formData = getVersionManifestFormData({ version, userValues }) - const fetchUrl = `/api/helm/releases/${ - namespace ? namespace : isInstall ? '' : '[empty]' - }${!isInstall ? `/${releaseName}` : `${!namespace ? 'default' : ''}`}` // if there is no release we don't provide anything, and we dont display version; - try { - setErrorMessage('') - const data = await callApi(fetchUrl, { - method: 'post', - body: formData, - }) - return data - } catch (e) { - setErrorMessage((e as Error).message as string) - } + const fetchDiff = async ({ userValues }: { userValues: string }) => { + if (!selectedRepo || versionsError) { + return } - const fetchDiff = async ({ userValues }: { userValues: string }) => { - if (!selectedRepo || versionsError) { - return - } + const currentVersion = currentlyInstalledChartVersion - const currentVersion = currentlyInstalledChartVersion + setIsLoadingDiff(true) + try { + const [currentVerData, selectedVerData] = await Promise.all([ + currentVersion + ? fetchVersionData({ version: currentVersion, userValues }) + : Promise.resolve({ manifest: "" }), + fetchVersionData({ + version: selectedVersion || "", + userValues, + }), + ]) + const formData = new FormData() - setIsLoadingDiff(true) - try { - const [currentVerData, selectedVerData] = await Promise.all([ - currentVersion - ? fetchVersionData({ version: currentVersion, userValues }) - : Promise.resolve({ manifest: '' }), - fetchVersionData({ - version: selectedVersion || '', - userValues, - }), - ]) - const formData = new FormData() + formData.append("a", isInstall ? "" : (currentVerData as any).manifest) + formData.append("b", (currentVerData as any).manifest) - formData.append('a', isInstall ? '' : (currentVerData as any).manifest) - formData.append('b', (currentVerData as any).manifest) + const response = await apiService.fetchWithDefaults("/diff", { + method: "post", + body: formData, + }) + const diff = await response.text() + setDiff(diff) + } catch (error) { + console.error(error) + } finally { + setIsLoadingDiff(false) + } + } - const response = await apiService.fetchWithDefaults('/diff', { - method: 'post', - body: formData, - }) - const diff = await response.text() - setDiff(diff) - } catch (error) { - console.error(error) - } finally { - setIsLoadingDiff(false) - } + useEffect(() => { + if ( + selectedVersion && + ((!isInstall && !loadingReleaseValues) || + (isInstall && !loadingChartValues)) && + selectedRepo + ) { + fetchDiff({ userValues }) } + }, [selectedVersion, userValues, loadingReleaseValues, selectedRepo]) - useEffect(() => { - if ( - selectedVersion && - ((!isInstall && !loadingReleaseValues) || - (isInstall && !loadingChartValues)) && - selectedRepo - ) { - fetchDiff({ userValues }) + return ( + { + setSelectedVersionData({ version: "", urls: [] }) + if (!isInstall) { + setUserValues(releaseValues) + fetchDiff({ userValues: releaseValues }) } - }, [selectedVersion, userValues, loadingReleaseValues, selectedRepo]) - - return ( - { - setSelectedVersionData({ version: '', urls: [] }) - if (!isInstall) { - setUserValues(releaseValues) - fetchDiff({ userValues: releaseValues }) - } - onClose() - }} - title={ -
- {`${ - isUpgrade || (!isUpgrade && !isInstall) - ? 'Upgrade' - : 'Install' - } `} - {(isUpgrade || releaseValues || isInstall) && ( - {chartName} - )} -
- } - containerClassNames="w-full text-2xl h-2/3" - actions={[ - { - id: '1', - callback: setReleaseVersionMutation.mutate, - variant: ModalButtonStyle.info, - isLoading: setReleaseVersionMutation.isLoading, - disabled: - (isInstall && loadingChartValues) || - (!isInstall && loadingReleaseValues) || - isLoadingDiff || - setReleaseVersionMutation.isLoading, - }, - ]} - > - {versions && isNoneEmptyArray(versions) && ( - - )} + onClose() + }} + title={ +
+ {`${ + isUpgrade || (!isUpgrade && !isInstall) ? "Upgrade" : "Install" + } `} + {(isUpgrade || releaseValues || isInstall) && ( + {chartName} + )} +
+ } + containerClassNames="w-full text-2xl h-2/3" + actions={[ + { + id: "1", + callback: setReleaseVersionMutation.mutate, + variant: ModalButtonStyle.info, + isLoading: setReleaseVersionMutation.isLoading, + disabled: + (isInstall && loadingChartValues) || + (!isInstall && loadingReleaseValues) || + isLoadingDiff || + setReleaseVersionMutation.isLoading, + }, + ]} + > + {versions && isNoneEmptyArray(versions) && ( + + )} - -
- { - setUserValues(val) - fetchDiff({ userValues: val }) - }} - /> + +
+ { + setUserValues(val) + fetchDiff({ userValues: val }) + }} + /> - -
+ +
- -
- ) + +
+ ) } diff --git a/dashboard/src/components/modal/InstallChartModal/ManifestDiff.tsx b/dashboard/src/components/modal/InstallChartModal/ManifestDiff.tsx index 6c8a9f57..ff62cfe0 100644 --- a/dashboard/src/components/modal/InstallChartModal/ManifestDiff.tsx +++ b/dashboard/src/components/modal/InstallChartModal/ManifestDiff.tsx @@ -1,64 +1,64 @@ -import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui-base' -import hljs from 'highlight.js' +import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-base" +import hljs from "highlight.js" -import { useEffect, useRef } from 'react' -import Spinner from '../../Spinner' -import { diffConfiguration } from '../../../utils' +import { useEffect, useRef } from "react" +import Spinner from "../../Spinner" +import { diffConfiguration } from "../../../utils" interface ManifestDiffProps { - diff: string - isLoading: boolean - error: string + diff: string + isLoading: boolean + error: string } export const ManifestDiff = ({ diff, isLoading, error }: ManifestDiffProps) => { - const diffContainerRef = useRef(null) - - useEffect(() => { - if (isLoading) { - // we're listening to isLoading to draw new diffs which are not - // always rerender, probably because of the use of ref - return - } - if (diff && diffContainerRef.current) { - const diff2htmlUi = new Diff2HtmlUI( - diffContainerRef.current, - diff, - diffConfiguration, - hljs - ) - diff2htmlUi.draw() - diff2htmlUi.highlightCode() - } - }, [diff, diffContainerRef.current, isLoading]) + const diffContainerRef = useRef(null) + useEffect(() => { if (isLoading) { - return ( -
- - Calculating diff... -
- ) + // we're listening to isLoading to draw new diffs which are not + // always rerender, probably because of the use of ref + return + } + if (diff && diffContainerRef.current) { + const diff2htmlUi = new Diff2HtmlUI( + diffContainerRef.current, + diff, + diffConfiguration, + hljs + ) + diff2htmlUi.draw() + diff2htmlUi.highlightCode() } + }, [diff, diffContainerRef.current, isLoading]) + if (isLoading) { return ( -
-

Manifest changes:

- - {error ? ( -

- Failed to get upgrade info: {error} -

- ) : diff ? ( -
- ) : ( -
-                    No changes will happen to the cluster
-                
- )} -
+
+ + Calculating diff... +
) + } + + return ( +
+

Manifest changes:

+ + {error ? ( +

+ Failed to get upgrade info: {error} +

+ ) : diff ? ( +
+ ) : ( +
+          No changes will happen to the cluster
+        
+ )} +
+ ) } diff --git a/dashboard/src/components/modal/InstallChartModal/UserDefinedValues.tsx b/dashboard/src/components/modal/InstallChartModal/UserDefinedValues.tsx index 6d10471b..58754991 100644 --- a/dashboard/src/components/modal/InstallChartModal/UserDefinedValues.tsx +++ b/dashboard/src/components/modal/InstallChartModal/UserDefinedValues.tsx @@ -1,44 +1,44 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from "react" export const UserDefinedValues = ({ - initialValue, - setValues, + initialValue, + setValues, }: { - initialValue: string - setValues: (val: string) => void + initialValue: string + setValues: (val: string) => void }) => { - const [localState, setLocalState] = useState(initialValue) + const [localState, setLocalState] = useState(initialValue) - useEffect(() => { - setLocalState(initialValue) - }, [initialValue]) + useEffect(() => { + setLocalState(initialValue) + }, [initialValue]) - const prevValueRef = useRef(initialValue) - const timeoutRef = useRef(null) - useEffect(() => { + const prevValueRef = useRef(initialValue) + const timeoutRef = useRef(null) + useEffect(() => { + clearTimeout(timeoutRef.current) + if (prevValueRef.current !== localState) { + timeoutRef.current = setTimeout(() => { + setValues(localState) clearTimeout(timeoutRef.current) - if (prevValueRef.current !== localState) { - timeoutRef.current = setTimeout(() => { - setValues(localState) - clearTimeout(timeoutRef.current) - }, 800) - } - }, [localState]) + }, 800) + } + }, [localState]) - return ( -
- - -
- ) + return ( +
+ + +
+ ) } diff --git a/dashboard/src/components/modal/InstallChartModal/VersionToInstall.tsx b/dashboard/src/components/modal/InstallChartModal/VersionToInstall.tsx index 3306d958..5245ea19 100644 --- a/dashboard/src/components/modal/InstallChartModal/VersionToInstall.tsx +++ b/dashboard/src/components/modal/InstallChartModal/VersionToInstall.tsx @@ -1,115 +1,108 @@ -import { useMemo, useState } from 'react' -import Select, { components } from 'react-select' -import { BsCheck2 } from 'react-icons/bs' -import { NonEmptyArray } from '../../../data/types' +import { useMemo, useState } from "react" +import Select, { components } from "react-select" +import { BsCheck2 } from "react-icons/bs" +import { NonEmptyArray } from "../../../data/types" interface Version { - repository: string - version: string - isChartVersion: boolean - urls: string[] + repository: string + version: string + isChartVersion: boolean + urls: string[] } export const VersionToInstall: React.FC<{ - versions: NonEmptyArray - initialVersion?: { - repository?: string - version?: string - } - onSelectVersion: (props: { - version: string - repository: string - urls: string[] - }) => void - isInstall?: boolean + versions: NonEmptyArray + initialVersion?: { + repository?: string + version?: string + } + onSelectVersion: (props: { + version: string + repository: string + urls: string[] + }) => void + isInstall?: boolean }> = ({ versions, onSelectVersion, isInstall, initialVersion }) => { - const chartVersion = useMemo( - () => versions.find(({ isChartVersion }) => isChartVersion)?.version, - [versions] - ) + const chartVersion = useMemo( + () => versions.find(({ isChartVersion }) => isChartVersion)?.version, + [versions] + ) - const currentVersion = - chartVersion && !isInstall ? ( -

- {`(current version is `} - {`${chartVersion}`} - {`)`} -

- ) : null + const currentVersion = + chartVersion && !isInstall ? ( +

+ {`(current version is `} + {`${chartVersion}`} + {`)`} +

+ ) : null - // Prepare your options for react-select - const options = - versions.map(({ repository, version, urls }) => ({ - value: { repository, version, urls }, - label: `${repository} @ ${version}`, - check: chartVersion === version, - })) || [] - const [selectedOption, setSelectedOption] = - useState<(typeof options)[number]>() - const initOpt = useMemo( - () => - options.find( - ({ value }) => - value.version === initialVersion?.version && - value.repository === initialVersion?.repository - ), - [options, initialVersion] - ) - return ( -
- {versions?.length && (selectedOption || initOpt) ? ( - <> - Version to install:{' '} - { + if (selectedOption) { + setSelectedOption(selectedOption) + onSelectVersion(selectedOption.value) + } + }} + value={selectedOption ?? initOpt} + components={{ + SingleValue: ({ children, ...props }) => ( + + {children} + {props.data.check && !isInstall && ( + + )} + + ), + Option: ({ children, isSelected, innerProps, data }) => ( +
+
{children}
+ {data.check && !isInstall && ( + - - ) : null} + )} +
+ ), + }} // Use the custom Option component + /> + + ) : null} - {currentVersion} -
- ) + {currentVersion} +
+ ) } diff --git a/dashboard/src/components/modal/Modal.stories.tsx b/dashboard/src/components/modal/Modal.stories.tsx index e20512c9..bc06ded1 100644 --- a/dashboard/src/components/modal/Modal.stories.tsx +++ b/dashboard/src/components/modal/Modal.stories.tsx @@ -1,115 +1,115 @@ // Modal.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import Modal, { ModalAction, ModalButtonStyle } from './Modal' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import Modal, { ModalAction, ModalButtonStyle } from "./Modal" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'Modal', - component: Modal, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "Modal", + component: Modal, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - Basic text content + Basic text content ) export const Default = Template.bind({}) const confirmModalActions: ModalAction[] = [ - { - id: '1', - text: 'Cancel', - callback: () => { - console.log('confirmModal: clicked Cancel') - }, + { + id: "1", + text: "Cancel", + callback: () => { + console.log("confirmModal: clicked Cancel") }, - { - id: '2', - text: 'Confirm', - callback: () => { - console.log('confirmModal: clicked Confirm') - }, - variant: ModalButtonStyle.info, + }, + { + id: "2", + text: "Confirm", + callback: () => { + console.log("confirmModal: clicked Confirm") }, + variant: ModalButtonStyle.info, + }, ] Default.args = { - title: 'Basic text title', - isOpen: true, - actions: confirmModalActions, + title: "Basic text title", + isOpen: true, + actions: confirmModalActions, } const customModalActions: ModalAction[] = [ - { - id: '1', - text: 'custom button 1', - className: - 'text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800', - callback: () => { - console.log('confirmModal: clicked custom button 1') - }, + { + id: "1", + text: "custom button 1", + className: + "text-white bg-green-700 hover:bg-green-800 focus:ring-4 focus:outline-none focus:ring-green-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-green-600 dark:hover:bg-green-700 dark:focus:ring-green-800", + callback: () => { + console.log("confirmModal: clicked custom button 1") }, - { - id: '2', - text: 'custom button 2', - callback: () => { - console.log('confirmModal: clicked custom button 2') - }, - variant: ModalButtonStyle.error, + }, + { + id: "2", + text: "custom button 2", + callback: () => { + console.log("confirmModal: clicked custom button 2") }, + variant: ModalButtonStyle.error, + }, ] export const CustomModal: ComponentStory = (args) => ( - -
-

- Custom Modal Content -

- -
-
+ +
+

+ Custom Modal Content +

+ +
+
) CustomModal.args = { - title: ( -
- Custom Title -
- ), - isOpen: true, - actions: customModalActions, + title: ( +
+ Custom Title +
+ ), + isOpen: true, + actions: customModalActions, } export const AutoScrollWhenContentIsMoreThan500Height: ComponentStory< - typeof Modal + typeof Modal > = (args) => ( - -
- This div height is 1000 px so we can see a vertical scroll to the - right of it. -
-
+ +
+ This div height is 1000 px so we can see a vertical scroll to the right of + it. +
+
) AutoScrollWhenContentIsMoreThan500Height.args = { - title: 'Auto Scroll when content is more than 500px height', - isOpen: true, - actions: confirmModalActions, + title: "Auto Scroll when content is more than 500px height", + isOpen: true, + actions: confirmModalActions, } diff --git a/dashboard/src/components/modal/Modal.tsx b/dashboard/src/components/modal/Modal.tsx index 16626b8b..1da0bb0c 100644 --- a/dashboard/src/components/modal/Modal.tsx +++ b/dashboard/src/components/modal/Modal.tsx @@ -1,166 +1,158 @@ -import { PropsWithChildren, ReactNode } from 'react' -import ReactDom from 'react-dom' -import Spinner from '../Spinner' +import { PropsWithChildren, ReactNode } from "react" +import ReactDom from "react-dom" +import Spinner from "../Spinner" export enum ModalButtonStyle { - default, - info, - error, - success, - disabled, + default, + info, + error, + success, + disabled, } export interface ModalAction { - id: string - callback: () => void - text?: string - variant?: ModalButtonStyle - className?: string - disabled?: boolean - isLoading?: boolean + id: string + callback: () => void + text?: string + variant?: ModalButtonStyle + className?: string + disabled?: boolean + isLoading?: boolean } export interface ModalProps extends PropsWithChildren { - title?: string | ReactNode - isOpen: boolean - onClose?: () => void - containerClassNames?: string - actions?: ModalAction[] - bottomContent?: ReactNode + title?: string | ReactNode + isOpen: boolean + onClose?: () => void + containerClassNames?: string + actions?: ModalAction[] + bottomContent?: ReactNode } const Modal = ({ - title, - isOpen, - onClose, - children, - actions, - containerClassNames, - bottomContent, + title, + isOpen, + onClose, + children, + actions, + containerClassNames, + bottomContent, }: ModalProps) => { - const colorVariants = new Map([ - [ - 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 ', - ], - [ - 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 ', - ], - [ - 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 ', - ], - [ - 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 ', - ], - [ - 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 ', - ], - ]) + const colorVariants = new Map([ + [ + 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 ", + ], + [ + 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 ", + ], + [ + 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 ", + ], + [ + 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 ", + ], + [ + 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 ", + ], + ]) - const getClassName = (action: ModalAction) => { - if (action.className) return action.className + const getClassName = (action: ModalAction) => { + if (action.className) return action.className - return action.variant - ? colorVariants.get(action.variant) - : colorVariants.get(ModalButtonStyle.default) - } + return action.variant + ? colorVariants.get(action.variant) + : colorVariants.get(ModalButtonStyle.default) + } - const getTitle = (title: string | ReactNode) => { - if (typeof title === 'string') - return

{title}

- else return title - } + const getTitle = (title: string | ReactNode) => { + if (typeof title === "string") + return

{title}

+ else return title + } - return ReactDom.createPortal( - <> - {isOpen && ( -
-
-
- {title && ( -
- {getTitle(title)} - {onClose ? ( - - ) : null} -
- )} -
- {children} -
- {bottomContent ? ( -
- {bottomContent} -
- ) : ( -
- {actions?.map((action) => ( - - ))} -
- )} -
-
+ return ReactDom.createPortal( + <> + {isOpen && ( +
+
+
+ {title && ( +
+ {getTitle(title)} + {onClose ? ( + + ) : null}
- )} - , - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - document.getElementById('portal')! - ) + )} +
+ {children} +
+ {bottomContent ? ( +
{bottomContent}
+ ) : ( +
+ {actions?.map((action) => ( + + ))} +
+ )} +
+
+
+ )} + , + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + document.getElementById("portal")! + ) } export default Modal diff --git a/dashboard/src/components/repository/ChartViewer.stories.tsx b/dashboard/src/components/repository/ChartViewer.stories.tsx index c32ca31d..026e2ccd 100644 --- a/dashboard/src/components/repository/ChartViewer.stories.tsx +++ b/dashboard/src/components/repository/ChartViewer.stories.tsx @@ -1,31 +1,31 @@ // ChartViewer.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import ChartViewer from './ChartViewer' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import ChartViewer from "./ChartViewer" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'ChartViewer', - component: ChartViewer, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "ChartViewer", + component: ChartViewer, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = (args) => ( - + ) export const Default = Template.bind({}) const defaultArgs = { - chart: { - name: 'chart1', - description: 'chart1 description', - version: 'v1.0.0', - }, + chart: { + name: "chart1", + description: "chart1 description", + version: "v1.0.0", + }, } //@ts-ignore diff --git a/dashboard/src/components/repository/ChartViewer.tsx b/dashboard/src/components/repository/ChartViewer.tsx index 9ec0588c..68911a5e 100644 --- a/dashboard/src/components/repository/ChartViewer.tsx +++ b/dashboard/src/components/repository/ChartViewer.tsx @@ -1,57 +1,57 @@ -import { useState } from 'react' -import { Chart } from '../../data/types' -import { InstallChartModal } from '../modal/InstallChartModal/InstallChartModal' +import { useState } from "react" +import { Chart } from "../../data/types" +import { InstallChartModal } from "../modal/InstallChartModal/InstallChartModal" type ChartViewerProps = { - chart: Chart + chart: Chart } function ChartViewer({ chart }: ChartViewerProps) { - const [showInstallButton, setShowInstallButton] = useState(false) - const [showInstallModal, setShowInstallModal] = useState(false) + const [showInstallButton, setShowInstallButton] = useState(false) + const [showInstallModal, setShowInstallModal] = useState(false) - const handleMouseOver = () => { - setShowInstallButton(true) - } - const handleMouseOut = () => { - setShowInstallButton(false) - } + const handleMouseOver = () => { + setShowInstallButton(true) + } + const handleMouseOut = () => { + setShowInstallButton(false) + } - return ( - <> -
- - - {chart.name} - - {chart.description} - {chart.version} - - - -
- {showInstallModal && ( - setShowInstallModal(false)} - isInstall={true} - /> - )} - - ) + return ( + <> +
+ + + {chart.name} + + {chart.description} + {chart.version} + + + +
+ {showInstallModal && ( + setShowInstallModal(false)} + isInstall={true} + /> + )} + + ) } export default ChartViewer diff --git a/dashboard/src/components/repository/RepositoriesList.stories.tsx b/dashboard/src/components/repository/RepositoriesList.stories.tsx index eb61d8ad..6cc6f95e 100644 --- a/dashboard/src/components/repository/RepositoriesList.stories.tsx +++ b/dashboard/src/components/repository/RepositoriesList.stories.tsx @@ -1,27 +1,27 @@ // RepositoriesList.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import RepositoriesList from './RepositoriesList' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import RepositoriesList from "./RepositoriesList" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'RepositoriesList', - component: RepositoriesList, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "RepositoriesList", + component: RepositoriesList, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = () => ( - {}} - repositories={[]} - /> + {}} + repositories={[]} + /> ) export const Default = Template.bind({}) diff --git a/dashboard/src/components/repository/RepositoriesList.tsx b/dashboard/src/components/repository/RepositoriesList.tsx index f9f00b97..ad40deca 100644 --- a/dashboard/src/components/repository/RepositoriesList.tsx +++ b/dashboard/src/components/repository/RepositoriesList.tsx @@ -1,90 +1,83 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import AddRepositoryModal from '../modal/AddRepositoryModal' -import { Repository } from '../../data/types' -import useCustomSearchParams from '../../hooks/useCustomSearchParams' -import { useParams } from 'react-router' +import { useCallback, useEffect, useMemo, useState } from "react" +import AddRepositoryModal from "../modal/AddRepositoryModal" +import { Repository } from "../../data/types" +import useCustomSearchParams from "../../hooks/useCustomSearchParams" +import { useParams } from "react-router" type RepositoriesListProps = { - selectedRepository: Repository | undefined - onRepositoryChanged: (selectedRepository: Repository) => void - repositories: Repository[] + selectedRepository: Repository | undefined + onRepositoryChanged: (selectedRepository: Repository) => void + repositories: Repository[] } function RepositoriesList({ - onRepositoryChanged, - selectedRepository, - repositories, + onRepositoryChanged, + selectedRepository, + repositories, }: RepositoriesListProps) { - const { searchParamsObject, upsertSearchParams, removeSearchParam } = - useCustomSearchParams() - const showAddRepositoryModal = useMemo( - () => searchParamsObject['add_repo'] === 'true', - [searchParamsObject] - ) - const setShowAddRepositoryModal = useCallback( - (value: boolean) => - value - ? upsertSearchParams('add_repo', 'true') - : removeSearchParam('add_repo'), - [upsertSearchParams, removeSearchParam] - ) + const { searchParamsObject, upsertSearchParams, removeSearchParam } = + useCustomSearchParams() + const showAddRepositoryModal = useMemo( + () => searchParamsObject["add_repo"] === "true", + [searchParamsObject] + ) + const setShowAddRepositoryModal = useCallback( + (value: boolean) => + value + ? upsertSearchParams("add_repo", "true") + : removeSearchParam("add_repo"), + [upsertSearchParams, removeSearchParam] + ) - return ( - <> -
- -
- {repositories?.map((repository) => ( - - { - onRepositoryChanged(repository) - }} - className="cursor-pointer" - type="radio" - id={repository.name} - value={repository.name} - checked={ - repository.name === selectedRepository?.name - } - name="clusters" - /> - - - ))} -
- -

- Charts developers: you can also add local directories as - chart source. Use{' '} - - --local-chart - {' '} - CLI switch to specify it. -

-
- setShowAddRepositoryModal(false)} - /> - - ) + return ( + <> +
+ +
+ {repositories?.map((repository) => ( + + { + onRepositoryChanged(repository) + }} + className="cursor-pointer" + type="radio" + id={repository.name} + value={repository.name} + checked={repository.name === selectedRepository?.name} + name="clusters" + /> + + + ))} +
+ +

+ Charts developers: you can also add local directories as chart source. + Use{" "} + --local-chart{" "} + CLI switch to specify it. +

+
+ setShowAddRepositoryModal(false)} + /> + + ) } export default RepositoriesList diff --git a/dashboard/src/components/repository/RepositoryViewer.stories.tsx b/dashboard/src/components/repository/RepositoryViewer.stories.tsx index a1b46b34..ab759b68 100644 --- a/dashboard/src/components/repository/RepositoryViewer.stories.tsx +++ b/dashboard/src/components/repository/RepositoryViewer.stories.tsx @@ -1,21 +1,21 @@ // RepositoryViewer.stories.ts|tsx -import { ComponentStory, ComponentMeta } from '@storybook/react' -import RepositoryViewer from './RepositoryViewer' +import { ComponentStory, ComponentMeta } from "@storybook/react" +import RepositoryViewer from "./RepositoryViewer" //👇 This default export determines where your story goes in the story list export default { - /* 👇 The title prop is optional. - * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading - * to learn how to generate automatic titles - */ - title: 'RepositoryViewer', - component: RepositoryViewer, + /* 👇 The title prop is optional. + * See https://storybook.js.org/docs/react/configure/overview#configure-story-loading + * to learn how to generate automatic titles + */ + title: "RepositoryViewer", + component: RepositoryViewer, } as ComponentMeta //👇 We create a “template” of how args map to rendering const Template: ComponentStory = () => ( - + ) export const Default = Template.bind({}) diff --git a/dashboard/src/components/repository/RepositoryViewer.tsx b/dashboard/src/components/repository/RepositoryViewer.tsx index 70eaeaf4..6a7ed1eb 100644 --- a/dashboard/src/components/repository/RepositoryViewer.tsx +++ b/dashboard/src/components/repository/RepositoryViewer.tsx @@ -1,162 +1,153 @@ -import { BsTrash3, BsArrowRepeat } from 'react-icons/bs' -import { Chart, Repository } from '../../data/types' -import ChartViewer from './ChartViewer' -import { useQuery, useQueryClient } from '@tanstack/react-query' -import apiService from '../../API/apiService' -import Spinner from '../Spinner' -import { useUpdateRepo } from '../../API/repositories' -import { callApi } from '../../API/releases' -import { useEffect, useMemo, useState } from 'react' -import { useNavigate, useParams } from 'react-router-dom' -import { useAppContext } from '../../context/AppContext' +import { BsTrash3, BsArrowRepeat } from "react-icons/bs" +import { Chart, Repository } from "../../data/types" +import ChartViewer from "./ChartViewer" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import apiService from "../../API/apiService" +import Spinner from "../Spinner" +import { useUpdateRepo } from "../../API/repositories" +import { callApi } from "../../API/releases" +import { useEffect, useMemo, useState } from "react" +import { useNavigate, useParams } from "react-router-dom" +import { useAppContext } from "../../context/AppContext" type RepositoryViewerProps = { - repository: Repository | undefined + repository: Repository | undefined } function RepositoryViewer({ repository }: RepositoryViewerProps) { - const [searchValue, setSearchValue] = useState('') - const [isRemoveLoading, setIsRemove] = useState(false) - const { context } = useParams() - const { setSelectedRepo, selectedRepo } = useAppContext() - const queryClient = useQueryClient() + const [searchValue, setSearchValue] = useState("") + const [isRemoveLoading, setIsRemove] = useState(false) + const { context } = useParams() + const { setSelectedRepo, selectedRepo } = useAppContext() + const queryClient = useQueryClient() - const navigate = useNavigate() + const navigate = useNavigate() - const { data: charts, isLoading } = useQuery({ - //@ts-ignore - queryKey: ['charts', repository?.name || ''], - queryFn: apiService.getRepositoryCharts, - refetchOnWindowFocus: false, - enabled: !!repository?.name, - }) + const { data: charts, isLoading } = useQuery({ + //@ts-ignore + queryKey: ["charts", repository?.name || ""], + queryFn: apiService.getRepositoryCharts, + refetchOnWindowFocus: false, + enabled: !!repository?.name, + }) - useEffect(() => { - setSearchValue('') - }, [repository, selectedRepo]) + useEffect(() => { + setSearchValue("") + }, [repository, selectedRepo]) - const update = useUpdateRepo(repository?.name || '', { - retry: false, - onSuccess: () => { - window.location.reload() - }, - }) + const update = useUpdateRepo(repository?.name || "", { + retry: false, + onSuccess: () => { + window.location.reload() + }, + }) - const removeRepository = async () => { - //this is expected - //eslint-disable-next-line no-alert - if (confirm('Confirm removing repository?')) { - try { - setIsRemove(true) - const repo = repository?.name || '' - await callApi(`/api/helm/repositories/${repo}`, { - method: 'DELETE', - }) - navigate(`/${context}/repository`, { replace: true }) - setSelectedRepo('') - queryClient.invalidateQueries({ - queryKey: ['helm', 'repositories'], - }) - } catch (error) { - console.error(error) - } finally { - setIsRemove(false) - } - } + const removeRepository = async () => { + //this is expected + //eslint-disable-next-line no-alert + if (confirm("Confirm removing repository?")) { + try { + setIsRemove(true) + const repo = repository?.name || "" + await callApi(`/api/helm/repositories/${repo}`, { + method: "DELETE", + }) + navigate(`/${context}/repository`, { replace: true }) + setSelectedRepo("") + queryClient.invalidateQueries({ + queryKey: ["helm", "repositories"], + }) + } catch (error) { + console.error(error) + } finally { + setIsRemove(false) + } } + } - const numOfCharts = (charts as Chart[])?.length - const showNoChartsAlert = Boolean(!numOfCharts && numOfCharts == 0) - const filteredCharts = useMemo(() => { - return (charts as Chart[])?.filter((ch: Chart) => - ch.name.toLowerCase().includes(searchValue.toLowerCase()) - ) - }, [searchValue]) - - if (repository == undefined) { - return ( -
- Looks like you don't have any repositories installed. You can - add one with the "Add Repository" button on the left side bar. -
- ) - } + const numOfCharts = (charts as Chart[])?.length + const showNoChartsAlert = Boolean(!numOfCharts && numOfCharts == 0) + const filteredCharts = useMemo(() => { + return (charts as Chart[])?.filter((ch: Chart) => + ch.name.toLowerCase().includes(searchValue.toLowerCase()) + ) + }, [searchValue]) + if (repository == undefined) { return ( -
- REPOSITORY -
- - {repository?.name} - +
+ Looks like you don't have any repositories installed. You can add one + with the "Add Repository" button on the left side bar. +
+ ) + } -
-
- - -
- setSearchValue(e.target.value)} - value={searchValue} - type="text" - placeholder="Filter..." - className="mt-2 h-8 p-2 text-sm w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow rounded" - /> -
-
- - URL: {repository?.url} - + return ( +
+ REPOSITORY +
+ + {repository?.name} + -
- CHART NAME - DESCRIPTION - VERSION - -
- {isLoading ? ( - - ) : ( - (filteredCharts || charts)?.map((chart: Chart) => ( - - )) - )} +
+
+ + +
+ setSearchValue(e.target.value)} + value={searchValue} + type="text" + placeholder="Filter..." + className="mt-2 h-8 p-2 text-sm w-full border border-gray-300 focus:outline-none focus:border-sky-500 input-box-shadow rounded" + /> +
+
+ + URL: {repository?.url} + + +
+ CHART NAME + DESCRIPTION + VERSION + +
+ {isLoading ? ( + + ) : ( + (filteredCharts || charts)?.map((chart: Chart) => ( + + )) + )} - {showNoChartsAlert && ( -
- Looks like you don't have any repositories installed. You - can add one with the "Add Repository" button on the left - side bar. -
- )} + {showNoChartsAlert && ( +
+ Looks like you don't have any repositories installed. You can add one + with the "Add Repository" button on the left side bar.
- ) + )} +
+ ) } export default RepositoryViewer diff --git a/dashboard/src/components/revision/RevisionDetails.tsx b/dashboard/src/components/revision/RevisionDetails.tsx index 585eedc1..343f1fcd 100644 --- a/dashboard/src/components/revision/RevisionDetails.tsx +++ b/dashboard/src/components/revision/RevisionDetails.tsx @@ -1,550 +1,525 @@ -import { useEffect, useRef, useState } from 'react' -import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui-slim.js' +import { useEffect, useRef, useState } from "react" +import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js" import { - BsPencil, - BsTrash3, - BsHourglassSplit, - BsArrowRepeat, - BsArrowUp, - BsCheckCircle, -} from 'react-icons/bs' -import { Release, ReleaseRevision } from '../../data/types' -import StatusLabel, { DeploymentStatus } from '../common/StatusLabel' -import { useNavigate, useParams, useSearchParams } from 'react-router-dom' -import { useGetReleaseInfoByType } from '../../API/releases' - -import RevisionDiff from './RevisionDiff' -import RevisionResource from './RevisionResource' -import Tabs from '../Tabs' + BsPencil, + BsTrash3, + BsHourglassSplit, + BsArrowRepeat, + BsArrowUp, + BsCheckCircle, +} from "react-icons/bs" +import { Release, ReleaseRevision } from "../../data/types" +import StatusLabel, { DeploymentStatus } from "../common/StatusLabel" +import { useNavigate, useParams, useSearchParams } from "react-router-dom" +import { useGetReleaseInfoByType } from "../../API/releases" + +import RevisionDiff from "./RevisionDiff" +import RevisionResource from "./RevisionResource" +import Tabs from "../Tabs" import { - useGetLatestVersion, - useGetResources, - useRollbackRelease, - useTestRelease, -} from '../../API/releases' -import { useMutation } from '@tanstack/react-query' -import Modal, { ModalButtonStyle } from '../modal/Modal' -import Spinner from '../Spinner' -import useAlertError from '../../hooks/useAlertError' -import Button from '../Button' -import { InstallChartModal } from '../modal/InstallChartModal/InstallChartModal' -import { diffConfiguration, isNewerVersion } from '../../utils' -import useNavigateWithSearchParams from '../../hooks/useNavigateWithSearchParams' -import apiService from '../../API/apiService' + useGetLatestVersion, + useGetResources, + useRollbackRelease, + useTestRelease, +} from "../../API/releases" +import { useMutation } from "@tanstack/react-query" +import Modal, { ModalButtonStyle } from "../modal/Modal" +import Spinner from "../Spinner" +import useAlertError from "../../hooks/useAlertError" +import Button from "../Button" +import { InstallChartModal } from "../modal/InstallChartModal/InstallChartModal" +import { diffConfiguration, isNewerVersion } from "../../utils" +import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams" +import apiService from "../../API/apiService" type RevisionTagProps = { - caption: string - text: string + caption: string + text: string } type RevisionDetailsProps = { - release: Release - installedRevision: ReleaseRevision - isLatest: boolean - latestRevision: number + release: Release + installedRevision: ReleaseRevision + isLatest: boolean + latestRevision: number } export default function RevisionDetails({ - release, - installedRevision, - isLatest, - latestRevision, + release, + installedRevision, + isLatest, + latestRevision, }: RevisionDetailsProps) { - const [searchParams] = useSearchParams() - const revisionTabs = [ - { - value: 'resources', - label: 'Resources', - content: , - }, - { - value: 'manifests', - label: 'Manifests', - content: , - }, - { - value: 'values', - label: 'Values', - content: ( - - ), - }, - { - value: 'notes', - label: 'Notes', - content: , - }, - ] - const { context, namespace, chart } = useParams() - const tab = searchParams.get('tab') - const selectedTab = - revisionTabs.find((t) => t.value === tab) || revisionTabs[0] - const [isReconfigureModalOpen, setIsReconfigureModalOpen] = useState(false) - - const { - data: latestVerData, - refetch: refetchLatestVersion, - isLoading: isLoadingLatestVersion, - isRefetching: isRefetchingLatestVersion, - } = useGetLatestVersion(release.chart_name, { cacheTime: 0 }) - - const [showTestsResults, setShowTestResults] = useState(false) - - const { setShowErrorModal } = useAlertError() - const { - mutate: runTests, - isLoading: isRunningTests, - data: testResults, - } = useTestRelease({ - onError: (error) => { - setShowTestResults(false) - setShowErrorModal({ - title: 'Failed to run tests for chart ' + chart, - msg: (error as Error).message, - }) - console.error('Failed to execute test for chart', error) - }, - }) - - const handleRunTests = () => { - if (!namespace || !chart) { - setShowErrorModal({ - title: 'Missing data to run test', - msg: 'Missing chart and/or namespace', - }) - return - } - - try { - runTests({ - ns: namespace, - name: chart, - }) - } catch (error: any) { - setShowErrorModal({ - title: 'Test failed to run', - msg: error.message, - }) - } - setShowTestResults(true) + const [searchParams] = useSearchParams() + const revisionTabs = [ + { + value: "resources", + label: "Resources", + content: , + }, + { + value: "manifests", + label: "Manifests", + content: , + }, + { + value: "values", + label: "Values", + content: ( + + ), + }, + { + value: "notes", + label: "Notes", + content: , + }, + ] + const { context, namespace, chart } = useParams() + const tab = searchParams.get("tab") + const selectedTab = + revisionTabs.find((t) => t.value === tab) || revisionTabs[0] + const [isReconfigureModalOpen, setIsReconfigureModalOpen] = useState(false) + + const { + data: latestVerData, + refetch: refetchLatestVersion, + isLoading: isLoadingLatestVersion, + isRefetching: isRefetchingLatestVersion, + } = useGetLatestVersion(release.chart_name, { cacheTime: 0 }) + + const [showTestsResults, setShowTestResults] = useState(false) + + const { setShowErrorModal } = useAlertError() + const { + mutate: runTests, + isLoading: isRunningTests, + data: testResults, + } = useTestRelease({ + onError: (error) => { + setShowTestResults(false) + setShowErrorModal({ + title: "Failed to run tests for chart " + chart, + msg: (error as Error).message, + }) + console.error("Failed to execute test for chart", error) + }, + }) + + const handleRunTests = () => { + if (!namespace || !chart) { + setShowErrorModal({ + title: "Missing data to run test", + msg: "Missing chart and/or namespace", + }) + return } - const displayTestResults = () => { - if (!testResults || (testResults as []).length === 0) { - return ( -
- Tests executed successfully -
-
-
Empty response from API
-
- ) - } else { - return ( -
- {(testResults as string).split('\n').map((line, index) => ( -
- {line} -
-
- ))} -
- ) - } + try { + runTests({ + ns: namespace, + name: chart, + }) + } catch (error: any) { + setShowErrorModal({ + title: "Test failed to run", + msg: error.message, + }) } - - const Header = () => { - const navigate = useNavigate() - return ( -
-

- {chart} -

-
-
- - - {isReconfigureModalOpen && ( - { - setIsReconfigureModalOpen(false) - }} - latestRevision={latestRevision} - /> - )} - {latestVerData?.[0]?.isSuggestedRepo ? ( - { - navigate( - `/${context}/repository?add_repo=true&repo_url=${latestVerData[0].urls[0]}&repo_name=${latestVerData[0].repository}` - ) - }} - className="underline text-sm cursor-pointer text-blue-600" - > - Add repository for it:{' '} - {latestVerData[0].repository} - - ) : ( - refetchLatestVersion()} - className="underline cursor-pointer text-xs" - > - Check for new version - - )} -
- - - {release.has_tests ? ( - <> - {' '} - - setShowTestResults(false)} - > - {isRunningTests ? ( -
- Waiting for completion.. -
- ) : ( - displayTestResults() - )} -
{' '} - - ) : null} - - -
-
- ) + setShowTestResults(true) + } + + const displayTestResults = () => { + if (!testResults || (testResults as []).length === 0) { + return ( +
+ Tests executed successfully +
+
+
Empty response from API
+
+ ) + } else { + return ( +
+ {(testResults as string).split("\n").map((line, index) => ( +
+ {line} +
+
+ ))} +
+ ) } + } - const canUpgrade = !latestVerData?.[0]?.version - ? false - : isNewerVersion( - installedRevision.chart_ver, - latestVerData?.[0]?.version - ) - + const Header = () => { + const navigate = useNavigate() return ( -
- -
-
- - Revision{' '} - #{release.revision} - - - {new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - hour12: true, - }).format(new Date(release.updated))} - -
-
- - - - -
- - +

+ {chart} +

+
+
+ + + {isReconfigureModalOpen && ( + { + setIsReconfigureModalOpen(false) + }} + latestRevision={latestRevision} + /> + )} + {latestVerData?.[0]?.isSuggestedRepo ? ( + { + navigate( + `/${context}/repository?add_repo=true&repo_url=${latestVerData[0].urls[0]}&repo_name=${latestVerData[0].repository}` + ) + }} + className="underline text-sm cursor-pointer text-blue-600" + > + Add repository for it: {latestVerData[0].repository} + + ) : ( + refetchLatestVersion()} + className="underline cursor-pointer text-xs" + > + Check for new version + + )} +
+ + + {release.has_tests ? ( + <> + {" "} + + setShowTestResults(false)} + > + {isRunningTests ? ( +
+ Waiting for completion.. +
+ ) : ( + displayTestResults() + )} +
{" "} + + ) : null} + +
+
) + } + + const canUpgrade = !latestVerData?.[0]?.version + ? false + : isNewerVersion(installedRevision.chart_ver, latestVerData?.[0]?.version) + + return ( +
+ +
+
+ + Revision #{release.revision} + + + {new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "numeric", + minute: "numeric", + second: "numeric", + hour12: true, + }).format(new Date(release.updated))} + +
+
+ + + + +
+ + + {release.description} + + +
+ ) } function RevisionTag({ caption, text }: RevisionTagProps) { - return ( - - {caption}: - {text} - - ) + return ( + + {caption}: + {text} + + ) } const Rollback = ({ - release, - installedRevision, + release, + installedRevision, }: { - release: Release - installedRevision: ReleaseRevision + release: Release + installedRevision: ReleaseRevision }) => { - const { chart, namespace, revision, context } = useParams() - const navigate = useNavigateWithSearchParams() - if (!chart || !namespace || !revision) { - return null - } - - const [showRollbackDiff, setShowRollbackDiff] = useState(false) - const revisionInt = parseInt(revision || '', 10) - const rollbackRevision = - installedRevision.revision === release.revision - ? installedRevision.revision - 1 - : revisionInt - - const { mutate: rollbackRelease, isLoading: isRollingBackRelease } = - useRollbackRelease({ - onSuccess: () => { - navigate( - `/${context}/${namespace}/${chart}/installed/revision/${ - revisionInt + 1 - }` - ) - window.location.reload() - }, - }) - - const handleRollback = () => { - setShowRollbackDiff(true) - } + const { chart, namespace, revision, context } = useParams() + const navigate = useNavigateWithSearchParams() + if (!chart || !namespace || !revision) { + return null + } + + const [showRollbackDiff, setShowRollbackDiff] = useState(false) + const revisionInt = parseInt(revision || "", 10) + const rollbackRevision = + installedRevision.revision === release.revision + ? installedRevision.revision - 1 + : revisionInt + + const { mutate: rollbackRelease, isLoading: isRollingBackRelease } = + useRollbackRelease({ + onSuccess: () => { + navigate( + `/${context}/${namespace}/${chart}/installed/revision/${ + revisionInt + 1 + }` + ) + window.location.reload() + }, + }) - const rollbackTitle = ( -
- Rollback {chart} from revision{' '} - {installedRevision.revision} to {rollbackRevision} -
+ const handleRollback = () => { + setShowRollbackDiff(true) + } + + const rollbackTitle = ( +
+ Rollback {chart} from revision{" "} + {installedRevision.revision} to {rollbackRevision} +
+ ) + + if (release.revision <= 1) return null + + const RollbackModal = () => { + const response = useGetReleaseInfoByType( + { + chart, + namespace, + revision: rollbackRevision.toString(), + tab: "manifests", + }, + `&revisionDiff=${installedRevision.revision}` ) - if (release.revision <= 1) return null - - const RollbackModal = () => { - const response = useGetReleaseInfoByType( - { - chart, - namespace, - revision: rollbackRevision.toString(), - tab: 'manifests', + return ( + setShowRollbackDiff(false)} + containerClassNames="w-4/5" + actions={[ + { + id: "1", + callback: () => { + rollbackRelease({ + ns: namespace as string, + name: String(chart), + revision: release.revision, + }) }, - `&revisionDiff=${installedRevision.revision}` - ) - - return ( - setShowRollbackDiff(false)} - containerClassNames="w-4/5" - actions={[ - { - id: '1', - callback: () => { - rollbackRelease({ - ns: namespace as string, - name: String(chart), - revision: release.revision, - }) - }, - variant: ModalButtonStyle.info, - isLoading: isRollingBackRelease, - text: isRollingBackRelease ? 'Rolling back' : 'Confirm', - }, - ]} - > - - - ) - } - - const RollbackModalContent = ({ dataResponse }: { dataResponse: any }) => { - const { - data, - isLoading, - isSuccess: fetchedDataSuccessfully, - } = dataResponse - const diffElement = useRef(null) - - useEffect(() => { - if (data && fetchedDataSuccessfully && diffElement?.current) { - const diff2htmlUi = new Diff2HtmlUI( - diffElement.current, - data, - diffConfiguration - ) - diff2htmlUi.draw() - diff2htmlUi.highlightCode() - } - }, [data, isLoading, fetchedDataSuccessfully, diffElement?.current]) - - return ( -
- {isLoading ? ( -
- -

Loading changes that will happen to cluster

-
- ) : data ? ( -

- Following changes will happen to cluster: -

- ) : ( -

- No changes will happen to cluster -

- )} -
-
+ variant: ModalButtonStyle.info, + isLoading: isRollingBackRelease, + text: isRollingBackRelease ? "Rolling back" : "Confirm", + }, + ]} + > + + + ) + } + + const RollbackModalContent = ({ dataResponse }: { dataResponse: any }) => { + const { data, isLoading, isSuccess: fetchedDataSuccessfully } = dataResponse + const diffElement = useRef(null) + + useEffect(() => { + if (data && fetchedDataSuccessfully && diffElement?.current) { + const diff2htmlUi = new Diff2HtmlUI( + diffElement.current, + data, + diffConfiguration ) - } + diff2htmlUi.draw() + diff2htmlUi.highlightCode() + } + }, [data, isLoading, fetchedDataSuccessfully, diffElement?.current]) return ( - <> - - {showRollbackDiff && } - +
+ {isLoading ? ( +
+ +

Loading changes that will happen to cluster

+
+ ) : data ? ( +

Following changes will happen to cluster:

+ ) : ( +

No changes will happen to cluster

+ )} +
+
) + } + + return ( + <> + + {showRollbackDiff && } + + ) } const Uninstall = () => { - const [isOpen, setIsOpen] = useState(false) - const { namespace = '', chart = '' } = useParams() - const { data: resources } = useGetResources(namespace, chart, { - enabled: isOpen, - }) - - const uninstallMutation = useMutation( - ['uninstall', namespace, chart], - () => - apiService.fetchWithDefaults( - '/api/helm/releases/' + namespace + '/' + chart, - { - method: 'delete', - } - ), + const [isOpen, setIsOpen] = useState(false) + const { namespace = "", chart = "" } = useParams() + const { data: resources } = useGetResources(namespace, chart, { + enabled: isOpen, + }) + + const uninstallMutation = useMutation( + ["uninstall", namespace, chart], + () => + apiService.fetchWithDefaults( + "/api/helm/releases/" + namespace + "/" + chart, { - onSuccess: () => { - window.location.href = '/' - }, + method: "delete", } - ) - const uninstallTitle = ( -
- Uninstall {chart} from - namespace {namespace} -
- ) - - return ( - <> - - {resources?.length ? ( - setIsOpen(false)} - actions={[ - { - id: '1', - callback: uninstallMutation.mutate, - variant: ModalButtonStyle.info, - isLoading: uninstallMutation.isLoading, - }, - ]} - containerClassNames="w-[800px]" + ), + { + onSuccess: () => { + window.location.href = "/" + }, + } + ) + const uninstallTitle = ( +
+ Uninstall {chart} from namespace{" "} + {namespace} +
+ ) + + return ( + <> + + {resources?.length ? ( + setIsOpen(false)} + actions={[ + { + id: "1", + callback: uninstallMutation.mutate, + variant: ModalButtonStyle.info, + isLoading: uninstallMutation.isLoading, + }, + ]} + containerClassNames="w-[800px]" + > +
Following resources will be deleted from the cluster:
+
+ {resources?.map((resource) => ( +
+ -
- Following resources will be deleted from the cluster: -
-
- {resources?.map((resource) => ( -
- - {resource.kind} - - - {resource.metadata.name} - -
- ))} -
- - ) : null} - - ) + {resource.kind} +
+ + {resource.metadata.name} + +
+ ))} +
+
+ ) : null} + + ) } diff --git a/dashboard/src/components/revision/RevisionDiff.tsx b/dashboard/src/components/revision/RevisionDiff.tsx index c27e1401..0b679479 100644 --- a/dashboard/src/components/revision/RevisionDiff.tsx +++ b/dashboard/src/components/revision/RevisionDiff.tsx @@ -1,240 +1,232 @@ -import { ChangeEvent, useMemo, useState, useRef, useEffect } from 'react' -import { Diff2HtmlUI } from 'diff2html/lib/ui/js/diff2html-ui-slim.js' -import { useGetReleaseInfoByType } from '../../API/releases' -import { useParams } from 'react-router-dom' -import useCustomSearchParams from '../../hooks/useCustomSearchParams' +import { ChangeEvent, useMemo, useState, useRef, useEffect } from "react" +import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js" +import { useGetReleaseInfoByType } from "../../API/releases" +import { useParams } from "react-router-dom" +import useCustomSearchParams from "../../hooks/useCustomSearchParams" -import parse from 'html-react-parser' +import parse from "html-react-parser" -import hljs from 'highlight.js' -import Spinner from '../Spinner' -import { diffConfiguration } from '../../utils' +import hljs from "highlight.js" +import Spinner from "../Spinner" +import { diffConfiguration } from "../../utils" type RevisionDiffProps = { - includeUserDefineOnly?: boolean - latestRevision: number + includeUserDefineOnly?: boolean + latestRevision: number } -const VIEW_MODE_VIEW_ONLY = 'view' -const VIEW_MODE_DIFF_PREV = 'diff-with-previous' -const VIEW_MODE_DIFF_SPECIFIC = 'diff-with-specific-revision' +const VIEW_MODE_VIEW_ONLY = "view" +const VIEW_MODE_DIFF_PREV = "diff-with-previous" +const VIEW_MODE_DIFF_SPECIFIC = "diff-with-specific-revision" function RevisionDiff({ - includeUserDefineOnly, - latestRevision, + includeUserDefineOnly, + latestRevision, }: RevisionDiffProps) { - const params = useParams() + const params = useParams() - const [specificVersion, setSpecificVersion] = useState(latestRevision) - const { - searchParamsObject: searchParams, - upsertSearchParams, - removeSearchParam, - } = useCustomSearchParams() - const { - tab, - mode: viewMode = VIEW_MODE_VIEW_ONLY, - 'user-defined': userDefinedValue, - } = searchParams + const [specificVersion, setSpecificVersion] = useState(latestRevision) + const { + searchParamsObject: searchParams, + upsertSearchParams, + removeSearchParam, + } = useCustomSearchParams() + const { + tab, + mode: viewMode = VIEW_MODE_VIEW_ONLY, + "user-defined": userDefinedValue, + } = searchParams - //@ts-ignore - const diffElement = useRef({}) + //@ts-ignore + const diffElement = useRef({}) - const handleChanged = (e: ChangeEvent) => { - upsertSearchParams('mode', e.target.value) - } + const handleChanged = (e: ChangeEvent) => { + upsertSearchParams("mode", e.target.value) + } - const handleUserDefinedCheckbox = (e: ChangeEvent) => { - if (e.target.checked) { - upsertSearchParams('user-defined', `${e.target.checked}`) - } else { - removeSearchParam('user-defined') - } + const handleUserDefinedCheckbox = (e: ChangeEvent) => { + if (e.target.checked) { + upsertSearchParams("user-defined", `${e.target.checked}`) + } else { + removeSearchParam("user-defined") } - const revisionInt = parseInt(params.revision || '', 10) - const hasMultipleRevisions = revisionInt > 1 + } + const revisionInt = parseInt(params.revision || "", 10) + const hasMultipleRevisions = revisionInt > 1 - const additionalParams = useMemo(() => { - let additionalParamStr = '' - if (userDefinedValue) { - additionalParamStr += '&userDefined=true' - } - if (viewMode === VIEW_MODE_DIFF_PREV && hasMultipleRevisions) { - additionalParamStr += `&revisionDiff=${revisionInt - 1}` - } - const specificRevisionInt = parseInt(specificVersion.toString() || '', 10) - if ( - viewMode === VIEW_MODE_DIFF_SPECIFIC && - hasMultipleRevisions && - !Number.isNaN(specificRevisionInt) - ) { - additionalParamStr += `&revisionDiff=${specificVersion}` - } - return additionalParamStr - }, [ - viewMode, - userDefinedValue, - specificVersion, - revisionInt, - hasMultipleRevisions, - ]) - const hasRevisionToDiff = !!additionalParams + const additionalParams = useMemo(() => { + let additionalParamStr = "" + if (userDefinedValue) { + additionalParamStr += "&userDefined=true" + } + if (viewMode === VIEW_MODE_DIFF_PREV && hasMultipleRevisions) { + additionalParamStr += `&revisionDiff=${revisionInt - 1}` + } + const specificRevisionInt = parseInt(specificVersion.toString() || "", 10) + if ( + viewMode === VIEW_MODE_DIFF_SPECIFIC && + hasMultipleRevisions && + !Number.isNaN(specificRevisionInt) + ) { + additionalParamStr += `&revisionDiff=${specificVersion}` + } + return additionalParamStr + }, [ + viewMode, + userDefinedValue, + specificVersion, + revisionInt, + hasMultipleRevisions, + ]) + const hasRevisionToDiff = !!additionalParams - const { - data, - isLoading, - isSuccess: fetchedDataSuccessfully, - } = useGetReleaseInfoByType({ ...params, tab }, additionalParams) + const { + data, + isLoading, + isSuccess: fetchedDataSuccessfully, + } = useGetReleaseInfoByType({ ...params, tab }, additionalParams) - const content = useMemo(() => { - if ( - data && - !isLoading && - (viewMode === VIEW_MODE_VIEW_ONLY || !hasRevisionToDiff) - ) { - return hljs.highlight(data, { language: 'yaml' }).value - } - if ( - fetchedDataSuccessfully && - !data && - viewMode === VIEW_MODE_VIEW_ONLY - ) { - return 'No value to display' - } - return '' - }, [data, viewMode, isLoading]) + const content = useMemo(() => { + if ( + data && + !isLoading && + (viewMode === VIEW_MODE_VIEW_ONLY || !hasRevisionToDiff) + ) { + return hljs.highlight(data, { language: "yaml" }).value + } + if (fetchedDataSuccessfully && !data && viewMode === VIEW_MODE_VIEW_ONLY) { + return "No value to display" + } + return "" + }, [data, viewMode, isLoading]) - useEffect(() => { - if ( - viewMode !== VIEW_MODE_VIEW_ONLY && - hasRevisionToDiff && - data && - !isLoading - ) { - const diff2htmlUi = new Diff2HtmlUI( - diffElement!.current!, - data, - diffConfiguration - ) - diff2htmlUi.draw() - diff2htmlUi.highlightCode() - } else if (viewMode === VIEW_MODE_VIEW_ONLY && diffElement.current) { - diffElement.current.innerHTML = '' - } else if ( - fetchedDataSuccessfully && - (!hasRevisionToDiff || !data) && - diffElement.current - ) { - diffElement.current.innerHTML = 'No differences to display' - } - }, [ - viewMode, - hasRevisionToDiff, + useEffect(() => { + if ( + viewMode !== VIEW_MODE_VIEW_ONLY && + hasRevisionToDiff && + data && + !isLoading + ) { + const diff2htmlUi = new Diff2HtmlUI( + diffElement!.current!, data, - isLoading, - fetchedDataSuccessfully, - diffElement, - ]) + diffConfiguration + ) + diff2htmlUi.draw() + diff2htmlUi.highlightCode() + } else if (viewMode === VIEW_MODE_VIEW_ONLY && diffElement.current) { + diffElement.current.innerHTML = "" + } else if ( + fetchedDataSuccessfully && + (!hasRevisionToDiff || !data) && + diffElement.current + ) { + diffElement.current.innerHTML = "No differences to display" + } + }, [ + viewMode, + hasRevisionToDiff, + data, + isLoading, + fetchedDataSuccessfully, + diffElement, + ]) - return ( -
-
-
- - -
-
- - -
-
- - -
- {includeUserDefineOnly && ( -
- - -
- )} + return ( +
+
+
+ + +
+
+ + +
+
+ + +
+ {includeUserDefineOnly && ( +
+ + +
+ )} +
+ {isLoading ? : ""} + {viewMode === VIEW_MODE_VIEW_ONLY && content ? ( +
+
{parse(content)}
- ) + ) : ( + "" + )} +
+
+ ) } export default RevisionDiff diff --git a/dashboard/src/components/revision/RevisionResource.tsx b/dashboard/src/components/revision/RevisionResource.tsx index eb2bf885..39558d5d 100644 --- a/dashboard/src/components/revision/RevisionResource.tsx +++ b/dashboard/src/components/revision/RevisionResource.tsx @@ -1,245 +1,222 @@ -import { useEffect, useState } from 'react' -import { useParams } from 'react-router-dom' -import hljs from 'highlight.js' -import { RiExternalLinkLine } from 'react-icons/ri' +import { useEffect, useState } from "react" +import { useParams } from "react-router-dom" +import hljs from "highlight.js" +import { RiExternalLinkLine } from "react-icons/ri" import { - StructuredResources, - useGetResourceDescription, - useGetResources, -} from '../../API/releases' -import closeIcon from '../../assets/close.png' + StructuredResources, + useGetResourceDescription, + useGetResources, +} from "../../API/releases" +import closeIcon from "../../assets/close.png" -import Drawer from 'react-modern-drawer' -import 'react-modern-drawer/dist/index.css' +import Drawer from "react-modern-drawer" +import "react-modern-drawer/dist/index.css" -import Button from '../Button' -import Badge, { BadgeCode, getBadgeType } from '../Badge' -import Spinner from '../Spinner' -import { Troubleshoot } from '../Troubleshoot' +import Button from "../Button" +import Badge, { BadgeCode, getBadgeType } from "../Badge" +import Spinner from "../Spinner" +import { Troubleshoot } from "../Troubleshoot" interface Props { - isLatest: boolean + isLatest: boolean } export default function RevisionResource({ isLatest }: Props) { - const { namespace = '', chart = '' } = useParams() - const { data: resources, isLoading } = useGetResources(namespace, chart) - const interestingResources = ['STATEFULSET', 'DEAMONSET', 'DEPLOYMENT'] + const { namespace = "", chart = "" } = useParams() + const { data: resources, isLoading } = useGetResources(namespace, chart) + const interestingResources = ["STATEFULSET", "DEAMONSET", "DEPLOYMENT"] - return ( - - - - - - - - - - - {isLoading ? ( - - ) : ( - - {resources?.length ? ( - resources - .sort(function (a, b) { - return ( - interestingResources.indexOf( - a.kind.toUpperCase() - ) - - interestingResources.indexOf( - b.kind.toUpperCase() - ) - ) - }) - .reverse() - .map((resource: StructuredResources) => ( - - )) - ) : ( - -
- Looks like you don't have any resources.{' '} - -
- - )} - - )} -
RESOURCE TYPENAMESTATUSSTATUS MESSAGE
- ) + return ( + + + + + + + + + + + {isLoading ? ( + + ) : ( + + {resources?.length ? ( + resources + .sort(function (a, b) { + return ( + interestingResources.indexOf(a.kind.toUpperCase()) - + interestingResources.indexOf(b.kind.toUpperCase()) + ) + }) + .reverse() + .map((resource: StructuredResources) => ( + + )) + ) : ( + +
+ Looks like you don't have any resources.{" "} + +
+ + )} + + )} +
RESOURCE TYPENAMESTATUSSTATUS MESSAGE
+ ) } const ResourceRow = ({ - resource, - isLatest, + resource, + isLatest, }: { - resource: StructuredResources - isLatest: boolean + resource: StructuredResources + isLatest: boolean }) => { - const { - kind, - metadata: { name }, - status: { conditions }, - } = resource - const [isOpen, setIsOpen] = useState(false) - const toggleDrawer = () => { - setIsOpen((prevState) => !prevState) - } - const { reason = '', status = '', message = '' } = conditions?.[0] || {} + const { + kind, + metadata: { name }, + status: { conditions }, + } = resource + const [isOpen, setIsOpen] = useState(false) + const toggleDrawer = () => { + setIsOpen((prevState) => !prevState) + } + const { reason = "", status = "", message = "" } = conditions?.[0] || {} - const badgeType = getBadgeType(status) + const badgeType = getBadgeType(status) - return ( - <> - - - {kind} - - {name} - - {reason ? {reason} : null} - - -
- {message && ( -
- {message} -
- )} - {(badgeType === 'error' || badgeType === 'warning') && ( - - )} -
- - - {isLatest && reason !== 'NotFound' ? ( -
- -
- ) : null} - - - - {isOpen ? ( - { - setIsOpen(false) - }} - /> - ) : null} - - - ) + return ( + <> + + {kind} + {name} + {reason ? {reason} : null} + +
+ {message && ( +
{message}
+ )} + {(badgeType === "error" || badgeType === "warning") && ( + + )} +
+ + + {isLatest && reason !== "NotFound" ? ( +
+ +
+ ) : null} + + + + {isOpen ? ( + { + setIsOpen(false) + }} + /> + ) : null} + + + ) } const DescribeResource = ({ - resource, - closeDrawer, + resource, + closeDrawer, }: { - resource: StructuredResources - closeDrawer: () => void + resource: StructuredResources + closeDrawer: () => void }) => { - const { - kind, - metadata: { name }, - status: { conditions }, - } = resource + const { + kind, + metadata: { name }, + status: { conditions }, + } = resource - const { status, reason = '' } = conditions?.[0] || {} - const { namespace = '', chart = '' } = useParams() - const { data, isLoading } = useGetResourceDescription( - resource.kind, - namespace, - chart - ) - const [yamlFormattedData, setYamlFormattedData] = useState('') + const { status, reason = "" } = conditions?.[0] || {} + const { namespace = "", chart = "" } = useParams() + const { data, isLoading } = useGetResourceDescription( + resource.kind, + namespace, + chart + ) + const [yamlFormattedData, setYamlFormattedData] = useState("") - useEffect(() => { - if (data) { - const val = hljs.highlight(data, { language: 'yaml' }).value - setYamlFormattedData(val) - } - }, [data]) + useEffect(() => { + if (data) { + const val = hljs.highlight(data, { language: "yaml" }).value + setYamlFormattedData(val) + } + }, [data]) - const badgeType = getBadgeType(status) - return ( - <> -
-
-
-

- {name} -

- {reason} -
-

- {kind} -

-
+ const badgeType = getBadgeType(status) + return ( + <> +
+
+
+

{name}

+ {reason} +
+

{kind}

+
- -
+ +
- {isLoading ? ( - - ) : ( -
-
-                
- )} - - ) + {isLoading ? ( + + ) : ( +
+
+        
+ )} + + ) } diff --git a/dashboard/src/components/revision/RevisionsList.tsx b/dashboard/src/components/revision/RevisionsList.tsx index d0cd16b4..9ec8bb75 100644 --- a/dashboard/src/components/revision/RevisionsList.tsx +++ b/dashboard/src/components/revision/RevisionsList.tsx @@ -1,114 +1,97 @@ -import { BsArrowDownRight, BsArrowUpRight } from 'react-icons/bs' -import { useParams } from 'react-router-dom' -import { compare } from 'compare-versions' +import { BsArrowDownRight, BsArrowUpRight } from "react-icons/bs" +import { useParams } from "react-router-dom" +import { compare } from "compare-versions" -import { ReleaseRevision } from '../../data/types' -import { getAge } from '../../timeUtils' -import StatusLabel from '../common/StatusLabel' -import useNavigateWithSearchParams from '../../hooks/useNavigateWithSearchParams' -import { DateTime } from 'luxon' +import { ReleaseRevision } from "../../data/types" +import { getAge } from "../../timeUtils" +import StatusLabel from "../common/StatusLabel" +import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams" +import { DateTime } from "luxon" type RevisionsListProps = { - releaseRevisions: ReleaseRevision[] - selectedRevision: number + releaseRevisions: ReleaseRevision[] + selectedRevision: number } export default function RevisionsList({ - releaseRevisions, - selectedRevision, + releaseRevisions, + selectedRevision, }: RevisionsListProps) { - const navigate = useNavigateWithSearchParams() - const { context, namespace, chart } = useParams() - const changeRelease = (newRevision: number) => { - navigate( - `/${context}/${namespace}/${chart}/installed/revision/${newRevision}` - ) - } - - return ( - <> - {releaseRevisions?.map((release, idx) => { - const hasMultipleReleases = - releaseRevisions.length > 1 && - idx < releaseRevisions.length - 1 - const prevRelease = hasMultipleReleases - ? releaseRevisions[idx + 1] - : null - const isRollback = - release.description.startsWith('Rollback to ') - return ( -
changeRelease(release.revision)} - key={release.revision} - className={`flex flex-col border rounded-md mx-5 p-2 gap-4 cursor-pointer ${ - release.revision === selectedRevision - ? 'border-[#007bff] bg-white' - : 'border-[#DCDDDF] bg-[#F4F7FA]' - }`} - > -
- - - #{release.revision} - -
-
-
- {prevRelease - ? compare( - prevRelease.chart_ver, - release.chart_ver, - '!=' - ) && ( - <> - - {prevRelease.chart_ver} - - {compare( - prevRelease.chart_ver, - release.chart_ver, - '>' - ) ? ( - - ) : ( - - )} - {release.chart_ver} - - ) - : ''} -
- - AGE:{getAge(release, releaseRevisions[idx - 1])} - -
-
- ) - })} - + const navigate = useNavigateWithSearchParams() + const { context, namespace, chart } = useParams() + const changeRelease = (newRevision: number) => { + navigate( + `/${context}/${namespace}/${chart}/installed/revision/${newRevision}` ) + } + + return ( + <> + {releaseRevisions?.map((release, idx) => { + const hasMultipleReleases = + releaseRevisions.length > 1 && idx < releaseRevisions.length - 1 + const prevRelease = hasMultipleReleases + ? releaseRevisions[idx + 1] + : null + const isRollback = release.description.startsWith("Rollback to ") + return ( +
changeRelease(release.revision)} + key={release.revision} + className={`flex flex-col border rounded-md mx-5 p-2 gap-4 cursor-pointer ${ + release.revision === selectedRevision + ? "border-[#007bff] bg-white" + : "border-[#DCDDDF] bg-[#F4F7FA]" + }`} + > +
+ + #{release.revision} +
+
+
+ {prevRelease + ? compare(prevRelease.chart_ver, release.chart_ver, "!=") && ( + <> + + {prevRelease.chart_ver} + + {compare( + prevRelease.chart_ver, + release.chart_ver, + ">" + ) ? ( + + ) : ( + + )} + {release.chart_ver} + + ) + : ""} +
+ + AGE:{getAge(release, releaseRevisions[idx - 1])} + +
+
+ ) + })} + + ) } diff --git a/dashboard/src/context/AppContext.tsx b/dashboard/src/context/AppContext.tsx index 53f4c8d0..4839b73a 100644 --- a/dashboard/src/context/AppContext.tsx +++ b/dashboard/src/context/AppContext.tsx @@ -1,44 +1,40 @@ -import { createContext, useState, useContext } from 'react' +import { createContext, useState, useContext } from "react" export interface AppContextData { - selectedRepo: string - setSelectedRepo: (newValue: string) => void - clusterMode: boolean - setClusterMode: (newValue: boolean) => void + selectedRepo: string + setSelectedRepo: (newValue: string) => void + clusterMode: boolean + setClusterMode: (newValue: boolean) => void } const AppContext = createContext(undefined) export const useAppContext = (): AppContextData => { - const context = useContext(AppContext) - if (!context) { - throw new Error( - 'useAppContext must be used within a AppContextProvider' - ) - } - return context + const context = useContext(AppContext) + if (!context) { + throw new Error("useAppContext must be used within a AppContextProvider") + } + return context } export const AppContextProvider = ({ - children, + children, }: { - children: React.ReactNode + children: React.ReactNode }) => { - const [selectedRepo, setSelectedRepo] = useState('') - const [clusterMode, setClusterMode] = useState(false) + const [selectedRepo, setSelectedRepo] = useState("") + const [clusterMode, setClusterMode] = useState(false) - const contextValue: AppContextData = { - selectedRepo, - setSelectedRepo, - clusterMode, - setClusterMode, - } + const contextValue: AppContextData = { + selectedRepo, + setSelectedRepo, + clusterMode, + setClusterMode, + } - return ( - - {children} - - ) + return ( + {children} + ) } export default AppContext diff --git a/dashboard/src/context/ErrorModalContext.tsx b/dashboard/src/context/ErrorModalContext.tsx index 55afd2aa..a1ab2f92 100644 --- a/dashboard/src/context/ErrorModalContext.tsx +++ b/dashboard/src/context/ErrorModalContext.tsx @@ -1,16 +1,16 @@ -import { createContext } from 'react' +import { createContext } from "react" export interface ErrorAlert { - title?: string - msg: string + title?: string + msg: string } export const ErrorModalContext = createContext<{ - shouldShowErrorModal?: ErrorAlert - setShowErrorModal: (toggle?: ErrorAlert) => void + shouldShowErrorModal?: ErrorAlert + setShowErrorModal: (toggle?: ErrorAlert) => void }>({ - shouldShowErrorModal: undefined, - // in this case we allow Unexpected empty method - //eslint-disable-next-line @typescript-eslint/no-empty-function - setShowErrorModal: (toggle?: ErrorAlert) => {}, + shouldShowErrorModal: undefined, + // in this case we allow Unexpected empty method + //eslint-disable-next-line @typescript-eslint/no-empty-function + setShowErrorModal: (toggle?: ErrorAlert) => {}, }) diff --git a/dashboard/src/data/types.ts b/dashboard/src/data/types.ts index d90fca77..c5ccf6fb 100644 --- a/dashboard/src/data/types.ts +++ b/dashboard/src/data/types.ts @@ -1,120 +1,120 @@ export type Chart = { - id: string + id: string + name: string + home: string + sources: string[] + version: string + description: string + keywords: string[] + maintainers: { + name: string + url: string + }[] + icon: string + apiVersion: string + appVersion: string + annotations: { + category: string + licenses: string + } + dependencies: { name: string - home: string - sources: string[] version: string - description: string - keywords: string[] - maintainers: { - name: string - url: string - }[] - icon: string - apiVersion: string - appVersion: string - annotations: { - category: string - licenses: string - } - dependencies: { - name: string - version: string - repository: string - condition?: string - tags?: string[] - }[] - urls: string[] - created: string - digest: string + repository: string + condition?: string + tags?: string[] + }[] + urls: string[] + created: string + digest: string } export type Release = { - id: string - name: string - namespace: string - revision: number - updated: string - status: string - chart: string - chart_name: string - chart_ver: string - app_version: string - icon: string - description: string - has_tests: boolean - chartName: string // duplicated in some cases in the backend, we need to resolve this - chartVersion: string // duplicated in some cases in the backend, we need to resolve this + id: string + name: string + namespace: string + revision: number + updated: string + status: string + chart: string + chart_name: string + chart_ver: string + app_version: string + icon: string + description: string + has_tests: boolean + chartName: string // duplicated in some cases in the backend, we need to resolve this + chartVersion: string // duplicated in some cases in the backend, we need to resolve this } export type ReleaseHealthStatus = { - kind: string - apiVersion: string - metadata: { - name: string - namespace: string - creationTimestamp?: string - labels: { - [key: string]: string - } - } - spec: unknown - status: { - conditions: [ - { - type: string - status: string - lastProbeTime: string - lastTransitionTime?: string - reason: string - } - ] + kind: string + apiVersion: string + metadata: { + name: string + namespace: string + creationTimestamp?: string + labels: { + [key: string]: string } + } + spec: unknown + status: { + conditions: [ + { + type: string + status: string + lastProbeTime: string + lastTransitionTime?: string + reason: string + } + ] + } } export type Repository = { - name: string - url: string + name: string + url: string } export type ReleaseRevision = { - revision: number - updated: string - status: string - chart: string - app_version: string - description: string - chart_name: string - chart_ver: string - has_tests: boolean + revision: number + updated: string + status: string + chart: string + app_version: string + description: string + chart_name: string + chart_ver: string + has_tests: boolean } export type Cluster = { - IsCurrent: boolean - Name: string - Cluster: string - AuthInfo: string - Namespace: string + IsCurrent: boolean + Name: string + Cluster: string + AuthInfo: string + Namespace: string } export type Status = { - CurVer: string - LatestVer: string - Analytics: boolean - CacheHitRatio: number - ClusterMode: boolean + CurVer: string + LatestVer: string + Analytics: boolean + CacheHitRatio: number + ClusterMode: boolean } export type ChartVersion = { - name: string - version: string - app_version: string - description: string - installed_namespace: string - installed_name: string - repository: string - urls: string[] - isSuggestedRepo: boolean + name: string + version: string + app_version: string + description: string + installed_namespace: string + installed_name: string + repository: string + urls: string[] + isSuggestedRepo: boolean } export type NonEmptyArray = [T, ...T[]] diff --git a/dashboard/src/hooks/useAlertError.ts b/dashboard/src/hooks/useAlertError.ts index fd2573d9..4bec011c 100644 --- a/dashboard/src/hooks/useAlertError.ts +++ b/dashboard/src/hooks/useAlertError.ts @@ -1,10 +1,10 @@ -import { useContext } from 'react' -import { ErrorModalContext } from '../context/ErrorModalContext' +import { useContext } from "react" +import { ErrorModalContext } from "../context/ErrorModalContext" function useAlertError() { - const { setShowErrorModal, shouldShowErrorModal } = - useContext(ErrorModalContext) - return { setShowErrorModal, shouldShowErrorModal } + const { setShowErrorModal, shouldShowErrorModal } = + useContext(ErrorModalContext) + return { setShowErrorModal, shouldShowErrorModal } } export default useAlertError diff --git a/dashboard/src/hooks/useCustomSearchParams.ts b/dashboard/src/hooks/useCustomSearchParams.ts index 5a852cab..f89b8631 100644 --- a/dashboard/src/hooks/useCustomSearchParams.ts +++ b/dashboard/src/hooks/useCustomSearchParams.ts @@ -1,34 +1,34 @@ -import { useCallback } from 'react' -import { useSearchParams } from 'react-router-dom' +import { useCallback } from "react" +import { useSearchParams } from "react-router-dom" const useCustomSearchParams = () => { - const [search, setSearch] = useSearchParams() - const searchAsObject: { [key: string]: string } = Object.fromEntries( - new URLSearchParams(search) - ) + const [search, setSearch] = useSearchParams() + const searchAsObject: { [key: string]: string } = Object.fromEntries( + new URLSearchParams(search) + ) - const upsertSearchParams = useCallback( - (k: string, value: string) => { - const copySearchParams = new URLSearchParams(search) - copySearchParams.set(k, value) - setSearch(copySearchParams) - return copySearchParams - }, - [search] - ) + const upsertSearchParams = useCallback( + (k: string, value: string) => { + const copySearchParams = new URLSearchParams(search) + copySearchParams.set(k, value) + setSearch(copySearchParams) + return copySearchParams + }, + [search] + ) - const removeSearchParam = (k: string) => { - const copySearchParams = new URLSearchParams(search) - copySearchParams.delete(k) - setSearch(copySearchParams) - } + const removeSearchParam = (k: string) => { + const copySearchParams = new URLSearchParams(search) + copySearchParams.delete(k) + setSearch(copySearchParams) + } - return { - searchParamsObject: searchAsObject, - setSearchParams: setSearch, - upsertSearchParams, - removeSearchParam, - } + return { + searchParamsObject: searchAsObject, + setSearchParams: setSearch, + upsertSearchParams, + removeSearchParam, + } } export default useCustomSearchParams diff --git a/dashboard/src/hooks/useNavigateWithSearchParams.ts b/dashboard/src/hooks/useNavigateWithSearchParams.ts index e056e2b4..05acfadc 100644 --- a/dashboard/src/hooks/useNavigateWithSearchParams.ts +++ b/dashboard/src/hooks/useNavigateWithSearchParams.ts @@ -1,13 +1,13 @@ -import { useLocation, useNavigate } from 'react-router-dom' +import { useLocation, useNavigate } from "react-router-dom" const useNavigateWithSearchParams = () => { - const navigate = useNavigate() - const { search } = useLocation() - const navigateWithSearchParams = (url: string, ...restArgs: any[]) => { - navigate(url + search, ...restArgs) - } + const navigate = useNavigate() + const { search } = useLocation() + const navigateWithSearchParams = (url: string, ...restArgs: any[]) => { + navigate(url + search, ...restArgs) + } - return navigateWithSearchParams + return navigateWithSearchParams } export default useNavigateWithSearchParams diff --git a/dashboard/src/index.css b/dashboard/src/index.css index 7fc096f7..beaa8510 100644 --- a/dashboard/src/index.css +++ b/dashboard/src/index.css @@ -1,70 +1,70 @@ -@import url('https://fonts.googleapis.com/css2?family=Roboto&family=Inter&family=Poppins:wght@600&family=Poppins:wght@500&family=Inter:wght@500&family=Roboto+Slab:wght@400&family=Roboto+Slab:wght@700&family=Roboto:wght@700&family=Roboto:wght@500'); +@import url("https://fonts.googleapis.com/css2?family=Roboto&family=Inter&family=Poppins:wght@600&family=Poppins:wght@500&family=Inter:wght@500&family=Roboto+Slab:wght@400&family=Roboto+Slab:wght@700&family=Roboto:wght@700&family=Roboto:wght@500"); @tailwind base; @tailwind components; @tailwind utilities; body { - color: #3d4048; - font-family: 'Roboto', serif; + color: #3d4048; + font-family: "Roboto", serif; } button, input, input::placeholder { - font-family: 'Roboto', serif; + font-family: "Roboto", serif; } @layer components { - .card { - background-color: theme('colors.white'); - border-radius: theme('borderRadius.lg'); - border: theme('borderWidth.DEFAULT') solid theme('colors.gray.200'); - padding: theme('spacing.6'); - box-shadow: theme('boxShadow.xl'); - padding-top: theme('spacing.12'); - } - .card-hover { - background-color: theme('colors.gray.100'); - border-radius: theme('borderRadius.lg'); - padding: theme('spacing.6'); - box-shadow: theme('boxShadow.xl'); - } - .card-active { - background-color: theme('colors.gray.200'); - border-radius: theme('borderRadius.lg'); - padding: theme('spacing.6'); - box-shadow: theme('boxShadow.xl'); - } - .card-disabled { - background-color: theme('colors.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); - } + .card { + background-color: theme("colors.white"); + border-radius: theme("borderRadius.lg"); + border: theme("borderWidth.DEFAULT") solid theme("colors.gray.200"); + padding: theme("spacing.6"); + box-shadow: theme("boxShadow.xl"); + padding-top: theme("spacing.12"); + } + .card-hover { + background-color: theme("colors.gray.100"); + border-radius: theme("borderRadius.lg"); + padding: theme("spacing.6"); + box-shadow: theme("boxShadow.xl"); + } + .card-active { + background-color: theme("colors.gray.200"); + border-radius: theme("borderRadius.lg"); + padding: theme("spacing.6"); + box-shadow: theme("boxShadow.xl"); + } + .card-disabled { + background-color: theme("colors.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); + } } pre, code { - font-family: SFMono-Regular, Menlo; - font-size: 12.5px; + font-family: SFMono-Regular, Menlo; + font-size: 12.5px; } #portal { - top: 0; - width: 40%; - left: 30%; - position: absolute; + top: 0; + width: 40%; + left: 30%; + position: absolute; } .require:after { - content: ' *'; - color: red; + content: " *"; + color: red; } .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); } diff --git a/dashboard/src/layout/Header.tsx b/dashboard/src/layout/Header.tsx index 6e509697..ebba3a34 100644 --- a/dashboard/src/layout/Header.tsx +++ b/dashboard/src/layout/Header.tsx @@ -1,166 +1,166 @@ -import { useLocation, useNavigate, useParams } from 'react-router-dom' -import LogoHeader from '../assets/logo-header.svg' -import DropDown from '../components/common/DropDown' -import WatcherIcon from '../assets/k8s-watcher.svg' -import ShutDownButton from '../components/ShutDownButton' +import { useLocation, useNavigate, useParams } from "react-router-dom" +import LogoHeader from "../assets/logo-header.svg" +import DropDown from "../components/common/DropDown" +import WatcherIcon from "../assets/k8s-watcher.svg" +import ShutDownButton from "../components/ShutDownButton" import { - BsSlack, - BsGithub, - BsArrowRepeat, - BsBraces, - BsBoxArrowUpRight, -} from 'react-icons/bs' -import { useGetApplicationStatus } from '../API/other' -import LinkWithSearchParams from '../components/LinkWithSearchParams' -import apiService from '../API/apiService' -import { useAppContext } from '../context/AppContext' + BsSlack, + BsGithub, + BsArrowRepeat, + BsBraces, + BsBoxArrowUpRight, +} from "react-icons/bs" +import { useGetApplicationStatus } from "../API/other" +import LinkWithSearchParams from "../components/LinkWithSearchParams" +import apiService from "../API/apiService" +import { useAppContext } from "../context/AppContext" export default function Header() { - const { clusterMode, setClusterMode } = useAppContext() - const navigate = useNavigate() - const { data: statusData } = useGetApplicationStatus({ - onSuccess: (data) => { - setClusterMode(data.ClusterMode) - }, - }) - const { context } = useParams() - const location = useLocation() - const openSupportChat = () => { - window.open('https://app.slack.com/client/T03Q4H8PCRW', '_blank') - } - - const openProjectPage = () => { - window.open('https://github.com/komodorio/helm-dashboard', '_blank') - } + const { clusterMode, setClusterMode } = useAppContext() + const navigate = useNavigate() + const { data: statusData } = useGetApplicationStatus({ + onSuccess: (data) => { + setClusterMode(data.ClusterMode) + }, + }) + const { context } = useParams() + const location = useLocation() + const openSupportChat = () => { + window.open("https://app.slack.com/client/T03Q4H8PCRW", "_blank") + } - const resetCache = async () => { - try { - await apiService.fetchWithDefaults('/api/cache', { - method: 'DELETE', - }) - window.location.reload() - } catch (error) { - console.error(error) - } - } + const openProjectPage = () => { + window.open("https://github.com/komodorio/helm-dashboard", "_blank") + } - const openAPI = () => { - window.open('/#/docs', '_blank') + const resetCache = async () => { + try { + await apiService.fetchWithDefaults("/api/cache", { + method: "DELETE", + }) + window.location.reload() + } catch (error) { + console.error(error) } + } - const getBtnStyle = (identifier: string) => - `text-md py-2.5 px-5 ${ - location.pathname.includes(`/${identifier}`) - ? ' text-[#1347FF] rounded-sm bg-[#EBEFFF]' - : '' - }` + const openAPI = () => { + window.open("/#/docs", "_blank") + } - return ( -
-
- - helm dashboard logo - - -
-
    -
  • - - Installed - -
  • -
  • - - Repository - -
  • -
  • - , - onClick: openSupportChat, - }, - { - id: '2', - text: 'Project Page', - icon: , - onClick: openProjectPage, - }, - { id: '3', isSeparator: true }, - { - id: '4', - text: 'Reset Cache', - icon: , - onClick: resetCache, - }, - { - id: '5', - text: 'REST API', - icon: , - onClick: openAPI, - }, - { id: '6', isSeparator: true }, - { - id: '7', - text: `version ${statusData?.CurVer}`, - isDisabled: true, - }, - ]} - /> -
  • - {statusData?.LatestVer ? ( -
  • - - Upgrade to {statusData?.LatestVer} - -
  • - ) : null} -
-
-
-
-
- -
- -
- Upgrade your HELM experience - Free - -
-
- -
-
+ const getBtnStyle = (identifier: string) => + `text-md py-2.5 px-5 ${ + location.pathname.includes(`/${identifier}`) + ? " text-[#1347FF] rounded-sm bg-[#EBEFFF]" + : "" + }` - - {!clusterMode ? : null} -
+ return ( +
+
+ + helm dashboard logo + + +
+
    +
  • + + Installed + +
  • +
  • + + Repository + +
  • +
  • + , + onClick: openSupportChat, + }, + { + id: "2", + text: "Project Page", + icon: , + onClick: openProjectPage, + }, + { id: "3", isSeparator: true }, + { + id: "4", + text: "Reset Cache", + icon: , + onClick: resetCache, + }, + { + id: "5", + text: "REST API", + icon: , + onClick: openAPI, + }, + { id: "6", isSeparator: true }, + { + id: "7", + text: `version ${statusData?.CurVer}`, + isDisabled: true, + }, + ]} + /> +
  • + {statusData?.LatestVer ? ( +
  • + + Upgrade to {statusData?.LatestVer} + +
  • + ) : null} +
+
+
+
+
+ +
+ +
+ Upgrade your HELM experience - Free + +
+
+ +
- ) + + + {!clusterMode ? : null} +
+
+ ) } diff --git a/dashboard/src/layout/Sidebar.tsx b/dashboard/src/layout/Sidebar.tsx index a9699630..03979704 100644 --- a/dashboard/src/layout/Sidebar.tsx +++ b/dashboard/src/layout/Sidebar.tsx @@ -1,27 +1,27 @@ -import '../App.css' +import "../App.css" function Sidebar(): JSX.Element { - return ( -
-

Repositories

-
-
- -
-
- -
-
- -

Some text that describes chart

+ return ( +
+

Repositories

+
+
+
- ) +
+ +
+
+ +

Some text that describes chart

+
+ ) } export default Sidebar diff --git a/dashboard/src/main.tsx b/dashboard/src/main.tsx index 013b143a..fd41c35a 100644 --- a/dashboard/src/main.tsx +++ b/dashboard/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import './index.css' +import React from "react" +import ReactDOM from "react-dom/client" +import App from "./App" +import "./index.css" -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + ) diff --git a/dashboard/src/pages/DocsPage.tsx b/dashboard/src/pages/DocsPage.tsx index 506540da..6b706a82 100644 --- a/dashboard/src/pages/DocsPage.tsx +++ b/dashboard/src/pages/DocsPage.tsx @@ -1,723 +1,712 @@ -import SwaggerUI from 'swagger-ui-react' -import 'swagger-ui-react/swagger-ui.css' +import SwaggerUI from "swagger-ui-react" +import "swagger-ui-react/swagger-ui.css" const DocsPage = () => { - return + return } export default DocsPage const obj = { - openapi: '3.0.3', - info: { - title: 'Helm Dashboard API', - version: '', + openapi: "3.0.3", + info: { + title: "Helm Dashboard API", + version: "", + }, + tags: [ + { + name: "Releases", }, - tags: [ - { - name: 'Releases', + { + name: "Repositories", + }, + { + name: "K8s", + }, + { + name: "Scanners", + }, + { + name: "Miscellaneous", + }, + ], + paths: { + "/api/helm/releases": { + get: { + tags: ["Releases"], + description: "Get list of installed releases", + responses: { + "200": { + description: "Returns list of installed releases", + }, }, + }, + }, + "/api/helm/releases/{ns}": { + parameters: [ { - name: 'Repositories', + name: "ns", + in: "path", + description: + "Name of kubernetes namespace, use '[emtpy]' if you want to use k8s context default", }, - { - name: 'K8s', + ], + post: { + tags: ["Releases"], + description: "Install new release", + requestBody: { + content: { + "multipart/form-data": { + schema: { + type: "object", + properties: { + name: { + type: "string", + required: true, + }, + chart: { + type: "string", + required: true, + }, + version: { + type: "string", + }, + values: { + type: "string", + description: "Text of values.yaml to use", + }, + preview: { + type: "boolean", + }, + }, + }, + }, + }, + }, + responses: { + "200": { + description: "In case preview=true, the preview diff is generated", + content: { + "text/plain": {}, + }, + }, + "202": { + description: + "In case preview=false, the actial install is performed and resulting release object is returned", + content: { + "application/json": {}, + }, + }, }, + }, + }, + "/api/helm/releases/{ns}/{name}": { + parameters: [ { - name: 'Scanners', + name: "ns", + in: "path", + description: "Name of kubernetes namespace", }, { - name: 'Miscellaneous', + name: "name", + in: "path", + description: "Name of Helm release", }, - ], - paths: { - '/api/helm/releases': { - get: { - tags: ['Releases'], - description: 'Get list of installed releases', - responses: { - '200': { - description: 'Returns list of installed releases', - }, - }, + ], + post: { + tags: ["Releases"], + description: "Upgrade/reconfigure existing release", + requestBody: { + content: { + "multipart/form-data": { + schema: { + type: "object", + properties: { + name: { + type: "string", + required: true, + }, + chart: { + type: "string", + required: true, + }, + version: { + type: "string", + }, + values: { + type: "string", + description: "Text of values.yaml to use", + }, + preview: { + type: "boolean", + }, + }, + }, }, + }, }, - '/api/helm/releases/{ns}': { - parameters: [ - { - name: 'ns', - in: 'path', - description: - "Name of kubernetes namespace, use '[emtpy]' if you want to use k8s context default", - }, - ], - post: { - tags: ['Releases'], - description: 'Install new release', - requestBody: { - content: { - 'multipart/form-data': { - schema: { - type: 'object', - properties: { - name: { - type: 'string', - required: true, - }, - chart: { - type: 'string', - required: true, - }, - version: { - type: 'string', - }, - values: { - type: 'string', - description: - 'Text of values.yaml to use', - }, - preview: { - type: 'boolean', - }, - }, - }, - }, - }, - }, - responses: { - '200': { - description: - 'In case preview=true, the preview diff is generated', - content: { - 'text/plain': {}, - }, - }, - '202': { - description: - 'In case preview=false, the actial install is performed and resulting release object is returned', - content: { - 'application/json': {}, - }, - }, - }, + responses: { + "200": { + description: "In case preview=true, the preview diff is generated", + content: { + "text/plain": {}, }, - }, - '/api/helm/releases/{ns}/{name}': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - ], - post: { - tags: ['Releases'], - description: 'Upgrade/reconfigure existing release', - requestBody: { - content: { - 'multipart/form-data': { - schema: { - type: 'object', - properties: { - name: { - type: 'string', - required: true, - }, - chart: { - type: 'string', - required: true, - }, - version: { - type: 'string', - }, - values: { - type: 'string', - description: - 'Text of values.yaml to use', - }, - preview: { - type: 'boolean', - }, - }, - }, - }, - }, - }, - responses: { - '200': { - description: - 'In case preview=true, the preview diff is generated', - content: { - 'text/plain': {}, - }, - }, - '202': { - description: - 'In case preview=false, the actial install is performed and resulting release object is returned', - content: { - 'application/json': {}, - }, - }, - }, + }, + "202": { + description: + "In case preview=false, the actial install is performed and resulting release object is returned", + content: { + "application/json": {}, }, + }, }, - '/api/helm/releases/{ns}/{name}/history': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - ], - get: { - tags: ['Releases'], - description: 'Get revision history for release', - responses: { - '200': { - description: 'List of release revisions', - }, - }, - }, + }, + }, + "/api/helm/releases/{ns}/{name}/history": { + parameters: [ + { + name: "ns", + in: "path", + description: "Name of kubernetes namespace", }, - '/api/helm/releases/{ns}/{name}/manifest': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - { - name: 'revision', - in: 'query', - description: 'Revision to get data from', - }, - { - name: 'revisionDiff', - in: 'query', - description: 'Revision to diff against', - }, - ], - get: { - tags: ['Releases'], - description: 'Get manifest for release', - responses: { - '200': { - description: - 'Manifest text, or diff if revisionDiff is specified', - }, - }, - }, + { + name: "name", + in: "path", + description: "Name of Helm release", }, - '/api/helm/releases/{ns}/{name}/values': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - { - name: 'revision', - in: 'query', - description: 'Revision to get data from', - }, - { - name: 'revisionDiff', - in: 'query', - description: 'Revision to diff against', - }, - { - name: 'userDefined', - in: 'query', - description: - 'If set, only user-defined values will be listed', - }, - ], - get: { - tags: ['Releases'], - description: 'Get values for release', - responses: { - '200': { - description: - 'Values YAML text, or diff if revisionDiff is specified', - }, - }, - }, + ], + get: { + tags: ["Releases"], + description: "Get revision history for release", + responses: { + "200": { + description: "List of release revisions", + }, }, - '/api/helm/releases/{ns}/{name}/notes': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - { - name: 'revision', - in: 'query', - description: 'Revision to get data from', - }, - { - name: 'revisionDiff', - in: 'query', - description: 'Revision to diff against', - }, - ], - get: { - tags: ['Releases'], - description: 'Get textual notes for release', - responses: { - '200': { - description: - 'Notes text, or diff if revisionDiff is specified', - }, - }, - }, + }, + }, + "/api/helm/releases/{ns}/{name}/manifest": { + parameters: [ + { + name: "ns", + in: "path", + description: "Name of kubernetes namespace", }, - '/api/helm/releases/{ns}/{name}/resources': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - { - name: 'health', - in: 'query', - description: 'Flag to query k8s health status of resources', - }, - ], - get: { - tags: ['Releases'], - description: 'List of installed k8s resources for this release', - responses: { - '200': { - description: 'Structured list of resources', - content: { - 'application/json': {}, - }, - }, - }, - }, + { + name: "name", + in: "path", + description: "Name of Helm release", }, - '/api/helm/releases/{ns}/{name}/rollback': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - ], - post: { - tags: ['Releases'], - description: 'Rollback the release to a previous revision', - requestBody: { - content: { - 'application/x-www-form-urlencoded': { - schema: { - type: 'object', - properties: { - revision: { - type: 'integer', - }, - }, - }, - }, - }, - }, - responses: { - '202': { - description: 'Rolled back successfully', - }, - }, - }, + { + name: "revision", + in: "query", + description: "Revision to get data from", }, - '/api/helm/releases/{ns}/{name}/test': { - parameters: [ - { - name: 'ns', - in: 'path', - description: 'Name of kubernetes namespace', - }, - { - name: 'name', - in: 'path', - description: 'Name of Helm release', - }, - ], - post: { - tags: ['Releases'], - description: 'Run the tests on a release', - responses: { - '200': { - description: 'Logs of a test run', - }, - }, - }, + { + name: "revisionDiff", + in: "query", + description: "Revision to diff against", }, - '/api/helm/repositories': { - get: { - tags: ['Repositories'], - description: 'Get list of Helm repositories', - responses: { - '200': { - description: 'Returns list of Helm repositories', - }, - }, - }, - post: { - tags: ['Repositories'], - description: 'Adds new repository', - requestBody: { - content: { - 'application/x-www-form-urlencoded': { - schema: { - type: 'object', - properties: { - name: { - type: 'string', - }, - url: { - type: 'string', - }, - }, - required: ['name', 'url'], - }, - }, - }, - }, - responses: { - '204': { - description: - 'Empty response in case repository were added', - }, - }, - }, + ], + get: { + tags: ["Releases"], + description: "Get manifest for release", + responses: { + "200": { + description: "Manifest text, or diff if revisionDiff is specified", + }, }, - '/api/helm/repositories/{repo}': { - parameters: [ - { - name: 'repo', - in: 'path', - description: 'Name of Helm repository', - }, - ], - get: { - tags: ['Repositories'], - description: 'Get list of charts in repository', - responses: { - '200': { - description: 'Returns list of charts', - }, - }, - }, - post: { - tags: ['Repositories'], - description: 'Update repository from remote', - responses: { - '204': { - description: 'Empty response', - }, - }, - }, - delete: { - tags: ['Repositories'], - description: 'Remove repository', - responses: { - '204': { - description: 'Empty response', - }, - }, - }, + }, + }, + "/api/helm/releases/{ns}/{name}/values": { + parameters: [ + { + name: "ns", + in: "path", + description: "Name of kubernetes namespace", }, - '/api/helm/repositories/latestver': { - parameters: [ - { - name: 'name', - in: 'query', - description: 'Name of Helm chart to search for', - required: true, - }, - ], + { + name: "name", + in: "path", + description: "Name of Helm release", + }, + { + name: "revision", + in: "query", + description: "Revision to get data from", + }, + { + name: "revisionDiff", + in: "query", + description: "Revision to diff against", + }, + { + name: "userDefined", + in: "query", + description: "If set, only user-defined values will be listed", + }, + ], + get: { + tags: ["Releases"], + description: "Get values for release", + responses: { + "200": { description: - 'Find the latest available version of specified chart through all the repositories', - get: { - tags: ['Repositories'], - responses: { - '200': { - description: - 'The object with latest available version is returned', - }, - '204': { - description: - 'In case no matching repository found, the response is empty with status 204', - }, - }, - }, + "Values YAML text, or diff if revisionDiff is specified", + }, }, - '/api/helm/repositories/versions': { - parameters: [ - { - name: 'name', - in: 'query', - description: 'Name of Helm chart to search for', - required: true, - }, - ], - get: { - description: - 'Get the list of versions for specified chart across the repositories', - tags: ['Repositories'], - responses: { - '200': { - description: 'The list if chart versions is returned', - }, - }, - }, + }, + }, + "/api/helm/releases/{ns}/{name}/notes": { + parameters: [ + { + name: "ns", + in: "path", + description: "Name of kubernetes namespace", }, - '/api/helm/repositories/values': { - parameters: [ - { - name: 'chart', - in: 'query', - description: - 'Name of Helm chart to search for, in format of /', - required: true, - }, - { - name: 'version', - in: 'query', - description: 'Version of Helm chart to get values from', - required: true, - }, - ], - get: { - description: 'Get the original values.yaml file for the chart', - tags: ['Repositories'], - responses: { - '200': { - description: 'The content of values.yaml', - }, - }, - }, + { + name: "name", + in: "path", + description: "Name of Helm release", }, - '/api/k8s/contexts': { - get: { - tags: ['K8s'], - description: 'Get list of kubectl contexts configured locally', - responses: { - '200': { - description: 'Returns list of contexts', - }, - }, - }, + { + name: "revision", + in: "query", + description: "Revision to get data from", }, - '/api/k8s/{kind}/get': { - parameters: [ - { - name: 'kind', - in: 'path', - description: 'Kind of kubernetes resource', - }, - { - name: 'name', - in: 'query', - description: 'Name of kubernetes resource', - required: true, - }, - { - name: 'namespace', - in: 'query', - description: 'Namespace of kubernetes resource', - required: true, - }, - ], - get: { - tags: ['K8s'], - responses: { - '200': { - description: 'Returns resources information', - }, - }, - }, + { + name: "revisionDiff", + in: "query", + description: "Revision to diff against", }, - '/api/k8s/{kind}/list': { - parameters: [ - { - name: 'kind', - in: 'path', - description: 'Kind of kubernetes resource', - schema: { - enum: ['namespaces'], - }, - }, - ], - get: { - tags: ['K8s'], - responses: { - '200': { - description: 'Returns list of resources', - }, - }, - }, + ], + get: { + tags: ["Releases"], + description: "Get textual notes for release", + responses: { + "200": { + description: "Notes text, or diff if revisionDiff is specified", + }, }, - '/api/k8s/{kind}/describe': { - parameters: [ - { - name: 'kind', - in: 'path', - description: 'Kind of kubernetes resource', - }, - { - name: 'name', - in: 'query', - description: 'Name of kubernetes resource', - required: true, - }, - { - name: 'namespace', - in: 'query', - description: 'Namespace of kubernetes resource', - required: true, - }, - ], - get: { - tags: ['K8s'], - responses: { - '200': { - content: { - 'text/plain': {}, - }, - description: 'Returns describe text', - }, - }, + }, + }, + "/api/helm/releases/{ns}/{name}/resources": { + parameters: [ + { + name: "ns", + in: "path", + description: "Name of kubernetes namespace", + }, + { + name: "name", + in: "path", + description: "Name of Helm release", + }, + { + name: "health", + in: "query", + description: "Flag to query k8s health status of resources", + }, + ], + get: { + tags: ["Releases"], + description: "List of installed k8s resources for this release", + responses: { + "200": { + description: "Structured list of resources", + content: { + "application/json": {}, }, + }, }, - '/api/scanners': { - get: { - tags: ['Scanners'], - description: 'Get list of discovered scanners', - responses: { - '200': { - description: 'List of scanners', - }, - }, + }, + }, + "/api/helm/releases/{ns}/{name}/rollback": { + parameters: [ + { + name: "ns", + in: "path", + description: "Name of kubernetes namespace", + }, + { + name: "name", + in: "path", + description: "Name of Helm release", + }, + ], + post: { + tags: ["Releases"], + description: "Rollback the release to a previous revision", + requestBody: { + content: { + "application/x-www-form-urlencoded": { + schema: { + type: "object", + properties: { + revision: { + type: "integer", + }, + }, + }, }, + }, }, - '/api/scanners/manifests': { - post: { - tags: ['Scanners'], - description: 'Scan manifests using all applicable scanners', - requestBody: { - content: { - 'multipart/form-data': { - schema: { - type: 'object', - properties: { - manifest: { - type: 'string', - description: 'Text of manifest to scan', - }, - }, - }, - }, - }, - }, - responses: { - '200': { - description: 'Map of scan results per scanner type', - }, - }, + responses: { + "202": { + description: "Rolled back successfully", + }, + }, + }, + }, + "/api/helm/releases/{ns}/{name}/test": { + parameters: [ + { + name: "ns", + in: "path", + description: "Name of kubernetes namespace", + }, + { + name: "name", + in: "path", + description: "Name of Helm release", + }, + ], + post: { + tags: ["Releases"], + description: "Run the tests on a release", + responses: { + "200": { + description: "Logs of a test run", + }, + }, + }, + }, + "/api/helm/repositories": { + get: { + tags: ["Repositories"], + description: "Get list of Helm repositories", + responses: { + "200": { + description: "Returns list of Helm repositories", + }, + }, + }, + post: { + tags: ["Repositories"], + description: "Adds new repository", + requestBody: { + content: { + "application/x-www-form-urlencoded": { + schema: { + type: "object", + properties: { + name: { + type: "string", + }, + url: { + type: "string", + }, + }, + required: ["name", "url"], + }, }, + }, }, - '/api/scanners/resource/{kind}': { - parameters: [ - { - in: 'path', - name: 'kind', - }, - { - in: 'query', - name: 'namespace', - required: true, - }, - { - in: 'query', - name: 'name', - required: true, - }, - ], - get: { - tags: ['Scanners'], - description: 'Scan specified k8s resource in cluster', - responses: { - '200': { - description: - 'Information with scan results per scanner type', - }, - }, + responses: { + "204": { + description: "Empty response in case repository were added", + }, + }, + }, + }, + "/api/helm/repositories/{repo}": { + parameters: [ + { + name: "repo", + in: "path", + description: "Name of Helm repository", + }, + ], + get: { + tags: ["Repositories"], + description: "Get list of charts in repository", + responses: { + "200": { + description: "Returns list of charts", + }, + }, + }, + post: { + tags: ["Repositories"], + description: "Update repository from remote", + responses: { + "204": { + description: "Empty response", + }, + }, + }, + delete: { + tags: ["Repositories"], + description: "Remove repository", + responses: { + "204": { + description: "Empty response", + }, + }, + }, + }, + "/api/helm/repositories/latestver": { + parameters: [ + { + name: "name", + in: "query", + description: "Name of Helm chart to search for", + required: true, + }, + ], + description: + "Find the latest available version of specified chart through all the repositories", + get: { + tags: ["Repositories"], + responses: { + "200": { + description: "The object with latest available version is returned", + }, + "204": { + description: + "In case no matching repository found, the response is empty with status 204", + }, + }, + }, + }, + "/api/helm/repositories/versions": { + parameters: [ + { + name: "name", + in: "query", + description: "Name of Helm chart to search for", + required: true, + }, + ], + get: { + description: + "Get the list of versions for specified chart across the repositories", + tags: ["Repositories"], + responses: { + "200": { + description: "The list if chart versions is returned", + }, + }, + }, + }, + "/api/helm/repositories/values": { + parameters: [ + { + name: "chart", + in: "query", + description: + "Name of Helm chart to search for, in format of /", + required: true, + }, + { + name: "version", + in: "query", + description: "Version of Helm chart to get values from", + required: true, + }, + ], + get: { + description: "Get the original values.yaml file for the chart", + tags: ["Repositories"], + responses: { + "200": { + description: "The content of values.yaml", + }, + }, + }, + }, + "/api/k8s/contexts": { + get: { + tags: ["K8s"], + description: "Get list of kubectl contexts configured locally", + responses: { + "200": { + description: "Returns list of contexts", + }, + }, + }, + }, + "/api/k8s/{kind}/get": { + parameters: [ + { + name: "kind", + in: "path", + description: "Kind of kubernetes resource", + }, + { + name: "name", + in: "query", + description: "Name of kubernetes resource", + required: true, + }, + { + name: "namespace", + in: "query", + description: "Namespace of kubernetes resource", + required: true, + }, + ], + get: { + tags: ["K8s"], + responses: { + "200": { + description: "Returns resources information", + }, + }, + }, + }, + "/api/k8s/{kind}/list": { + parameters: [ + { + name: "kind", + in: "path", + description: "Kind of kubernetes resource", + schema: { + enum: ["namespaces"], + }, + }, + ], + get: { + tags: ["K8s"], + responses: { + "200": { + description: "Returns list of resources", + }, + }, + }, + }, + "/api/k8s/{kind}/describe": { + parameters: [ + { + name: "kind", + in: "path", + description: "Kind of kubernetes resource", + }, + { + name: "name", + in: "query", + description: "Name of kubernetes resource", + required: true, + }, + { + name: "namespace", + in: "query", + description: "Namespace of kubernetes resource", + required: true, + }, + ], + get: { + tags: ["K8s"], + responses: { + "200": { + content: { + "text/plain": {}, }, + description: "Returns describe text", + }, }, - '/': { - delete: { - tags: ['Miscellaneous'], - description: 'Shuts down the Helm Dashboard application', - responses: { - '202': { - description: 'Shutdown command has been accepted', - }, - }, + }, + }, + "/api/scanners": { + get: { + tags: ["Scanners"], + description: "Get list of discovered scanners", + responses: { + "200": { + description: "List of scanners", + }, + }, + }, + }, + "/api/scanners/manifests": { + post: { + tags: ["Scanners"], + description: "Scan manifests using all applicable scanners", + requestBody: { + content: { + "multipart/form-data": { + schema: { + type: "object", + properties: { + manifest: { + type: "string", + description: "Text of manifest to scan", + }, + }, + }, }, + }, }, - '/status': { - get: { - tags: ['Miscellaneous'], - description: 'Gets application status', - responses: { - '200': { - description: 'Returns JSON with some options', - headers: { - 'X-Application-Name': { - description: - 'A string to self-identify the application', - }, - }, - }, - }, + responses: { + "200": { + description: "Map of scan results per scanner type", + }, + }, + }, + }, + "/api/scanners/resource/{kind}": { + parameters: [ + { + in: "path", + name: "kind", + }, + { + in: "query", + name: "namespace", + required: true, + }, + { + in: "query", + name: "name", + required: true, + }, + ], + get: { + tags: ["Scanners"], + description: "Scan specified k8s resource in cluster", + responses: { + "200": { + description: "Information with scan results per scanner type", + }, + }, + }, + }, + "/": { + delete: { + tags: ["Miscellaneous"], + description: "Shuts down the Helm Dashboard application", + responses: { + "202": { + description: "Shutdown command has been accepted", + }, + }, + }, + }, + "/status": { + get: { + tags: ["Miscellaneous"], + description: "Gets application status", + responses: { + "200": { + description: "Returns JSON with some options", + headers: { + "X-Application-Name": { + description: "A string to self-identify the application", + }, }, + }, }, + }, }, + }, } diff --git a/dashboard/src/pages/Installed.tsx b/dashboard/src/pages/Installed.tsx index e4302d03..cd3b4111 100644 --- a/dashboard/src/pages/Installed.tsx +++ b/dashboard/src/pages/Installed.tsx @@ -1,92 +1,90 @@ -import InstalledPackagesHeader from '../components/InstalledPackages/InstalledPackagesHeader' -import InstalledPackagesList from '../components/InstalledPackages/InstalledPackagesList' -import ClustersList from '../components/ClustersList' -import { useGetInstalledReleases } from '../API/releases' -import { useMemo, useState } from 'react' -import Spinner from '../components/Spinner' -import useAlertError from '../hooks/useAlertError' -import { useParams, useNavigate } from 'react-router-dom' -import useCustomSearchParams from '../hooks/useCustomSearchParams' -import { Release } from '../data/types' +import InstalledPackagesHeader from "../components/InstalledPackages/InstalledPackagesHeader" +import InstalledPackagesList from "../components/InstalledPackages/InstalledPackagesList" +import ClustersList from "../components/ClustersList" +import { useGetInstalledReleases } from "../API/releases" +import { useMemo, useState } from "react" +import Spinner from "../components/Spinner" +import useAlertError from "../hooks/useAlertError" +import { useParams, useNavigate } from "react-router-dom" +import useCustomSearchParams from "../hooks/useCustomSearchParams" +import { Release } from "../data/types" function Installed() { - const { searchParamsObject, upsertSearchParams } = useCustomSearchParams() - const { context } = useParams() - const { filteredNamespace } = searchParamsObject - const namespaces = filteredNamespace?.split('+') ?? ['default'] - const navigate = useNavigate() + const { searchParamsObject, upsertSearchParams } = useCustomSearchParams() + const { context } = useParams() + const { filteredNamespace } = searchParamsObject + const namespaces = filteredNamespace?.split("+") ?? ["default"] + const navigate = useNavigate() - const handleClusterChange = ( - clusterName: string, - clusterNamespaces: string[] = [] - ) => { - const newSearchParams = upsertSearchParams( - 'filteredNamespace', - clusterNamespaces.length > 0 - ? `${clusterNamespaces.map((ns) => ns).join('+')}` - : 'default' - ) - navigate({ - pathname: `/${clusterName}/installed`, - search: newSearchParams.toString(), + const handleClusterChange = ( + clusterName: string, + clusterNamespaces: string[] = [] + ) => { + const newSearchParams = upsertSearchParams( + "filteredNamespace", + clusterNamespaces.length > 0 + ? `${clusterNamespaces.map((ns) => ns).join("+")}` + : "default" + ) + navigate({ + pathname: `/${clusterName}/installed`, + search: newSearchParams.toString(), + }) + } + + const [filterKey, setFilterKey] = useState("") + const alertError = useAlertError() + const { data, isLoading, isRefetching } = useGetInstalledReleases( + context ?? "", + { + retry: false, + onError: (e) => { + alertError.setShowErrorModal({ + title: "Failed to get list of charts", + msg: (e as Error).message, }) + }, } + ) - const [filterKey, setFilterKey] = useState('') - const alertError = useAlertError() - const { data, isLoading, isRefetching } = useGetInstalledReleases( - context ?? '', - { - retry: false, - onError: (e) => { - alertError.setShowErrorModal({ - title: 'Failed to get list of charts', - msg: (e as Error).message, - }) - }, - } - ) - - const filteredReleases = useMemo(() => { + const filteredReleases = useMemo(() => { + return ( + data?.filter((installedPackage: Release) => { return ( - data?.filter((installedPackage: Release) => { - return ( - installedPackage.name.includes(filterKey) || - (installedPackage.namespace.includes(filterKey) && - namespaces.includes(installedPackage.namespace)) - ) - }) ?? [] + installedPackage.name.includes(filterKey) || + (installedPackage.namespace.includes(filterKey) && + namespaces.includes(installedPackage.namespace)) ) - }, [data, filterKey, namespaces]) + }) ?? [] + ) + }, [data, filterKey, namespaces]) - return ( -
- + return ( +
+ -
- +
+ - {isLoading || isRefetching ? ( -
- -
- ) : ( - - )} -
-
- ) + {isLoading || isRefetching ? ( +
+ +
+ ) : ( + + )} +
+
+ ) } export default Installed diff --git a/dashboard/src/pages/NotFound.tsx b/dashboard/src/pages/NotFound.tsx index 0d1df2e4..deaaf3cc 100644 --- a/dashboard/src/pages/NotFound.tsx +++ b/dashboard/src/pages/NotFound.tsx @@ -1,16 +1,16 @@ -import React from 'react' +import React from "react" function NotFound() { - return ( -
- 404 page not found -
- ) + return ( +
+ 404 page not found +
+ ) } export default NotFound diff --git a/dashboard/src/pages/Repository.tsx b/dashboard/src/pages/Repository.tsx index a786ddf9..8ef90ef2 100644 --- a/dashboard/src/pages/Repository.tsx +++ b/dashboard/src/pages/Repository.tsx @@ -1,69 +1,67 @@ -import { useMemo, useEffect } from 'react' +import { useMemo, useEffect } from "react" -import RepositoriesList from '../components/repository/RepositoriesList' -import RepositoryViewer from '../components/repository/RepositoryViewer' -import { Repository } from '../data/types' -import { useGetRepositories } from '../API/repositories' -import { HelmRepositories } from '../API/interfaces' -import { useParams, useNavigate } from 'react-router-dom' -import { useAppContext } from '../context/AppContext' -import useNavigateWithSearchParams from '../hooks/useNavigateWithSearchParams' +import RepositoriesList from "../components/repository/RepositoriesList" +import RepositoryViewer from "../components/repository/RepositoryViewer" +import { Repository } from "../data/types" +import { useGetRepositories } from "../API/repositories" +import { HelmRepositories } from "../API/interfaces" +import { useParams, useNavigate } from "react-router-dom" +import { useAppContext } from "../context/AppContext" +import useNavigateWithSearchParams from "../hooks/useNavigateWithSearchParams" function RepositoryPage() { - const { selectedRepo: repoFromParams, context } = useParams() - const navigate = useNavigateWithSearchParams() - const { setSelectedRepo, selectedRepo } = useAppContext() + const { selectedRepo: repoFromParams, context } = useParams() + const navigate = useNavigateWithSearchParams() + const { setSelectedRepo, selectedRepo } = useAppContext() - const handleRepositoryChanged = (selectedRepository: Repository) => { - navigate(`/${context}/repository/${selectedRepository.name}`, { - replace: true, - }) - } + const handleRepositoryChanged = (selectedRepository: Repository) => { + navigate(`/${context}/repository/${selectedRepository.name}`, { + replace: true, + }) + } - useEffect(() => { - if (repoFromParams) { - setSelectedRepo(repoFromParams) - } - }, [setSelectedRepo, repoFromParams]) + useEffect(() => { + if (repoFromParams) { + setSelectedRepo(repoFromParams) + } + }, [setSelectedRepo, repoFromParams]) - useEffect(() => { - if (selectedRepo && !repoFromParams) { - navigate(`/${context}/repository/${selectedRepo}`, { - replace: true, - }) - } - }, [selectedRepo, repoFromParams, context]) + useEffect(() => { + if (selectedRepo && !repoFromParams) { + navigate(`/${context}/repository/${selectedRepo}`, { + replace: true, + }) + } + }, [selectedRepo, repoFromParams, context]) - const { data: repositories = [] } = useGetRepositories({ - onSuccess: (data: HelmRepositories) => { - const sortedData = data?.sort((a, b) => - a.name.localeCompare(b.name) - ) + const { data: repositories = [] } = useGetRepositories({ + onSuccess: (data: HelmRepositories) => { + const sortedData = data?.sort((a, b) => a.name.localeCompare(b.name)) - if (sortedData && sortedData.length > 0 && !repoFromParams) { - handleRepositoryChanged(sortedData[0]) - } - }, - }) + if (sortedData && sortedData.length > 0 && !repoFromParams) { + handleRepositoryChanged(sortedData[0]) + } + }, + }) - const selectedRepository = useMemo(() => { - if (repoFromParams) { - return repositories?.find((repo) => repo.name === repoFromParams) - } - }, [repositories, repoFromParams]) + const selectedRepository = useMemo(() => { + if (repoFromParams) { + return repositories?.find((repo) => repo.name === repoFromParams) + } + }, [repositories, repoFromParams]) - return ( -
- -
- -
-
- ) + return ( +
+ +
+ +
+
+ ) } export default RepositoryPage diff --git a/dashboard/src/pages/Revision.tsx b/dashboard/src/pages/Revision.tsx index fe145b3c..d1d71357 100644 --- a/dashboard/src/pages/Revision.tsx +++ b/dashboard/src/pages/Revision.tsx @@ -1,101 +1,101 @@ -import { useMemo } from 'react' -import { useParams } from 'react-router-dom' -import RevisionDetails from '../components/revision/RevisionDetails' -import RevisionsList from '../components/revision/RevisionsList' -import { ReleaseRevision } from '../data/types' -import { useQuery } from '@tanstack/react-query' -import apiService from '../API/apiService' -import Spinner from '../components/Spinner' +import { useMemo } from "react" +import { useParams } from "react-router-dom" +import RevisionDetails from "../components/revision/RevisionDetails" +import RevisionsList from "../components/revision/RevisionsList" +import { ReleaseRevision } from "../data/types" +import { useQuery } from "@tanstack/react-query" +import apiService from "../API/apiService" +import Spinner from "../components/Spinner" const descendingSort = (r1: ReleaseRevision, r2: ReleaseRevision) => - r1.revision - r2.revision < 0 ? 1 : -1 + r1.revision - r2.revision < 0 ? 1 : -1 function Revision() { - const { revision = '', ...restParams } = useParams() + const { revision = "", ...restParams } = useParams() - const selectedRevision = revision ? parseInt(revision, 10) : 0 + const selectedRevision = revision ? parseInt(revision, 10) : 0 - const { data: releaseRevisions, isLoading: isLoadingHistory } = useQuery< - ReleaseRevision[] - >({ - //eslint-ignore - //@ts-ignore - queryKey: ['releasesHistory', restParams], - queryFn: apiService.getReleasesHistory, - }) + const { data: releaseRevisions, isLoading: isLoadingHistory } = useQuery< + ReleaseRevision[] + >({ + //eslint-ignore + //@ts-ignore + queryKey: ["releasesHistory", restParams], + queryFn: apiService.getReleasesHistory, + }) - const latestRevision = useMemo( - () => - Array.isArray(releaseRevisions) && - releaseRevisions.reduce((max, revisionData) => { - return Math.max(max, revisionData.revision) - }, Number.MIN_SAFE_INTEGER), - [releaseRevisions] - ) + const latestRevision = useMemo( + () => + Array.isArray(releaseRevisions) && + releaseRevisions.reduce((max, revisionData) => { + return Math.max(max, revisionData.revision) + }, Number.MIN_SAFE_INTEGER), + [releaseRevisions] + ) - const sortedReleases = useMemo( - () => (releaseRevisions as ReleaseRevision[])?.sort(descendingSort), - [releaseRevisions] - ) + const sortedReleases = useMemo( + () => (releaseRevisions as ReleaseRevision[])?.sort(descendingSort), + [releaseRevisions] + ) - const selectedRelease = useMemo(() => { - if (selectedRevision && releaseRevisions) { - return (releaseRevisions as ReleaseRevision[]).find( - (r: ReleaseRevision) => r.revision === selectedRevision - ) - } - return null - }, [releaseRevisions, selectedRevision]) + const selectedRelease = useMemo(() => { + if (selectedRevision && releaseRevisions) { + return (releaseRevisions as ReleaseRevision[]).find( + (r: ReleaseRevision) => r.revision === selectedRevision + ) + } + return null + }, [releaseRevisions, selectedRevision]) - return ( -
-
- - {isLoadingHistory ? ( - - ) : ( - - )} -
+ return ( +
+
+ + {isLoadingHistory ? ( + + ) : ( + + )} +
-
- {isLoadingHistory ? ( -
- -
- ) : selectedRelease ? ( - - ) : null} -
-
- ) +
+ {isLoadingHistory ? ( +
+ +
+ ) : selectedRelease ? ( + + ) : null} +
+
+ ) } const RevisionSidebarSkeleton = () => { - return ( - <> -
-
-
-
-
-
- - ) + return ( + <> +
+
+
+
+
+
+ + ) } export default Revision diff --git a/dashboard/src/stories/Button.stories.tsx b/dashboard/src/stories/Button.stories.tsx index 9d5cd6cd..22283975 100644 --- a/dashboard/src/stories/Button.stories.tsx +++ b/dashboard/src/stories/Button.stories.tsx @@ -1,16 +1,16 @@ -import React from 'react' -import { ComponentStory, ComponentMeta } from '@storybook/react' +import React from "react" +import { ComponentStory, ComponentMeta } from "@storybook/react" -import { Button } from './Button' +import { Button } from "./Button" // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export export default { - title: 'Example/Button', - component: Button, - // More on argTypes: https://storybook.js.org/docs/react/api/argtypes - argTypes: { - backgroundColor: { control: 'color' }, - }, + title: "Example/Button", + component: Button, + // More on argTypes: https://storybook.js.org/docs/react/api/argtypes + argTypes: { + backgroundColor: { control: "color" }, + }, } as ComponentMeta // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args @@ -19,23 +19,23 @@ const Template: ComponentStory = (args) => - ) + const mode = primary + ? "storybook-button--primary" + : "storybook-button--secondary" + return ( + + ) } diff --git a/dashboard/src/stories/Header.stories.tsx b/dashboard/src/stories/Header.stories.tsx index 0b510cee..0494aee8 100644 --- a/dashboard/src/stories/Header.stories.tsx +++ b/dashboard/src/stories/Header.stories.tsx @@ -1,24 +1,24 @@ -import React from 'react' -import { ComponentStory, ComponentMeta } from '@storybook/react' +import React from "react" +import { ComponentStory, ComponentMeta } from "@storybook/react" -import { Header } from './Header' +import { Header } from "./Header" export default { - title: 'Example/Header', - component: Header, - parameters: { - // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout - layout: 'fullscreen', - }, + title: "Example/Header", + component: Header, + parameters: { + // More on Story layout: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen", + }, } as ComponentMeta const Template: ComponentStory = (args) =>
export const LoggedIn = Template.bind({}) LoggedIn.args = { - user: { - name: 'Jane Doe', - }, + user: { + name: "Jane Doe", + }, } export const LoggedOut = Template.bind({}) diff --git a/dashboard/src/stories/Header.tsx b/dashboard/src/stories/Header.tsx index 32ec8f42..38b5c354 100644 --- a/dashboard/src/stories/Header.tsx +++ b/dashboard/src/stories/Header.tsx @@ -1,75 +1,71 @@ -import React from 'react' +import React from "react" -import { Button } from './Button' -import './header.css' +import { Button } from "./Button" +import "./header.css" type User = { - name: string + name: string } interface HeaderProps { - user?: User - onLogin: () => void - onLogout: () => void - onCreateAccount: () => void + user?: User + onLogin: () => void + onLogout: () => void + onCreateAccount: () => void } export const Header = ({ - user, - onLogin, - onLogout, - onCreateAccount, + user, + onLogin, + onLogout, + onCreateAccount, }: HeaderProps) => ( -
-
-
- - - - - - - -

Acme

-
-
- {user ? ( - <> - - Welcome, {user.name}! - -
-
-
+
+
+
+ + + + + + + +

Acme

+
+
+ {user ? ( + <> + + Welcome, {user.name}! + +
+
+
) diff --git a/dashboard/src/stories/Introduction.stories.mdx b/dashboard/src/stories/Introduction.stories.mdx index 8a0f531b..bd5ffd26 100644 --- a/dashboard/src/stories/Introduction.stories.mdx +++ b/dashboard/src/stories/Introduction.stories.mdx @@ -1,17 +1,17 @@ -import { Meta } from '@storybook/addon-docs' -import Code from './assets/code-brackets.svg' -import Colors from './assets/colors.svg' -import Comments from './assets/comments.svg' -import Direction from './assets/direction.svg' -import Flow from './assets/flow.svg' -import Plugin from './assets/plugin.svg' -import Repo from './assets/repo.svg' -import StackAlt from './assets/stackalt.svg' +import { Meta } from "@storybook/addon-docs" +import Code from "./assets/code-brackets.svg" +import Colors from "./assets/colors.svg" +import Comments from "./assets/comments.svg" +import Direction from "./assets/direction.svg" +import Flow from "./assets/flow.svg" +import Plugin from "./assets/plugin.svg" +import Repo from "./assets/repo.svg" +import StackAlt from "./assets/stackalt.svg"