mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-24 11:48:04 +00:00
Enabled recommended-requiring-type-checking as result type fixes provided (#632)
* Enabled recommended-requiring-type-checking * from .cjs to .js * check * check * check * check * A lot of types aligned and refactored * More strict types * Improvement * Improvements * Improvements * Fixed routs * Fixed import types
This commit is contained in:
@@ -1,92 +0,0 @@
|
|||||||
const {
|
|
||||||
defineConfig,
|
|
||||||
} = require("eslint/config");
|
|
||||||
|
|
||||||
const globals = require("globals");
|
|
||||||
const tsParser = require("@typescript-eslint/parser");
|
|
||||||
const typescriptEslint = require("@typescript-eslint/eslint-plugin");
|
|
||||||
const react = require("eslint-plugin-react");
|
|
||||||
const js = require("@eslint/js");
|
|
||||||
|
|
||||||
const {
|
|
||||||
FlatCompat,
|
|
||||||
} = require("@eslint/eslintrc");
|
|
||||||
|
|
||||||
const compat = new FlatCompat({
|
|
||||||
baseDirectory: __dirname,
|
|
||||||
recommendedConfig: js.configs.recommended,
|
|
||||||
allConfig: js.configs.all
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = defineConfig([{
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.browser,
|
|
||||||
heap: "writable",
|
|
||||||
DD_RUM: "writable",
|
|
||||||
},
|
|
||||||
|
|
||||||
parser: tsParser,
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: "latest",
|
|
||||||
sourceType: "module",
|
|
||||||
ecmaFeatures: { jsx: true },
|
|
||||||
project: "./tsconfig.json",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
extends: compat.extends(
|
|
||||||
"enpitech",
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:prettier/recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
// "plugin:@typescript-eslint/recommended-requiring-type-checking", TODO enable and fix the types
|
|
||||||
),
|
|
||||||
|
|
||||||
plugins: {
|
|
||||||
"@typescript-eslint": typescriptEslint,
|
|
||||||
react,
|
|
||||||
},
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
react: {
|
|
||||||
version: "detect"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
rules: {
|
|
||||||
"no-console": ["error", {
|
|
||||||
allow: ["error"],
|
|
||||||
}],
|
|
||||||
|
|
||||||
"no-alert": "error",
|
|
||||||
"no-debugger": "error",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
|
||||||
|
|
||||||
"@typescript-eslint/no-unused-vars": ["error", {
|
|
||||||
vars: "all",
|
|
||||||
args: "after-used",
|
|
||||||
ignoreRestSiblings: true,
|
|
||||||
}],
|
|
||||||
|
|
||||||
"react/react-in-jsx-scope": "off",
|
|
||||||
"linebreak-style": ["error", "unix"],
|
|
||||||
quotes: ["error", "double"],
|
|
||||||
semi: ["error", "always"],
|
|
||||||
"@typescript-eslint/no-explicit-any": "warn",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
languageOptions: {
|
|
||||||
globals: {
|
|
||||||
...globals.node,
|
|
||||||
},
|
|
||||||
|
|
||||||
sourceType: "script",
|
|
||||||
parserOptions: {},
|
|
||||||
},
|
|
||||||
|
|
||||||
files: ["**/.eslintrc.{js,cjs}"],
|
|
||||||
}]);
|
|
||||||
160
frontend/eslint.config.js
Normal file
160
frontend/eslint.config.js
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { defineConfig } from "eslint/config";
|
||||||
|
import globals from "globals";
|
||||||
|
import tsParser from "@typescript-eslint/parser";
|
||||||
|
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||||
|
import react from "eslint-plugin-react";
|
||||||
|
import js from "@eslint/js";
|
||||||
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
import tscPlugin from "eslint-plugin-tsc";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(new URL(import.meta.url));
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: js.configs.recommended,
|
||||||
|
allConfig: js.configs.all,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{
|
||||||
|
ignores: ["eslint.config.js"],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
heap: "writable",
|
||||||
|
DD_RUM: "writable",
|
||||||
|
},
|
||||||
|
|
||||||
|
parser: tsParser,
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
|
ecmaFeatures: { jsx: true },
|
||||||
|
project: "./tsconfig.json",
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
extends: compat.extends(
|
||||||
|
"enpitech",
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-requiring-type-checking"
|
||||||
|
),
|
||||||
|
|
||||||
|
plugins: {
|
||||||
|
"@typescript-eslint": typescriptEslint,
|
||||||
|
tsc: tscPlugin,
|
||||||
|
react,
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "detect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
"no-console": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
allow: ["error"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
"no-alert": "error",
|
||||||
|
"no-debugger": "error",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
|
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
vars: "all",
|
||||||
|
args: "after-used",
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/jsx-uses-react": "error",
|
||||||
|
"linebreak-style": ["error", "unix"],
|
||||||
|
quotes: ["error", "double"],
|
||||||
|
semi: ["error", "always"],
|
||||||
|
"no-restricted-properties": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
object: "React",
|
||||||
|
property: "*",
|
||||||
|
message: "Using React.* is prohibited.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
|
"@typescript-eslint/strict-boolean-expressions": "off",
|
||||||
|
"@typescript-eslint/no-unsafe-assignment": "error",
|
||||||
|
"@typescript-eslint/no-unsafe-member-access": "error",
|
||||||
|
"@typescript-eslint/no-unsafe-return": "error",
|
||||||
|
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
||||||
|
"@typescript-eslint/consistent-type-assertions": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
assertionStyle: "as",
|
||||||
|
objectLiteralTypeAssertions: "never",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@typescript-eslint/no-restricted-types": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
types: {
|
||||||
|
"React.FC": {
|
||||||
|
message:
|
||||||
|
"Avoid using React.FC. Use import type { FC } from React instead",
|
||||||
|
},
|
||||||
|
"React.Node": {
|
||||||
|
message:
|
||||||
|
"Avoid using React.Node. Use import type { Node } from React instead",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"no-restricted-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
name: "react",
|
||||||
|
importNames: ["default", "*"],
|
||||||
|
message:
|
||||||
|
"Default and namespace React imports are prohibited. Use specific named imports only (e.g., import { useState, type ReactNode } from 'react').",
|
||||||
|
allowTypeImports: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
"@typescript-eslint/consistent-type-imports": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
prefer: "type-imports",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
"tsc/config": ["error", { configFile: "./tsconfig.json" }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
|
||||||
|
sourceType: "script",
|
||||||
|
parserOptions: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
files: ["**/.eslintrc.{js,cjs}"],
|
||||||
|
},
|
||||||
|
]);
|
||||||
46
frontend/package-lock.json
generated
46
frontend/package-lock.json
generated
@@ -27,6 +27,8 @@
|
|||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.3.3",
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
"@storybook/addon-docs": "^10.0.8",
|
"@storybook/addon-docs": "^10.0.8",
|
||||||
"@storybook/addon-links": "^10.0.8",
|
"@storybook/addon-links": "^10.0.8",
|
||||||
"@storybook/mdx2-csf": "^1.1.0",
|
"@storybook/mdx2-csf": "^1.1.0",
|
||||||
@@ -48,6 +50,7 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-storybook": "^10.0.8",
|
"eslint-plugin-storybook": "^10.0.8",
|
||||||
|
"eslint-plugin-tsc": "^2.0.0",
|
||||||
"flowbite": "^4.0.1",
|
"flowbite": "^4.0.1",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
@@ -1133,9 +1136,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
|
||||||
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
|
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1145,7 +1148,7 @@
|
|||||||
"globals": "^14.0.0",
|
"globals": "^14.0.0",
|
||||||
"ignore": "^5.2.0",
|
"ignore": "^5.2.0",
|
||||||
"import-fresh": "^3.2.1",
|
"import-fresh": "^3.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.1",
|
||||||
"minimatch": "^3.1.2",
|
"minimatch": "^3.1.2",
|
||||||
"strip-json-comments": "^3.1.1"
|
"strip-json-comments": "^3.1.1"
|
||||||
},
|
},
|
||||||
@@ -6344,6 +6347,20 @@
|
|||||||
"storybook": "^10.0.8"
|
"storybook": "^10.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-plugin-tsc": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-tsc/-/eslint-plugin-tsc-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-we7n063HSoWDpXjuqgplrYxfWnlVgq7GXteEjxtc/Ve6C0BjGQyoNGjApSVspyru1cckAM9ASwPnSU8Y0OTwTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"typescript-service": "^2.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-scope": {
|
"node_modules/eslint-scope": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||||
@@ -12466,6 +12483,27 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript-service": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript-service/-/typescript-service-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-FzRlqRp965UBzGvGwc6rbeko84jLILZrHf++I4cN8usZUB7F8Lh8/WDiCOUvy2l5os+jBWEz4fbYkkj1DhYJcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^1.9.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6",
|
||||||
|
"npm": ">=3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript-service/node_modules/tslib": {
|
||||||
|
"version": "1.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/unbox-primitive": {
|
"node_modules/unbox-primitive": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.3.3",
|
||||||
|
"@eslint/js": "^9.39.1",
|
||||||
"@storybook/addon-docs": "^10.0.8",
|
"@storybook/addon-docs": "^10.0.8",
|
||||||
"@storybook/addon-links": "^10.0.8",
|
"@storybook/addon-links": "^10.0.8",
|
||||||
"@storybook/mdx2-csf": "^1.1.0",
|
"@storybook/mdx2-csf": "^1.1.0",
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-react-hooks": "^7.0.1",
|
"eslint-plugin-react-hooks": "^7.0.1",
|
||||||
"eslint-plugin-storybook": "^10.0.8",
|
"eslint-plugin-storybook": "^10.0.8",
|
||||||
|
"eslint-plugin-tsc": "^2.0.0",
|
||||||
"flowbite": "^4.0.1",
|
"flowbite": "^4.0.1",
|
||||||
"globals": "^16.5.0",
|
"globals": "^16.5.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
@@ -57,12 +60,8 @@
|
|||||||
"vite-plugin-static-copy": "^3.1.4"
|
"vite-plugin-static-copy": "^3.1.4"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"src/*.{js,jsx,ts,tsx}": [
|
"*.{js,jsx,ts,tsx}": "npm run lint:fix",
|
||||||
"npm run lint:fix",
|
"*.{js,jsx,ts,tsx,json,css,md,mdx}": "npm run prettier:fix"
|
||||||
"npm run prettier:fix"
|
|
||||||
],
|
|
||||||
"*.{json,css,md,mdx}": "npm run prettier:fix",
|
|
||||||
"src/*.{ts,tsx}": "npm run tsc:check"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -75,7 +74,7 @@
|
|||||||
"storybook": "storybook dev -p 6006",
|
"storybook": "storybook dev -p 6006",
|
||||||
"storybook:build": "storybook build",
|
"storybook:build": "storybook build",
|
||||||
"lint": "npx eslint src/",
|
"lint": "npx eslint src/",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix --max-warnings=0",
|
||||||
"prettier": "npx prettier src/ --check",
|
"prettier": "npx prettier src/ --check",
|
||||||
"prettier:fix": "npm run prettier -- --write",
|
"prettier:fix": "npm run prettier -- --write",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {
|
import type {
|
||||||
Chart,
|
Chart,
|
||||||
ChartVersion,
|
ChartVersion,
|
||||||
Release,
|
Release,
|
||||||
@@ -25,7 +25,7 @@ class ApiService {
|
|||||||
public async fetchWithDefaults<T>(
|
public async fetchWithDefaults<T>(
|
||||||
url: string,
|
url: string,
|
||||||
options?: RequestInit
|
options?: RequestInit
|
||||||
): Promise<T> {
|
): Promise<T | string> {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
if (this.currentCluster) {
|
if (this.currentCluster) {
|
||||||
@@ -43,49 +43,66 @@ class ApiService {
|
|||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let data;
|
const contentType = response.headers.get("Content-Type") || "";
|
||||||
if (!response.headers.get("Content-Type")) {
|
if (!contentType) {
|
||||||
return {} as T;
|
return {} as unknown as T;
|
||||||
} else if (response.headers.get("Content-Type")?.includes("text/plain")) {
|
} else if (contentType.includes("text/plain")) {
|
||||||
data = await response.text();
|
return await response.text();
|
||||||
} else {
|
} else {
|
||||||
data = await response.json();
|
return (await response.json()) as T;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async fetchWithSafeDefaults<T>({
|
||||||
|
url,
|
||||||
|
options,
|
||||||
|
fallback,
|
||||||
|
}: {
|
||||||
|
url: string;
|
||||||
|
options?: RequestInit;
|
||||||
|
fallback: T;
|
||||||
|
}): Promise<T> {
|
||||||
|
const data = await this.fetchWithDefaults<T>(url, options);
|
||||||
|
if (!data) {
|
||||||
|
console.error(url, " response is empty");
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof data === "string") {
|
||||||
|
console.error(url, " response is string");
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
getToolVersion = async () => {
|
getToolVersion = async () => {
|
||||||
const response = await fetch("/status");
|
return await this.fetchWithDefaults("/status");
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getRepositoryLatestVersion = async (repositoryName: string) => {
|
getRepositoryLatestVersion = async (repositoryName: string) => {
|
||||||
const data = await this.fetchWithDefaults(
|
return await this.fetchWithDefaults(
|
||||||
`/api/helm/repositories/latestver?name=${repositoryName}`
|
`/api/helm/repositories/latestver?name=${repositoryName}`
|
||||||
);
|
);
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getInstalledReleases = async () => {
|
getInstalledReleases = async () => {
|
||||||
const data = await this.fetchWithDefaults("/api/helm/releases");
|
return await this.fetchWithDefaults("/api/helm/releases");
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getClusters = async () => {
|
getClusters = async (): Promise<ClustersResponse[]> => {
|
||||||
const response = await fetch("/api/k8s/contexts");
|
return await this.fetchWithSafeDefaults<ClustersResponse[]>({
|
||||||
const data = (await response.json()) as ClustersResponse[];
|
url: "/api/k8s/contexts",
|
||||||
return data;
|
fallback: [],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getNamespaces = async () => {
|
getNamespaces = async () => {
|
||||||
const data = await this.fetchWithDefaults("/api/k8s/namespaces/list");
|
return await this.fetchWithDefaults("/api/k8s/namespaces/list");
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getRepositories = async () => {
|
getRepositories = async () => {
|
||||||
const data = await this.fetchWithDefaults("/api/helm/repositories");
|
return await this.fetchWithDefaults("/api/helm/repositories");
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getRepositoryCharts = async ({
|
getRepositoryCharts = async ({
|
||||||
@@ -94,13 +111,12 @@ class ApiService {
|
|||||||
queryKey: readonly unknown[];
|
queryKey: readonly unknown[];
|
||||||
}): Promise<Chart[]> => {
|
}): Promise<Chart[]> => {
|
||||||
const [, repository] = queryKey;
|
const [, repository] = queryKey;
|
||||||
if (!repository) {
|
if (!repository || typeof repository !== "string") {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.fetchWithDefaults<Chart[]>(
|
const url = `/api/helm/repositories/${repository}`;
|
||||||
`/api/helm/repositories/${repository}`
|
return await this.fetchWithSafeDefaults<Chart[]>({ url, fallback: [] });
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getChartVersions = async ({
|
getChartVersions = async ({
|
||||||
@@ -108,25 +124,22 @@ class ApiService {
|
|||||||
}: QueryFunctionContext<ChartVersion[], Chart>) => {
|
}: QueryFunctionContext<ChartVersion[], Chart>) => {
|
||||||
const [, chart] = queryKey;
|
const [, chart] = queryKey;
|
||||||
|
|
||||||
const data = await this.fetchWithDefaults(
|
return await this.fetchWithDefaults(
|
||||||
`/api/helm/repositories/versions?name=${chart.name}`
|
`/api/helm/repositories/versions?name=${chart.name}`
|
||||||
);
|
);
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getResourceStatus = async ({
|
getResourceStatus = async ({
|
||||||
release,
|
release,
|
||||||
}: {
|
}: {
|
||||||
release: Release;
|
release: Release;
|
||||||
}): Promise<ReleaseHealthStatus[] | null> => {
|
}): Promise<ReleaseHealthStatus[]> => {
|
||||||
if (!release) return null;
|
if (!release) return [];
|
||||||
|
|
||||||
const data = await this.fetchWithDefaults<
|
return await this.fetchWithSafeDefaults<ReleaseHealthStatus[]>({
|
||||||
Promise<ReleaseHealthStatus[] | null>
|
url: `/api/helm/releases/${release.namespace}/${release.name}/resources?health=true`,
|
||||||
>(
|
fallback: [],
|
||||||
`/api/helm/releases/${release.namespace}/${release.name}/resources?health=true`
|
});
|
||||||
);
|
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getReleasesHistory = async ({
|
getReleasesHistory = async ({
|
||||||
@@ -138,9 +151,10 @@ class ApiService {
|
|||||||
|
|
||||||
if (!params.namespace || !params.chart) return [];
|
if (!params.namespace || !params.chart) return [];
|
||||||
|
|
||||||
return await this.fetchWithDefaults<ReleaseRevision[]>(
|
return await this.fetchWithSafeDefaults<ReleaseRevision[]>({
|
||||||
`/api/helm/releases/${params.namespace}/${params.chart}/history`
|
url: `/api/helm/releases/${params.namespace}/${params.chart}/history`,
|
||||||
);
|
fallback: [],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getValues = async ({
|
getValues = async ({
|
||||||
@@ -158,9 +172,7 @@ class ApiService {
|
|||||||
return Promise.reject(new Error("missing parameters"));
|
return Promise.reject(new Error("missing parameters"));
|
||||||
|
|
||||||
const url = `/api/helm/repositories/values?chart=${namespace}/${chart.name}&version=${version}`;
|
const url = `/api/helm/repositories/values?chart=${namespace}/${chart.name}&version=${version}`;
|
||||||
const data = await this.fetchWithDefaults(url);
|
return await this.fetchWithDefaults(url);
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
|
import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
|
||||||
import { K8sResource, K8sResourceList, KubectlContexts } from "./interfaces";
|
import type {
|
||||||
|
K8sResource,
|
||||||
|
K8sResourceList,
|
||||||
|
KubectlContexts,
|
||||||
|
} from "./interfaces";
|
||||||
import apiService from "./apiService";
|
import apiService from "./apiService";
|
||||||
|
|
||||||
// Get list of kubectl contexts configured locally
|
// Get list of kubectl contexts configured locally
|
||||||
@@ -8,7 +12,10 @@ function useGetKubectlContexts(options?: UseQueryOptions<KubectlContexts>) {
|
|||||||
return useQuery<KubectlContexts>({
|
return useQuery<KubectlContexts>({
|
||||||
queryKey: ["k8s", "contexts"],
|
queryKey: ["k8s", "contexts"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<KubectlContexts>("/api/k8s/contexts"),
|
apiService.fetchWithSafeDefaults<KubectlContexts>({
|
||||||
|
url: "/api/k8s/contexts",
|
||||||
|
fallback: { contexts: [] },
|
||||||
|
}),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -23,9 +30,10 @@ function useGetK8sResource(
|
|||||||
return useQuery<K8sResource>({
|
return useQuery<K8sResource>({
|
||||||
queryKey: ["k8s", kind, "get", name, namespace],
|
queryKey: ["k8s", kind, "get", name, namespace],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<K8sResource>(
|
apiService.fetchWithSafeDefaults<K8sResource>({
|
||||||
`/api/k8s/${kind}/get?name=${name}&namespace=${namespace}`
|
url: `/api/k8s/${kind}/get?name=${name}&namespace=${namespace}`,
|
||||||
),
|
fallback: { kind: "", name: "", namespace: "" },
|
||||||
|
}),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -38,7 +46,10 @@ function useGetK8sResourceList(
|
|||||||
return useQuery<K8sResourceList>({
|
return useQuery<K8sResourceList>({
|
||||||
queryKey: ["k8s", kind, "list"],
|
queryKey: ["k8s", kind, "list"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<K8sResourceList>(`/api/k8s/${kind}/list`),
|
apiService.fetchWithSafeDefaults<K8sResourceList>({
|
||||||
|
url: `/api/k8s/${kind}/list`,
|
||||||
|
fallback: { items: [] },
|
||||||
|
}),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import {
|
|||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { ApplicationStatus } from "./interfaces";
|
import type { ApplicationStatus } from "./interfaces";
|
||||||
import apiService from "./apiService";
|
import apiService from "./apiService";
|
||||||
|
|
||||||
// Shuts down the Helm Dashboard application
|
// Shuts down the Helm Dashboard application
|
||||||
export function useShutdownHelmDashboard(
|
export function useShutdownHelmDashboard(
|
||||||
options?: UseMutationOptions<void, Error>
|
options?: UseMutationOptions<string, Error>
|
||||||
) {
|
) {
|
||||||
return useMutation<void, Error>({
|
return useMutation<string, Error>({
|
||||||
mutationFn: () =>
|
mutationFn: () =>
|
||||||
apiService.fetchWithDefaults("/", {
|
apiService.fetchWithDefaults("/", {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
@@ -22,11 +22,16 @@ export function useShutdownHelmDashboard(
|
|||||||
|
|
||||||
// Gets application status
|
// Gets application status
|
||||||
export function useGetApplicationStatus(
|
export function useGetApplicationStatus(
|
||||||
options?: UseQueryOptions<ApplicationStatus>
|
options?: UseQueryOptions<ApplicationStatus | null>
|
||||||
) {
|
) {
|
||||||
return useQuery<ApplicationStatus>({
|
return useQuery<ApplicationStatus | null>({
|
||||||
queryKey: ["status"],
|
queryKey: ["status"],
|
||||||
queryFn: () => apiService.fetchWithDefaults<ApplicationStatus>("/status"),
|
queryFn: async () =>
|
||||||
|
await apiService.fetchWithSafeDefaults<ApplicationStatus | null>({
|
||||||
|
url: "/status",
|
||||||
|
fallback: null,
|
||||||
|
}),
|
||||||
|
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
useQuery,
|
useQuery,
|
||||||
type UseQueryOptions,
|
type UseQueryOptions,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { ChartVersion, Release } from "../data/types";
|
import type { ChartVersion, Release } from "../data/types";
|
||||||
import { LatestChartVersion } from "./interfaces";
|
import type { LatestChartVersion } from "./interfaces";
|
||||||
import apiService from "./apiService";
|
import apiService from "./apiService";
|
||||||
import { getVersionManifestFormData } from "./shared";
|
import { getVersionManifestFormData } from "./shared";
|
||||||
import { isNewerVersion } from "../utils";
|
import { isNewerVersion } from "../utils";
|
||||||
@@ -16,7 +16,10 @@ export function useGetInstalledReleases(context: string) {
|
|||||||
return useQuery<Release[]>({
|
return useQuery<Release[]>({
|
||||||
queryKey: ["installedReleases", context],
|
queryKey: ["installedReleases", context],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<Release[]>("/api/helm/releases"),
|
apiService.fetchWithSafeDefaults<Release[]>({
|
||||||
|
url: "/api/helm/releases",
|
||||||
|
fallback: [],
|
||||||
|
}),
|
||||||
retry: false,
|
retry: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -65,44 +68,47 @@ export function useGetReleaseManifest({
|
|||||||
return useQuery<ReleaseManifest[]>({
|
return useQuery<ReleaseManifest[]>({
|
||||||
queryKey: ["manifest", namespace, chartName],
|
queryKey: ["manifest", namespace, chartName],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<ReleaseManifest[]>(
|
apiService.fetchWithSafeDefaults<ReleaseManifest[]>({
|
||||||
`/api/helm/releases/${namespace}/${chartName}/manifests`
|
url: `/api/helm/releases/${namespace}/${chartName}/manifests`,
|
||||||
),
|
fallback: [],
|
||||||
|
}),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of installed k8s resources for this release
|
// List of installed k8s resources for this release
|
||||||
export function useGetResources(ns: string, name: string, enabled?: boolean) {
|
export function useGetResources(ns: string, name: string, enabled?: boolean) {
|
||||||
const { data, ...rest } = useQuery<StructuredResources[]>({
|
return useQuery<StructuredResources[]>({
|
||||||
queryKey: ["resources", ns, name],
|
queryKey: ["resources", ns, name],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<StructuredResources[]>(
|
apiService.fetchWithSafeDefaults<StructuredResources[]>({
|
||||||
`/api/helm/releases/${ns}/${name}/resources?health=true`
|
url: `/api/helm/releases/${ns}/${name}/resources?health=true`,
|
||||||
),
|
fallback: [],
|
||||||
|
}),
|
||||||
|
select: (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())
|
||||||
|
);
|
||||||
|
}),
|
||||||
enabled,
|
enabled,
|
||||||
});
|
});
|
||||||
|
|
||||||
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(
|
export function useGetResourceDescription(
|
||||||
@@ -130,9 +136,10 @@ export function useGetLatestVersion(
|
|||||||
return useQuery<ChartVersion[]>({
|
return useQuery<ChartVersion[]>({
|
||||||
queryKey: ["latestver", chartName],
|
queryKey: ["latestver", chartName],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<ChartVersion[]>(
|
apiService.fetchWithSafeDefaults<ChartVersion[]>({
|
||||||
`/api/helm/repositories/latestver?name=${chartName}`
|
url: `/api/helm/repositories/latestver?name=${chartName}`,
|
||||||
),
|
fallback: [],
|
||||||
|
}),
|
||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
@@ -143,10 +150,13 @@ export function useGetVersions(
|
|||||||
) {
|
) {
|
||||||
return useQuery<LatestChartVersion[]>({
|
return useQuery<LatestChartVersion[]>({
|
||||||
queryKey: ["versions", chartName],
|
queryKey: ["versions", chartName],
|
||||||
queryFn: () =>
|
queryFn: async () => {
|
||||||
apiService.fetchWithDefaults<LatestChartVersion[]>(
|
const url = `/api/helm/repositories/versions?name=${chartName}`;
|
||||||
`/api/helm/repositories/versions?name=${chartName}`
|
return await apiService.fetchWithSafeDefaults<LatestChartVersion[]>({
|
||||||
),
|
url,
|
||||||
|
fallback: [],
|
||||||
|
});
|
||||||
|
},
|
||||||
select: (data) =>
|
select: (data) =>
|
||||||
data?.sort((a, b) => (isNewerVersion(a.version, b.version) ? 1 : -1)),
|
data?.sort((a, b) => (isNewerVersion(a.version, b.version) ? 1 : -1)),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
@@ -192,21 +202,21 @@ export function useGetDiff(
|
|||||||
// Rollback the release to a previous revision
|
// Rollback the release to a previous revision
|
||||||
export function useRollbackRelease(
|
export function useRollbackRelease(
|
||||||
options?: UseMutationOptions<
|
options?: UseMutationOptions<
|
||||||
void,
|
string,
|
||||||
unknown,
|
Error,
|
||||||
{ ns: string; name: string; revision: number }
|
{ ns: string; name: string; revision: number }
|
||||||
>
|
>
|
||||||
) {
|
) {
|
||||||
return useMutation<
|
return useMutation<
|
||||||
void,
|
string,
|
||||||
unknown,
|
Error,
|
||||||
{ ns: string; name: string; revision: number }
|
{ ns: string; name: string; revision: number }
|
||||||
>({
|
>({
|
||||||
mutationFn: ({ ns, name, revision }) => {
|
mutationFn: ({ ns, name, revision }) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("revision", revision.toString());
|
formData.append("revision", revision.toString());
|
||||||
|
|
||||||
return apiService.fetchWithDefaults<void>(
|
return apiService.fetchWithDefaults<string>(
|
||||||
`/api/helm/releases/${ns}/${name}/rollback`,
|
`/api/helm/releases/${ns}/${name}/rollback`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -220,11 +230,11 @@ export function useRollbackRelease(
|
|||||||
|
|
||||||
// Run the tests on a release
|
// Run the tests on a release
|
||||||
export function useTestRelease(
|
export function useTestRelease(
|
||||||
options?: UseMutationOptions<void, unknown, { ns: string; name: string }>
|
options?: UseMutationOptions<string, Error, { ns: string; name: string }>
|
||||||
) {
|
) {
|
||||||
return useMutation<void, unknown, { ns: string; name: string }>({
|
return useMutation<string, Error, { ns: string; name: string }>({
|
||||||
mutationFn: ({ ns, name }) => {
|
mutationFn: ({ ns, name }) => {
|
||||||
return apiService.fetchWithDefaults<void>(
|
return apiService.fetchWithDefaults<string>(
|
||||||
`/api/helm/releases/${ns}/${name}/test`,
|
`/api/helm/releases/${ns}/${name}/test`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -309,19 +319,22 @@ export const useVersionData = ({
|
|||||||
releaseName,
|
releaseName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchUrl = isInstallRepoChart
|
const url = isInstallRepoChart
|
||||||
? `/api/helm/releases/${namespace || "default"}`
|
? `/api/helm/releases/${namespace || "default"}`
|
||||||
: `/api/helm/releases/${
|
: `/api/helm/releases/${
|
||||||
namespace ? namespace : "[empty]"
|
namespace ? namespace : "[empty]"
|
||||||
}${`/${releaseName}`}`;
|
}${`/${releaseName}`}`;
|
||||||
|
|
||||||
return await apiService.fetchWithDefaults<{ [key: string]: string }>(
|
return await apiService.fetchWithSafeDefaults<{
|
||||||
fetchUrl,
|
[key: string]: string;
|
||||||
{
|
}>({
|
||||||
|
url,
|
||||||
|
options: {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: formData,
|
body: formData,
|
||||||
}
|
},
|
||||||
);
|
fallback: {},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
enabled,
|
enabled,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { HelmRepositories } from "./interfaces";
|
import type { HelmRepositories } from "./interfaces";
|
||||||
import apiService from "./apiService";
|
import apiService from "./apiService";
|
||||||
|
|
||||||
// Get list of Helm repositories
|
// Get list of Helm repositories
|
||||||
@@ -14,7 +14,11 @@ export function useGetRepositories(
|
|||||||
return useQuery<HelmRepositories>({
|
return useQuery<HelmRepositories>({
|
||||||
queryKey: ["helm", "repositories"],
|
queryKey: ["helm", "repositories"],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<HelmRepositories>("/api/helm/repositories"),
|
apiService.fetchWithSafeDefaults<HelmRepositories>({
|
||||||
|
url: "/api/helm/repositories",
|
||||||
|
fallback: [],
|
||||||
|
}),
|
||||||
|
select: (data) => data?.sort((a, b) => a?.name?.localeCompare(b?.name)),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -22,11 +26,11 @@ export function useGetRepositories(
|
|||||||
// Update repository from remote
|
// Update repository from remote
|
||||||
export function useUpdateRepo(
|
export function useUpdateRepo(
|
||||||
repo: string,
|
repo: string,
|
||||||
options?: UseMutationOptions<void, unknown, void>
|
options?: UseMutationOptions<string, Error>
|
||||||
) {
|
) {
|
||||||
return useMutation<void, unknown, void>({
|
return useMutation<string, Error>({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
return apiService.fetchWithDefaults<void>(
|
return apiService.fetchWithDefaults<string>(
|
||||||
`/api/helm/repositories/${repo}`,
|
`/api/helm/repositories/${repo}`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -40,11 +44,11 @@ export function useUpdateRepo(
|
|||||||
// Remove repository
|
// Remove repository
|
||||||
export function useDeleteRepo(
|
export function useDeleteRepo(
|
||||||
repo: string,
|
repo: string,
|
||||||
options?: UseMutationOptions<void, unknown, void>
|
options?: UseMutationOptions<string, Error>
|
||||||
) {
|
) {
|
||||||
return useMutation<void, unknown, void>({
|
return useMutation<string, Error>({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
return apiService.fetchWithDefaults<void>(
|
return apiService.fetchWithDefaults<string>(
|
||||||
`/api/helm/repositories/${repo}`,
|
`/api/helm/repositories/${repo}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
|||||||
@@ -7,14 +7,19 @@ import {
|
|||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { ScanResult, ScanResults, ScannersList } from "./interfaces";
|
import type { ScanResults, ScannersList } from "./interfaces";
|
||||||
|
import { ScanResult } from "./interfaces";
|
||||||
import apiService from "./apiService";
|
import apiService from "./apiService";
|
||||||
|
|
||||||
// Get list of discovered scanners
|
// Get list of discovered scanners
|
||||||
function useGetDiscoveredScanners(options?: UseQueryOptions<ScannersList>) {
|
function useGetDiscoveredScanners(options?: UseQueryOptions<ScannersList>) {
|
||||||
return useQuery<ScannersList>({
|
return useQuery<ScannersList>({
|
||||||
queryKey: ["scanners"],
|
queryKey: ["scanners"],
|
||||||
queryFn: () => apiService.fetchWithDefaults<ScannersList>("/api/scanners"),
|
queryFn: () =>
|
||||||
|
apiService.fetchWithSafeDefaults<ScannersList>({
|
||||||
|
url: "/api/scanners",
|
||||||
|
fallback: { scanners: [] },
|
||||||
|
}),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -28,9 +33,13 @@ function useScanManifests(
|
|||||||
formData.append("manifest", manifest);
|
formData.append("manifest", manifest);
|
||||||
return useMutation<ScanResults, Error, string>({
|
return useMutation<ScanResults, Error, string>({
|
||||||
mutationFn: () =>
|
mutationFn: () =>
|
||||||
apiService.fetchWithDefaults<ScanResults>("/api/scanners/manifests", {
|
apiService.fetchWithSafeDefaults<ScanResults>({
|
||||||
method: "POST",
|
url: "/api/scanners/manifests",
|
||||||
body: formData,
|
options: {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
},
|
||||||
|
fallback: {},
|
||||||
}),
|
}),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
@@ -46,9 +55,10 @@ function useScanK8sResource(
|
|||||||
return useQuery<ScanResults>({
|
return useQuery<ScanResults>({
|
||||||
queryKey: ["scanners", "resource", kind, namespace, name],
|
queryKey: ["scanners", "resource", kind, namespace, name],
|
||||||
queryFn: () =>
|
queryFn: () =>
|
||||||
apiService.fetchWithDefaults<ScanResults>(
|
apiService.fetchWithSafeDefaults<ScanResults>({
|
||||||
`/api/scanners/resource/${kind}?namespace=${namespace}&name=${name}`
|
url: `/api/scanners/resource/${kind}?namespace=${namespace}&name=${name}`,
|
||||||
),
|
fallback: {},
|
||||||
|
}),
|
||||||
...(options ?? {}),
|
...(options ?? {}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import Installed from "./pages/Installed";
|
|||||||
import RepositoryPage from "./pages/Repository";
|
import RepositoryPage from "./pages/Repository";
|
||||||
import Revision from "./pages/Revision";
|
import Revision from "./pages/Revision";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import type { FC } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ErrorAlert, ErrorModalContext } from "./context/ErrorModalContext";
|
import type { ErrorAlert } from "./context/ErrorModalContext";
|
||||||
|
import { ErrorModalContext } from "./context/ErrorModalContext";
|
||||||
import GlobalErrorModal from "./components/modal/GlobalErrorModal";
|
import GlobalErrorModal from "./components/modal/GlobalErrorModal";
|
||||||
import { AppContextProvider } from "./context/AppContext";
|
import { AppContextProvider } from "./context/AppContext";
|
||||||
import apiService from "./API/apiService";
|
import apiService from "./API/apiService";
|
||||||
@@ -31,7 +33,7 @@ const PageLayout = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SyncContext: React.FC = () => {
|
const SyncContext: FC = () => {
|
||||||
const { context } = useParams();
|
const { context } = useParams();
|
||||||
if (context) {
|
if (context) {
|
||||||
apiService.setCluster(decodeURIComponent(context));
|
apiService.setCluster(decodeURIComponent(context));
|
||||||
@@ -52,31 +54,29 @@ export default function App() {
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="docs/" element={<DocsPage />} />
|
<Route path="docs/*" element={<DocsPage />} />
|
||||||
<Route path="*" element={<PageLayout />}>
|
<Route path="*" element={<PageLayout />}>
|
||||||
<Route path=":context?/*" element={<SyncContext />}>
|
<Route path=":context?/*" element={<SyncContext />}>
|
||||||
|
<Route
|
||||||
|
path="repository/:selectedRepo?/*"
|
||||||
|
element={<RepositoryPage />}
|
||||||
|
/>
|
||||||
<Route path="installed/?" element={<Installed />} />
|
<Route path="installed/?" element={<Installed />} />
|
||||||
<Route
|
<Route
|
||||||
path=":namespace/:chart/installed/revision/:revision"
|
path=":namespace/:chart/installed/revision/:revision"
|
||||||
element={<Revision />}
|
element={<Revision />}
|
||||||
/>
|
/>
|
||||||
<Route path="repository/" element={<RepositoryPage />} />
|
|
||||||
<Route
|
|
||||||
path="repository/:selectedRepo?"
|
|
||||||
element={<RepositoryPage />}
|
|
||||||
/>
|
|
||||||
<Route path="*" element={<Installed />} />
|
<Route path="*" element={<Installed />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={<Installed />} />
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
<GlobalErrorModal
|
|
||||||
isOpen={!!shouldShowErrorModal}
|
|
||||||
onClose={() => setShowErrorModal(undefined)}
|
|
||||||
titleText={shouldShowErrorModal?.title || ""}
|
|
||||||
contentText={shouldShowErrorModal?.msg || ""}
|
|
||||||
/>
|
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
<GlobalErrorModal
|
||||||
|
isOpen={!!shouldShowErrorModal}
|
||||||
|
onClose={() => setShowErrorModal(undefined)}
|
||||||
|
titleText={shouldShowErrorModal?.title || ""}
|
||||||
|
contentText={shouldShowErrorModal?.msg || ""}
|
||||||
|
/>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ErrorModalContext.Provider>
|
</ErrorModalContext.Provider>
|
||||||
</AppContextProvider>
|
</AppContextProvider>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* @see https://storybook.js.org/docs/react/writing-stories/introduction
|
* @see https://storybook.js.org/docs/react/writing-stories/introduction
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import Badge from "./Badge";
|
import Badge from "./Badge";
|
||||||
|
|
||||||
// We set the metadata for the story.
|
// We set the metadata for the story.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { JSX, ReactNode } from "react";
|
import type { JSX, ReactNode } from "react";
|
||||||
|
|
||||||
export type BadgeCode = "success" | "warning" | "error" | "unknown";
|
export type BadgeCode = "success" | "warning" | "error" | "unknown";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { HTMLAttributes, JSX, ReactNode } from "react";
|
import type { HTMLAttributes, JSX, ReactNode } from "react";
|
||||||
|
|
||||||
// this is a type declaration for the action prop.
|
// this is a type declaration for the action prop.
|
||||||
// it is a function that takes a string as an argument and returns void.
|
// it is a function that takes a string as an argument and returns void.
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import { AppContextProvider } from "../context/AppContext";
|
|||||||
import ClustersList from "./ClustersList";
|
import ClustersList from "./ClustersList";
|
||||||
import { BrowserRouter } from "react-router";
|
import { BrowserRouter } from "react-router";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { Release } from "../data/types";
|
import type { Release } from "../data/types";
|
||||||
|
import { DeploymentStatus } from "./common/StatusLabel";
|
||||||
|
|
||||||
type ClustersListProps = {
|
type ClustersListProps = {
|
||||||
onClusterChange: (clusterName: string) => void;
|
onClusterChange: (clusterName: string) => void;
|
||||||
@@ -17,7 +18,7 @@ const generateTestReleaseData = (): Release => ({
|
|||||||
namespace: "default",
|
namespace: "default",
|
||||||
revision: 1,
|
revision: 1,
|
||||||
updated: "2024-01-23T15:37:35.0992836+02:00",
|
updated: "2024-01-23T15:37:35.0992836+02:00",
|
||||||
status: "deployed",
|
status: DeploymentStatus.DEPLOYED,
|
||||||
chart: "helm-dashboard-0.1.10",
|
chart: "helm-dashboard-0.1.10",
|
||||||
chart_name: "helm-dashboard",
|
chart_name: "helm-dashboard",
|
||||||
chart_ver: "0.1.10",
|
chart_ver: "0.1.10",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import ClustersList from "./ClustersList";
|
import ClustersList from "./ClustersList";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
import { useEffect, useEffectEvent, useMemo } from "react";
|
||||||
import { Cluster, Release } from "../data/types";
|
import type { Cluster, Release } from "../data/types";
|
||||||
import apiService from "../API/apiService";
|
import apiService from "../API/apiService";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import useCustomSearchParams from "../hooks/useCustomSearchParams";
|
import useCustomSearchParams from "../hooks/useCustomSearchParams";
|
||||||
@@ -43,21 +43,19 @@ function ClustersList({
|
|||||||
}: ClustersListProps) {
|
}: ClustersListProps) {
|
||||||
const { upsertSearchParams, removeSearchParam } = useCustomSearchParams();
|
const { upsertSearchParams, removeSearchParam } = useCustomSearchParams();
|
||||||
const { clusterMode } = useAppContext();
|
const { clusterMode } = useAppContext();
|
||||||
const [sortedClusters, setSortedClusters] = useState<Cluster[]>([]);
|
|
||||||
|
|
||||||
const { data: clusters, isSuccess } = useQuery<Cluster[]>({
|
const { data: clusters = [], isSuccess } = useQuery<Cluster[]>({
|
||||||
queryKey: ["clusters", selectedCluster],
|
queryKey: ["clusters", selectedCluster],
|
||||||
queryFn: apiService.getClusters,
|
queryFn: apiService.getClusters,
|
||||||
|
select: (data) =>
|
||||||
|
data?.sort((a, b) =>
|
||||||
|
getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name))
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSuccess = useEffectEvent((clusters: Cluster[]) => {
|
const onSuccess = useEffectEvent((clusters: Cluster[]) => {
|
||||||
const sortedData = [...clusters].sort((a, b) =>
|
if (clusters && clusters.length && !selectedCluster) {
|
||||||
getCleanClusterName(a.Name).localeCompare(getCleanClusterName(b.Name))
|
onClusterChange(clusters[0].Name);
|
||||||
);
|
|
||||||
setSortedClusters(sortedData);
|
|
||||||
|
|
||||||
if (sortedData && sortedData.length > 0 && !selectedCluster) {
|
|
||||||
onClusterChange(sortedData[0].Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedCluster) {
|
if (selectedCluster) {
|
||||||
@@ -111,10 +109,10 @@ function ClustersList({
|
|||||||
{!clusterMode ? (
|
{!clusterMode ? (
|
||||||
<>
|
<>
|
||||||
<label className="font-bold">Clusters</label>
|
<label className="font-bold">Clusters</label>
|
||||||
{sortedClusters?.map((cluster) => {
|
{clusters?.map((cluster) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={cluster.Name}
|
key={cluster.Name + cluster.Namespace}
|
||||||
className="data-cy-clusterName mt-2 flex items-center text-xs"
|
className="data-cy-clusterName mt-2 flex items-center text-xs"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { HD_RESOURCE_CONDITION_TYPE } from "../../API/releases";
|
import { HD_RESOURCE_CONDITION_TYPE } from "../../API/releases";
|
||||||
import { Tooltip } from "flowbite-react";
|
import { Tooltip } from "flowbite-react";
|
||||||
import { ReleaseHealthStatus } from "../../data/types";
|
import type { ReleaseHealthStatus } from "../../data/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
statusData: ReleaseHealthStatus[];
|
statusData: ReleaseHealthStatus[];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import InstalledPackageCard from "./InstalledPackageCard";
|
import InstalledPackageCard from "./InstalledPackageCard";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Release, ReleaseHealthStatus } from "../../data/types";
|
import type { Release, ReleaseHealthStatus } from "../../data/types";
|
||||||
import { BsArrowUpCircleFill, BsPlusCircleFill } from "react-icons/bs";
|
import { BsArrowUpCircleFill, BsPlusCircleFill } from "react-icons/bs";
|
||||||
import { getAge } from "../../timeUtils";
|
import { getAge } from "../../timeUtils";
|
||||||
import StatusLabel, {
|
import StatusLabel, {
|
||||||
@@ -13,7 +13,7 @@ import HelmGrayIcon from "../../assets/helm-gray-50.svg";
|
|||||||
import Spinner from "../Spinner";
|
import Spinner from "../Spinner";
|
||||||
import { useGetLatestVersion } from "../../API/releases";
|
import { useGetLatestVersion } from "../../API/releases";
|
||||||
import { isNewerVersion } from "../../utils";
|
import { isNewerVersion } from "../../utils";
|
||||||
import { LatestChartVersion } from "../../API/interfaces";
|
import type { LatestChartVersion } from "../../API/interfaces";
|
||||||
import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams";
|
import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export default function InstalledPackageCard({
|
|||||||
queryKey: ["chartName", release.chartName],
|
queryKey: ["chartName", release.chartName],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: statusData } = useQuery<ReleaseHealthStatus[] | null>({
|
const { data: statusData = [], isLoading } = useQuery<ReleaseHealthStatus[]>({
|
||||||
queryKey: ["resourceStatus", release],
|
queryKey: ["resourceStatus", release],
|
||||||
queryFn: () => apiService.getResourceStatus({ release }),
|
queryFn: () => apiService.getResourceStatus({ release }),
|
||||||
enabled: inView,
|
enabled: inView,
|
||||||
@@ -61,14 +61,21 @@ export default function InstalledPackageCard({
|
|||||||
setIsMouseOver(false);
|
setIsMouseOver(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnClick = () => {
|
const onClick = async () => {
|
||||||
const { name, namespace } = release;
|
const { name, namespace } = release;
|
||||||
navigate(`/${namespace}/${name}/installed/revision/${release.revision}`, {
|
await navigate(
|
||||||
state: release,
|
`/${namespace}/${name}/installed/revision/${release.revision}`,
|
||||||
});
|
{
|
||||||
|
state: release,
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusColor = getStatusColor(release.status as DeploymentStatus);
|
const handleClick = () => {
|
||||||
|
void onClick();
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusColor = getStatusColor(release.status);
|
||||||
const borderLeftColor: { [key: string]: string } = {
|
const borderLeftColor: { [key: string]: string } = {
|
||||||
[DeploymentStatus.DEPLOYED]: "border-l-border-deployed",
|
[DeploymentStatus.DEPLOYED]: "border-l-border-deployed",
|
||||||
[DeploymentStatus.FAILED]: "border-l-text-danger",
|
[DeploymentStatus.FAILED]: "border-l-text-danger",
|
||||||
@@ -85,7 +92,7 @@ export default function InstalledPackageCard({
|
|||||||
}`}
|
}`}
|
||||||
onMouseOver={handleMouseOver}
|
onMouseOver={handleMouseOver}
|
||||||
onMouseOut={handleMouseOut}
|
onMouseOut={handleMouseOut}
|
||||||
onClick={handleOnClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={release.icon || HelmGrayIcon}
|
src={release.icon || HelmGrayIcon}
|
||||||
@@ -118,10 +125,10 @@ export default function InstalledPackageCard({
|
|||||||
{release.description}
|
{release.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-3 mr-2">
|
<div className="col-span-3 mr-2">
|
||||||
{statusData ? (
|
{isLoading ? (
|
||||||
<HealthStatus statusData={statusData} />
|
|
||||||
) : (
|
|
||||||
<Spinner size={4} />
|
<Spinner size={4} />
|
||||||
|
) : (
|
||||||
|
<HealthStatus statusData={statusData} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="items col-span-2 flex flex-col text-muted">
|
<div className="items col-span-2 flex flex-col text-muted">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import InstalledPackagesHeader from "./InstalledPackagesHeader";
|
import InstalledPackagesHeader from "./InstalledPackagesHeader";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import HeaderLogo from "../../assets/packges-header.svg";
|
import HeaderLogo from "../../assets/packges-header.svg";
|
||||||
import { Release } from "../../data/types";
|
import type { Release } from "../../data/types";
|
||||||
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
|
|
||||||
type InstalledPackagesHeaderProps = {
|
type InstalledPackagesHeaderProps = {
|
||||||
filteredReleases?: Release[];
|
filteredReleases?: Release[];
|
||||||
setFilterKey: React.Dispatch<React.SetStateAction<string>>;
|
setFilterKey: Dispatch<SetStateAction<string>>;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import InstalledPackagesList from "./InstalledPackagesList";
|
import InstalledPackagesList from "./InstalledPackagesList";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import InstalledPackageCard from "./InstalledPackageCard";
|
import InstalledPackageCard from "./InstalledPackageCard";
|
||||||
import { Release } from "../../data/types";
|
import type { Release } from "../../data/types";
|
||||||
|
|
||||||
type InstalledPackagesListProps = {
|
type InstalledPackagesListProps = {
|
||||||
filteredReleases: Release[];
|
filteredReleases: Release[];
|
||||||
|
|||||||
@@ -27,13 +27,9 @@ const LinkWithSearchParams = ({
|
|||||||
prefixedUrl = `/${encodeURIComponent(context)}${to}`;
|
prefixedUrl = `/${encodeURIComponent(context)}${to}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const url = `${prefixedUrl}/?${params.toString()}`;
|
||||||
<NavLink
|
|
||||||
data-cy="navigation-link"
|
return <NavLink data-cy="navigation-link" to={url} {...props} />;
|
||||||
to={`${prefixedUrl}/?${params.toString()}`}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LinkWithSearchParams;
|
export default LinkWithSearchParams;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* The default story renders the component with the default props.
|
* The default story renders the component with the default props.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Meta, StoryObj } from "@storybook/react-vite";
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
import { action } from "storybook/actions";
|
import { action } from "storybook/actions";
|
||||||
import SelectMenu, { SelectMenuItem } from "./SelectMenu";
|
import SelectMenu, { SelectMenuItem } from "./SelectMenu";
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { JSX } from "react";
|
import type { JSX, ReactNode } from "react";
|
||||||
|
|
||||||
// define the SelectMenuItem type:
|
// define the SelectMenuItem type:
|
||||||
// This is an object with a label and id.
|
// This is an object with a label and id.
|
||||||
@@ -39,7 +39,7 @@ export interface SelectMenuItemProps {
|
|||||||
|
|
||||||
export interface SelectMenuProps {
|
export interface SelectMenuProps {
|
||||||
header: string;
|
header: string;
|
||||||
children: React.ReactNode;
|
children: ReactNode;
|
||||||
selected: number;
|
selected: number;
|
||||||
onSelect: (id: number) => void;
|
onSelect: (id: number) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { StoryFn, Meta } from "@storybook/react-vite";
|
import type { StoryFn, Meta } from "@storybook/react-vite";
|
||||||
import ShutDownButton from "./ShutDownButton";
|
import ShutDownButton from "./ShutDownButton";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { BsPower } from "react-icons/bs";
|
import { BsPower } from "react-icons/bs";
|
||||||
|
|
||||||
import Modal from "./modal/Modal";
|
import Modal from "./modal/Modal";
|
||||||
import { useShutdownHelmDashboard } from "../API/other";
|
import { useShutdownHelmDashboard } from "../API/other";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import Tabs from "./Tabs";
|
import Tabs from "./Tabs";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import useCustomSearchParams from "../hooks/useCustomSearchParams";
|
import useCustomSearchParams from "../hooks/useCustomSearchParams";
|
||||||
|
|
||||||
export interface Tab {
|
export interface Tab {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import TabsBar from "./TabsBar";
|
import TabsBar from "./TabsBar";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { JSX } from "react";
|
import type { JSX } from "react";
|
||||||
|
|
||||||
interface TabsBarProps {
|
interface TabsBarProps {
|
||||||
tabs: Array<{ name: string; component: JSX.Element }>;
|
tabs: Array<{ name: string; component: JSX.Element }>;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* the first story simply renders the component with the default props.
|
* the first story simply renders the component with the default props.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import TextInput from "./TextInput";
|
import TextInput from "./TextInput";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -12,13 +12,13 @@
|
|||||||
* @return JSX.Element
|
* @return JSX.Element
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import { JSX } from "react";
|
import type { ChangeEvent, JSX } from "react";
|
||||||
|
|
||||||
export interface TextInputProps {
|
export interface TextInputProps {
|
||||||
label: string;
|
label: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
isMandatory?: boolean;
|
isMandatory?: boolean;
|
||||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TextInput(props: TextInputProps): JSX.Element {
|
export default function TextInput(props: TextInputProps): JSX.Element {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { type ReactElement, cloneElement, HTMLAttributes } from "react";
|
import type { HTMLAttributes } from "react";
|
||||||
|
import { type ReactElement, cloneElement } from "react";
|
||||||
|
|
||||||
export default function Tooltip({
|
export default function Tooltip({
|
||||||
id,
|
id,
|
||||||
@@ -15,7 +16,7 @@ export default function Tooltip({
|
|||||||
element as ReactElement<HTMLAttributes<HTMLElement>>,
|
element as ReactElement<HTMLAttributes<HTMLElement>>,
|
||||||
{
|
{
|
||||||
"data-tooltip-target": id,
|
"data-tooltip-target": id,
|
||||||
} as HTMLAttributes<HTMLElement>
|
} as unknown as HTMLAttributes<HTMLElement>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
id={id}
|
id={id}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta, StoryFn } from "@storybook/react-vite";
|
import type { Meta, StoryFn } from "@storybook/react-vite";
|
||||||
import { Troubleshoot } from "./Troubleshoot";
|
import { Troubleshoot } from "./Troubleshoot";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
|
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import { action } from "storybook/actions";
|
import { action } from "storybook/actions";
|
||||||
import DropDown from "./DropDown";
|
import DropDown from "./DropDown";
|
||||||
import { BsSlack, BsGithub } from "react-icons/bs";
|
import { BsSlack, BsGithub } from "react-icons/bs";
|
||||||
@@ -10,7 +10,7 @@ const meta = {
|
|||||||
*/
|
*/
|
||||||
title: "DropDown",
|
title: "DropDown",
|
||||||
component: DropDown,
|
component: DropDown,
|
||||||
} as Meta<typeof DropDown>;
|
} as unknown as Meta<typeof DropDown>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
import { Fragment, useEffect, useRef, useState } from "react";
|
||||||
import ArrowDownIcon from "../../assets/arrow-down-icon.svg";
|
import ArrowDownIcon from "../../assets/arrow-down-icon.svg";
|
||||||
|
|
||||||
export type DropDownItem = {
|
export type DropDownItem = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
|
|
||||||
import { Header } from "./Header";
|
import { Header } from "./Header";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import StatusLabel, { DeploymentStatus } from "./StatusLabel";
|
import StatusLabel, { DeploymentStatus } from "./StatusLabel";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import { AiOutlineReload } from "react-icons/ai";
|
import { AiOutlineReload } from "react-icons/ai";
|
||||||
|
|
||||||
type StatusLabelProps = {
|
|
||||||
status: string;
|
|
||||||
isRollback?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum DeploymentStatus {
|
export enum DeploymentStatus {
|
||||||
DEPLOYED = "deployed",
|
DEPLOYED = "deployed",
|
||||||
FAILED = "failed",
|
FAILED = "failed",
|
||||||
@@ -12,6 +7,11 @@ export enum DeploymentStatus {
|
|||||||
SUPERSEDED = "superseded",
|
SUPERSEDED = "superseded",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StatusLabelProps = {
|
||||||
|
status: DeploymentStatus;
|
||||||
|
isRollback?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export function getStatusColor(status: DeploymentStatus) {
|
export function getStatusColor(status: DeploymentStatus) {
|
||||||
if (status === DeploymentStatus.DEPLOYED) return "text-deployed";
|
if (status === DeploymentStatus.DEPLOYED) return "text-deployed";
|
||||||
if (status === DeploymentStatus.FAILED) return "text-failed";
|
if (status === DeploymentStatus.FAILED) return "text-failed";
|
||||||
@@ -20,7 +20,7 @@ export function getStatusColor(status: DeploymentStatus) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function StatusLabel({ status, isRollback }: StatusLabelProps) {
|
function StatusLabel({ status, isRollback }: StatusLabelProps) {
|
||||||
const statusColor = getStatusColor(status as DeploymentStatus);
|
const statusColor = getStatusColor(status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { StoryFn, Meta } from "@storybook/react-vite";
|
import type { StoryFn, Meta } from "@storybook/react-vite";
|
||||||
import AddRepositoryModal from "./AddRepositoryModal";
|
import AddRepositoryModal from "./AddRepositoryModal";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const addRepository = () => {
|
const addRepository = async () => {
|
||||||
const body = new FormData();
|
const body = new FormData();
|
||||||
body.append("name", formData.name ?? "");
|
body.append("name", formData.name ?? "");
|
||||||
body.append("url", formData.url ?? "");
|
body.append("url", formData.url ?? "");
|
||||||
@@ -45,32 +45,34 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
|||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
apiService
|
try {
|
||||||
.fetchWithDefaults<void>("/api/helm/repositories", {
|
await apiService.fetchWithDefaults<void>("/api/helm/repositories", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body,
|
body,
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
onClose();
|
|
||||||
|
|
||||||
queryClient.invalidateQueries({
|
|
||||||
queryKey: ["helm", "repositories"],
|
|
||||||
});
|
|
||||||
setSelectedRepo(formData.name || "");
|
|
||||||
navigate(`/repository/${formData.name}`, {
|
|
||||||
replace: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
alertError.setShowErrorModal({
|
|
||||||
title: "Failed to add repo",
|
|
||||||
msg: error.message,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
onClose();
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["helm", "repositories"],
|
||||||
|
});
|
||||||
|
setSelectedRepo(formData.name || "");
|
||||||
|
await navigate(`/repository/${formData.name}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
alertError.setShowErrorModal({
|
||||||
|
title: "Failed to add repo",
|
||||||
|
msg: err instanceof Error ? err.message : String(err),
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddRepository = () => {
|
||||||
|
void addRepository();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -84,7 +86,7 @@ function AddRepositoryModal({ isOpen, onClose }: AddRepositoryModalProps) {
|
|||||||
<button
|
<button
|
||||||
data-cy="add-chart-repository-button"
|
data-cy="add-chart-repository-button"
|
||||||
className="flex cursor-pointer items-center rounded-lg bg-primary px-3 py-1.5 text-center text-base font-medium text-white hover:bg-add-repo focus:ring-4 focus:ring-blue-300 focus:outline-hidden disabled:bg-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
className="flex cursor-pointer items-center rounded-lg bg-primary px-3 py-1.5 text-center text-base font-medium text-white hover:bg-add-repo focus:ring-4 focus:ring-blue-300 focus:outline-hidden disabled:bg-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
|
||||||
onClick={addRepository}
|
onClick={handleAddRepository}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{isLoading && <Spinner size={4} />}
|
{isLoading && <Spinner size={4} />}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { action } from "storybook/actions";
|
import { action } from "storybook/actions";
|
||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import ErrorModal from "./ErrorModal";
|
import ErrorModal from "./ErrorModal";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
import { useEffect, useEffectEvent, useMemo, useState } from "react";
|
||||||
|
import type { VersionData } from "../../../API/releases";
|
||||||
import {
|
import {
|
||||||
useChartReleaseValues,
|
useChartReleaseValues,
|
||||||
useGetReleaseManifest,
|
useGetReleaseManifest,
|
||||||
useGetVersions,
|
useGetVersions,
|
||||||
useVersionData,
|
useVersionData,
|
||||||
VersionData,
|
|
||||||
} from "../../../API/releases";
|
} from "../../../API/releases";
|
||||||
import Modal, { ModalButtonStyle } from "../Modal";
|
import Modal, { ModalButtonStyle } from "../Modal";
|
||||||
import { GeneralDetails } from "./GeneralDetails";
|
import { GeneralDetails } from "./GeneralDetails";
|
||||||
@@ -17,11 +17,11 @@ import { isNoneEmptyArray } from "../../../utils";
|
|||||||
import useCustomSearchParams from "../../../hooks/useCustomSearchParams";
|
import useCustomSearchParams from "../../../hooks/useCustomSearchParams";
|
||||||
import { useChartRepoValues } from "../../../API/repositories";
|
import { useChartRepoValues } from "../../../API/repositories";
|
||||||
import { useDiffData } from "../../../API/shared";
|
import { useDiffData } from "../../../API/shared";
|
||||||
import { InstallChartModalProps } from "../../../data/types";
|
import type { InstallChartModalProps } from "../../../data/types";
|
||||||
import { DefinedValues } from "./DefinedValues";
|
import { DefinedValues } from "./DefinedValues";
|
||||||
import apiService from "../../../API/apiService";
|
import apiService from "../../../API/apiService";
|
||||||
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
|
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
|
||||||
import { LatestChartVersion } from "../../../API/interfaces";
|
import type { LatestChartVersion } from "../../../API/interfaces";
|
||||||
|
|
||||||
export const InstallReleaseChartModal = ({
|
export const InstallReleaseChartModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -129,7 +129,7 @@ export const InstallReleaseChartModal = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Confirm method (install)
|
// Confirm method (install)
|
||||||
const setReleaseVersionMutation = useMutation<VersionData>({
|
const setReleaseVersionMutation = useMutation<VersionData, Error>({
|
||||||
mutationKey: [
|
mutationKey: [
|
||||||
"setVersion",
|
"setVersion",
|
||||||
namespace,
|
namespace,
|
||||||
@@ -148,20 +148,23 @@ export const InstallReleaseChartModal = ({
|
|||||||
}
|
}
|
||||||
formData.append("version", selectedVersion || "");
|
formData.append("version", selectedVersion || "");
|
||||||
formData.append("values", userValues || releaseValues || ""); // if userValues is empty, we use the release values
|
formData.append("values", userValues || releaseValues || ""); // if userValues is empty, we use the release values
|
||||||
return await apiService.fetchWithDefaults(
|
const url = `/api/helm/releases/${
|
||||||
`/api/helm/releases/${
|
namespace ? namespace : "default"
|
||||||
namespace ? namespace : "default"
|
}/${releaseName}`;
|
||||||
}${`/${releaseName}`}`,
|
|
||||||
{
|
return await apiService.fetchWithSafeDefaults<VersionData>({
|
||||||
|
url,
|
||||||
|
options: {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: formData,
|
body: formData,
|
||||||
}
|
},
|
||||||
);
|
fallback: { version: "", urls: [""] },
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onSuccess: async (response) => {
|
onSuccess: async (response) => {
|
||||||
onClose();
|
onClose();
|
||||||
setSelectedVersionData({ version: "", urls: [] }); //cleanup
|
setSelectedVersionData({ version: "", urls: [] }); //cleanup
|
||||||
navigate(
|
await navigate(
|
||||||
`/${
|
`/${
|
||||||
namespace ? namespace : "default"
|
namespace ? namespace : "default"
|
||||||
}/${releaseName}/installed/revision/${response.version}`
|
}/${releaseName}/installed/revision/${response.version}`
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import useNavigateWithSearchParams from "../../../hooks/useNavigateWithSearchPar
|
|||||||
import { VersionToInstall } from "./VersionToInstall";
|
import { VersionToInstall } from "./VersionToInstall";
|
||||||
import { isNoneEmptyArray } from "../../../utils";
|
import { isNoneEmptyArray } from "../../../utils";
|
||||||
import { useDiffData } from "../../../API/shared";
|
import { useDiffData } from "../../../API/shared";
|
||||||
import { InstallChartModalProps } from "../../../data/types";
|
import type { InstallChartModalProps } from "../../../data/types";
|
||||||
import { DefinedValues } from "./DefinedValues";
|
import { DefinedValues } from "./DefinedValues";
|
||||||
import apiService from "../../../API/apiService";
|
import apiService from "../../../API/apiService";
|
||||||
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
|
import { InstallUpgradeTitle } from "./InstallUpgradeTitle";
|
||||||
import { LatestChartVersion } from "../../../API/interfaces";
|
import type { LatestChartVersion } from "../../../API/interfaces";
|
||||||
|
|
||||||
export const InstallRepoChartModal = ({
|
export const InstallRepoChartModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
@@ -135,18 +135,21 @@ export const InstallRepoChartModal = ({
|
|||||||
formData.append("values", userValues);
|
formData.append("values", userValues);
|
||||||
formData.append("name", releaseName || "");
|
formData.append("name", releaseName || "");
|
||||||
|
|
||||||
return await apiService.fetchWithDefaults(
|
return await apiService.fetchWithSafeDefaults({
|
||||||
`/api/helm/releases/${namespace ? namespace : "default"}`,
|
url: `/api/helm/releases/${namespace ? namespace : "default"}`,
|
||||||
{
|
options: {
|
||||||
method: "post",
|
method: "post",
|
||||||
body: formData,
|
body: formData,
|
||||||
}
|
},
|
||||||
);
|
fallback: { namespace: "", name: "" },
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: async (response: { namespace: string; name: string }) => {
|
onSuccess: async (response: { namespace: string; name: string }) => {
|
||||||
onClose();
|
onClose();
|
||||||
navigate(`/${response.namespace}/${response.name}/installed/revision/1`);
|
await navigate(
|
||||||
|
`/${response.namespace}/${response.name}/installed/revision/1`
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
setInstallError(error?.message || "Failed to update");
|
setInstallError(error?.message || "Failed to update");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC } from "react";
|
import type { FC } from "react";
|
||||||
|
|
||||||
interface InstallUpgradeProps {
|
interface InstallUpgradeProps {
|
||||||
isUpgrade: boolean;
|
isUpgrade: boolean;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { FC, useMemo, useState } from "react";
|
import type { FC } from "react";
|
||||||
import Select, { components, GroupBase, SingleValueProps } from "react-select";
|
import { useMemo, useState } from "react";
|
||||||
|
import type { GroupBase, SingleValueProps } from "react-select";
|
||||||
|
import Select, { components } from "react-select";
|
||||||
import { BsCheck2 } from "react-icons/bs";
|
import { BsCheck2 } from "react-icons/bs";
|
||||||
import { NonEmptyArray } from "../../../data/types";
|
import type { NonEmptyArray } from "../../../data/types";
|
||||||
|
|
||||||
interface Version {
|
interface Version {
|
||||||
repository: string;
|
repository: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { action } from "storybook/actions";
|
import { action } from "storybook/actions";
|
||||||
import { StoryObj, StoryFn, Meta } from "@storybook/react-vite";
|
import type { StoryObj, StoryFn, Meta } from "@storybook/react-vite";
|
||||||
import Modal, { ModalAction, ModalButtonStyle } from "./Modal";
|
import type { ModalAction } from "./Modal";
|
||||||
|
import Modal, { ModalButtonStyle } from "./Modal";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
/* 👇 The title prop is optional.
|
/* 👇 The title prop is optional.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PropsWithChildren, ReactNode } from "react";
|
import type { PropsWithChildren, ReactNode } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import Spinner from "../Spinner";
|
import Spinner from "../Spinner";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta } from "@storybook/react-vite";
|
import type { Meta } from "@storybook/react-vite";
|
||||||
import ChartViewer from "./ChartViewer";
|
import ChartViewer from "./ChartViewer";
|
||||||
|
|
||||||
//👇 This default export determines where your story goes in the story list
|
//👇 This default export determines where your story goes in the story list
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Chart } from "../../data/types";
|
import type { Chart } from "../../data/types";
|
||||||
import { InstallRepoChartModal } from "../modal/InstallChartModal/InstallRepoChartModal";
|
import { InstallRepoChartModal } from "../modal/InstallChartModal/InstallRepoChartModal";
|
||||||
|
|
||||||
type ChartViewerProps = {
|
type ChartViewerProps = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { StoryFn, Meta } from "@storybook/react-vite";
|
import type { StoryFn, Meta } from "@storybook/react-vite";
|
||||||
import RepositoriesList from "./RepositoriesList";
|
import RepositoriesList from "./RepositoriesList";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import AddRepositoryModal from "../modal/AddRepositoryModal";
|
import AddRepositoryModal from "../modal/AddRepositoryModal";
|
||||||
import { Repository } from "../../data/types";
|
import type { Repository } from "../../data/types";
|
||||||
import useCustomSearchParams from "../../hooks/useCustomSearchParams";
|
import useCustomSearchParams from "../../hooks/useCustomSearchParams";
|
||||||
|
|
||||||
type RepositoriesListProps = {
|
type RepositoriesListProps = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { StoryFn, Meta } from "@storybook/react-vite";
|
import type { StoryFn, Meta } from "@storybook/react-vite";
|
||||||
import RepositoryViewer from "./RepositoryViewer";
|
import RepositoryViewer from "./RepositoryViewer";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BsTrash3, BsArrowRepeat } from "react-icons/bs";
|
import { BsTrash3, BsArrowRepeat } from "react-icons/bs";
|
||||||
import { Chart, Repository } from "../../data/types";
|
import type { Chart, Repository } from "../../data/types";
|
||||||
import ChartViewer from "./ChartViewer";
|
import ChartViewer from "./ChartViewer";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import apiService from "../../API/apiService";
|
import apiService from "../../API/apiService";
|
||||||
@@ -52,9 +52,9 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
|||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
navigate("/repository", { replace: true });
|
await navigate("/repository", { replace: true });
|
||||||
setSelectedRepo("");
|
setSelectedRepo("");
|
||||||
queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: ["helm", "repositories"],
|
queryKey: ["helm", "repositories"],
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -104,7 +104,7 @@ function RepositoryViewer({ repository }: RepositoryViewerProps) {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
removeRepository();
|
void removeRepository();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="flex h-8 items-center gap-2 rounded-sm border border-gray-300 bg-white px-5 py-1 text-sm font-semibold">
|
<span className="flex h-8 items-center gap-2 rounded-sm border border-gray-300 bg-white px-5 py-1 text-sm font-semibold">
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
BsArrowUp,
|
BsArrowUp,
|
||||||
BsCheckCircle,
|
BsCheckCircle,
|
||||||
} from "react-icons/bs";
|
} from "react-icons/bs";
|
||||||
import { Release, ReleaseRevision } from "../../data/types";
|
import type { ReleaseRevision } from "../../data/types";
|
||||||
import StatusLabel, { DeploymentStatus } from "../common/StatusLabel";
|
import StatusLabel, { DeploymentStatus } from "../common/StatusLabel";
|
||||||
import { useNavigate, useParams, useSearchParams } from "react-router";
|
import { useNavigate, useParams, useSearchParams } from "react-router";
|
||||||
import {
|
import {
|
||||||
@@ -39,7 +39,7 @@ type RevisionTagProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type RevisionDetailsProps = {
|
type RevisionDetailsProps = {
|
||||||
release: Release;
|
release: ReleaseRevision;
|
||||||
installedRevision: ReleaseRevision;
|
installedRevision: ReleaseRevision;
|
||||||
isLatest: boolean;
|
isLatest: boolean;
|
||||||
latestRevision: number;
|
latestRevision: number;
|
||||||
@@ -105,7 +105,7 @@ export default function RevisionDetails({
|
|||||||
setShowTestResults(false);
|
setShowTestResults(false);
|
||||||
setShowErrorModal({
|
setShowErrorModal({
|
||||||
title: "Failed to run tests for chart " + chart,
|
title: "Failed to run tests for chart " + chart,
|
||||||
msg: (error as Error).message,
|
msg: error.message,
|
||||||
});
|
});
|
||||||
console.error("Failed to execute test for chart", error);
|
console.error("Failed to execute test for chart", error);
|
||||||
},
|
},
|
||||||
@@ -135,7 +135,7 @@ export default function RevisionDetails({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const displayTestResults = () => {
|
const displayTestResults = () => {
|
||||||
if (!testResults || (testResults as []).length === 0) {
|
if (!testResults || !testResults.length) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
Tests executed successfully
|
Tests executed successfully
|
||||||
@@ -147,7 +147,7 @@ export default function RevisionDetails({
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{(testResults as string).split("\n").map((line, index) => (
|
{testResults.split("\n").map((line, index) => (
|
||||||
<div key={index} className="mb-2">
|
<div key={index} className="mb-2">
|
||||||
{line}
|
{line}
|
||||||
<br />
|
<br />
|
||||||
@@ -160,6 +160,13 @@ export default function RevisionDetails({
|
|||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const addRepo = async () => {
|
||||||
|
await navigate(
|
||||||
|
`/repository?add_repo=true&repo_url=${latestVerData?.[0]?.urls[0]}&repo_name=${latestVerData?.[0]?.repository}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex flex-wrap justify-between">
|
<header className="flex flex-wrap justify-between">
|
||||||
<h1 className="float-left mb-1 font-roboto-slab text-3xl font-semibold">
|
<h1 className="float-left mb-1 font-roboto-slab text-3xl font-semibold">
|
||||||
@@ -206,9 +213,7 @@ export default function RevisionDetails({
|
|||||||
{latestVerData?.[0]?.isSuggestedRepo ? (
|
{latestVerData?.[0]?.isSuggestedRepo ? (
|
||||||
<span
|
<span
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(
|
void addRepo();
|
||||||
`/repository?add_repo=true&repo_url=${latestVerData[0].urls[0]}&repo_name=${latestVerData[0].repository}`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
className="cursor-pointer text-sm text-blue-600 underline"
|
className="cursor-pointer text-sm text-blue-600 underline"
|
||||||
>
|
>
|
||||||
@@ -216,7 +221,7 @@ export default function RevisionDetails({
|
|||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span
|
<span
|
||||||
onClick={() => refetchLatestVersion()}
|
onClick={() => void refetchLatestVersion()}
|
||||||
className="cursor-pointer text-xs underline"
|
className="cursor-pointer text-xs underline"
|
||||||
>
|
>
|
||||||
Check for new version
|
Check for new version
|
||||||
@@ -317,7 +322,7 @@ const Rollback = ({
|
|||||||
release,
|
release,
|
||||||
installedRevision,
|
installedRevision,
|
||||||
}: {
|
}: {
|
||||||
release: Release;
|
release: ReleaseRevision;
|
||||||
installedRevision: ReleaseRevision;
|
installedRevision: ReleaseRevision;
|
||||||
}) => {
|
}) => {
|
||||||
const { chart, namespace, revision } = useParams();
|
const { chart, namespace, revision } = useParams();
|
||||||
@@ -328,8 +333,8 @@ const Rollback = ({
|
|||||||
|
|
||||||
const { mutate: rollbackRelease, isPending: isRollingBackRelease } =
|
const { mutate: rollbackRelease, isPending: isRollingBackRelease } =
|
||||||
useRollbackRelease({
|
useRollbackRelease({
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
navigate(
|
await navigate(
|
||||||
`/${namespace}/${chart}/installed/revision/${revisionInt + 1}`
|
`/${namespace}/${chart}/installed/revision/${revisionInt + 1}`
|
||||||
);
|
);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ChangeEvent, useMemo, useState, useRef, useEffect } from "react";
|
import type { ChangeEvent } from "react";
|
||||||
|
import { useMemo, useState, useRef, useEffect } from "react";
|
||||||
import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js";
|
import { Diff2HtmlUI } from "diff2html/lib/ui/js/diff2html-ui-slim.js";
|
||||||
import { useGetReleaseInfoByType } from "../../API/releases";
|
import { useGetReleaseInfoByType } from "../../API/releases";
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ import { useParams } from "react-router";
|
|||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
import { RiExternalLinkLine } from "react-icons/ri";
|
import { RiExternalLinkLine } from "react-icons/ri";
|
||||||
|
|
||||||
import {
|
import type { StructuredResources } from "../../API/releases";
|
||||||
StructuredResources,
|
import { useGetResourceDescription, useGetResources } from "../../API/releases";
|
||||||
useGetResourceDescription,
|
|
||||||
useGetResources,
|
|
||||||
} from "../../API/releases";
|
|
||||||
import closeIcon from "../../assets/close.png";
|
import closeIcon from "../../assets/close.png";
|
||||||
|
|
||||||
import Drawer from "react-modern-drawer";
|
import Drawer from "react-modern-drawer";
|
||||||
@@ -25,7 +22,6 @@ interface Props {
|
|||||||
export default function RevisionResource({ isLatest }: Props) {
|
export default function RevisionResource({ isLatest }: Props) {
|
||||||
const { namespace = "", chart = "" } = useParams();
|
const { namespace = "", chart = "" } = useParams();
|
||||||
const { data: resources, isLoading } = useGetResources(namespace, chart);
|
const { data: resources, isLoading } = useGetResources(namespace, chart);
|
||||||
const interestingResources = ["STATEFULSET", "DEAMONSET", "DEPLOYMENT"];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table
|
<table
|
||||||
@@ -46,23 +42,15 @@ export default function RevisionResource({ isLatest }: Props) {
|
|||||||
) : (
|
) : (
|
||||||
<tbody className="mt-4 h-8 w-full rounded-sm bg-white">
|
<tbody className="mt-4 h-8 w-full rounded-sm bg-white">
|
||||||
{resources?.length ? (
|
{resources?.length ? (
|
||||||
resources
|
resources?.map((resource: StructuredResources) => (
|
||||||
.sort(function (a, b) {
|
<ResourceRow
|
||||||
return (
|
key={
|
||||||
interestingResources.indexOf(a.kind.toUpperCase()) -
|
resource.apiVersion + resource.kind + resource.metadata.name
|
||||||
interestingResources.indexOf(b.kind.toUpperCase())
|
}
|
||||||
);
|
resource={resource}
|
||||||
})
|
isLatest={isLatest}
|
||||||
.reverse()
|
/>
|
||||||
.map((resource: StructuredResources) => (
|
))
|
||||||
<ResourceRow
|
|
||||||
key={
|
|
||||||
resource.apiVersion + resource.kind + resource.metadata.name
|
|
||||||
}
|
|
||||||
resource={resource}
|
|
||||||
isLatest={isLatest}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<div className="display-none no-charts mt-3 rounded-sm bg-white p-4 text-sm shadow-sm">
|
<div className="display-none no-charts mt-3 rounded-sm bg-white p-4 text-sm shadow-sm">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { BsArrowDownRight, BsArrowUpRight } from "react-icons/bs";
|
|||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import { compare } from "compare-versions";
|
import { compare } from "compare-versions";
|
||||||
|
|
||||||
import { ReleaseRevision } from "../../data/types";
|
import type { ReleaseRevision } from "../../data/types";
|
||||||
import { getAge } from "../../timeUtils";
|
import { getAge } from "../../timeUtils";
|
||||||
import StatusLabel from "../common/StatusLabel";
|
import StatusLabel from "../common/StatusLabel";
|
||||||
import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams";
|
import useNavigateWithSearchParams from "../../hooks/useNavigateWithSearchParams";
|
||||||
@@ -20,8 +20,8 @@ export default function RevisionsList({
|
|||||||
const navigate = useNavigateWithSearchParams();
|
const navigate = useNavigateWithSearchParams();
|
||||||
const { namespace, chart } = useParams();
|
const { namespace, chart } = useParams();
|
||||||
|
|
||||||
const changeRelease = (newRevision: number) => {
|
const changeRelease = async (newRevision: number) => {
|
||||||
navigate(`/${namespace}/${chart}/installed/revision/${newRevision}`);
|
await navigate(`/${namespace}/${chart}/installed/revision/${newRevision}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -38,7 +38,7 @@ export default function RevisionsList({
|
|||||||
title={
|
title={
|
||||||
isRollback ? `Rollback to ${Number(release.revision) - 1}` : ""
|
isRollback ? `Rollback to ${Number(release.revision) - 1}` : ""
|
||||||
}
|
}
|
||||||
onClick={() => changeRelease(release.revision)}
|
onClick={() => void changeRelease(release.revision)}
|
||||||
key={release.revision}
|
key={release.revision}
|
||||||
className={`mx-5 flex cursor-pointer flex-col gap-4 rounded-md border border-gray-200 p-2 ${
|
className={`mx-5 flex cursor-pointer flex-col gap-4 rounded-md border border-gray-200 p-2 ${
|
||||||
release.revision === selectedRevision
|
release.revision === selectedRevision
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
import { createContext, useState, useContext } from "react";
|
import { createContext, useState, useContext } from "react";
|
||||||
|
|
||||||
export interface AppContextData {
|
export interface AppContextData {
|
||||||
@@ -17,11 +18,7 @@ export const useAppContext = (): AppContextData => {
|
|||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppContextProvider = ({
|
export const AppContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
const [selectedRepo, setSelectedRepo] = useState("");
|
const [selectedRepo, setSelectedRepo] = useState("");
|
||||||
const [clusterMode, setClusterMode] = useState(false);
|
const [clusterMode, setClusterMode] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { DeploymentStatus } from "../components/common/StatusLabel";
|
||||||
|
|
||||||
export type Chart = {
|
export type Chart = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -35,7 +37,7 @@ export type Release = {
|
|||||||
namespace: string;
|
namespace: string;
|
||||||
revision: number;
|
revision: number;
|
||||||
updated: string;
|
updated: string;
|
||||||
status: string;
|
status: DeploymentStatus;
|
||||||
chart: string;
|
chart: string;
|
||||||
chart_name: string;
|
chart_name: string;
|
||||||
chart_ver: string;
|
chart_ver: string;
|
||||||
@@ -79,7 +81,7 @@ export type Repository = {
|
|||||||
export type ReleaseRevision = {
|
export type ReleaseRevision = {
|
||||||
revision: number;
|
revision: number;
|
||||||
updated: string;
|
updated: string;
|
||||||
status: string;
|
status: DeploymentStatus;
|
||||||
chart: string;
|
chart: string;
|
||||||
app_version: string;
|
app_version: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
|||||||
@@ -12,19 +12,14 @@ const useNavigateWithSearchParams = () => {
|
|||||||
const { context } = useParams();
|
const { context } = useParams();
|
||||||
|
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const navigateWithSearchParams = (
|
return async (url: string, ...restArgs: NavigateOptions[]) => {
|
||||||
url: string,
|
|
||||||
...restArgs: NavigateOptions[]
|
|
||||||
) => {
|
|
||||||
let prefixedUrl = url;
|
let prefixedUrl = url;
|
||||||
|
|
||||||
if (!clusterMode) {
|
if (!clusterMode) {
|
||||||
prefixedUrl = `/${encodeURIComponent(context ?? "")}${url}`;
|
prefixedUrl = `/${encodeURIComponent(context ?? "")}${url}`;
|
||||||
}
|
}
|
||||||
navigate(`${prefixedUrl}${search}`, ...restArgs);
|
await navigate(`${prefixedUrl}${search}`, ...restArgs);
|
||||||
};
|
};
|
||||||
|
|
||||||
return navigateWithSearchParams;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useNavigateWithSearchParams;
|
export default useNavigateWithSearchParams;
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ export default function Header() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetCache = () => {
|
||||||
|
void resetCache();
|
||||||
|
};
|
||||||
|
|
||||||
const openAPI = () => {
|
const openAPI = () => {
|
||||||
window.open("/#/docs", "_blank");
|
window.open("/#/docs", "_blank");
|
||||||
};
|
};
|
||||||
@@ -72,7 +76,7 @@ export default function Header() {
|
|||||||
<ul className="flex w-full items-center md:mt-0 md:flex-row md:justify-between md:border-0 md:text-sm md:font-normal">
|
<ul className="flex w-full items-center md:mt-0 md:flex-row md:justify-between md:border-0 md:text-sm md:font-normal">
|
||||||
<li>
|
<li>
|
||||||
<LinkWithSearchParams
|
<LinkWithSearchParams
|
||||||
to={"/installed"}
|
to={"installed"}
|
||||||
exclude={["tab"]}
|
exclude={["tab"]}
|
||||||
className={getBtnStyle("installed")}
|
className={getBtnStyle("installed")}
|
||||||
>
|
>
|
||||||
@@ -81,7 +85,7 @@ export default function Header() {
|
|||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<LinkWithSearchParams
|
<LinkWithSearchParams
|
||||||
to={"/repository"}
|
to={"repository"}
|
||||||
exclude={["tab"]}
|
exclude={["tab"]}
|
||||||
end={false}
|
end={false}
|
||||||
className={getBtnStyle("repository")}
|
className={getBtnStyle("repository")}
|
||||||
@@ -103,7 +107,7 @@ export default function Header() {
|
|||||||
id: "4",
|
id: "4",
|
||||||
text: "Reset Cache",
|
text: "Reset Cache",
|
||||||
icon: <BsArrowRepeat />,
|
icon: <BsArrowRepeat />,
|
||||||
onClick: resetCache,
|
onClick: handleResetCache,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "5",
|
id: "5",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import "../App.css";
|
import "../App.css";
|
||||||
import { JSX } from "react";
|
import type { JSX } from "react";
|
||||||
|
|
||||||
function Sidebar(): JSX.Element {
|
function Sidebar(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Spinner from "../components/Spinner";
|
|||||||
import useAlertError from "../hooks/useAlertError";
|
import useAlertError from "../hooks/useAlertError";
|
||||||
import { useParams, useNavigate } from "react-router";
|
import { useParams, useNavigate } from "react-router";
|
||||||
import useCustomSearchParams from "../hooks/useCustomSearchParams";
|
import useCustomSearchParams from "../hooks/useCustomSearchParams";
|
||||||
import { Release } from "../data/types";
|
import type { Release } from "../data/types";
|
||||||
|
|
||||||
function Installed() {
|
function Installed() {
|
||||||
const { searchParamsObject } = useCustomSearchParams();
|
const { searchParamsObject } = useCustomSearchParams();
|
||||||
@@ -19,12 +19,16 @@ function Installed() {
|
|||||||
);
|
);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleClusterChange = (clusterName: string) => {
|
const clusterChange = async (clusterName: string) => {
|
||||||
navigate({
|
await navigate({
|
||||||
pathname: `/${encodeURIComponent(clusterName)}/installed`,
|
pathname: `/${encodeURIComponent(clusterName)}/installed`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClusterChange = (clusterName: string) => {
|
||||||
|
void clusterChange(clusterName);
|
||||||
|
};
|
||||||
|
|
||||||
const [filterKey, setFilterKey] = useState<string>("");
|
const [filterKey, setFilterKey] = useState<string>("");
|
||||||
const alertError = useAlertError();
|
const alertError = useAlertError();
|
||||||
const { data, isLoading, isRefetching, isError, error } =
|
const { data, isLoading, isRefetching, isError, error } =
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useMemo, useEffect, useEffectEvent } from "react";
|
import { useMemo, useEffect, useEffectEvent, useCallback } from "react";
|
||||||
|
|
||||||
import RepositoriesList from "../components/repository/RepositoriesList";
|
import RepositoriesList from "../components/repository/RepositoriesList";
|
||||||
import RepositoryViewer from "../components/repository/RepositoryViewer";
|
import RepositoryViewer from "../components/repository/RepositoryViewer";
|
||||||
import { Repository } from "../data/types";
|
import type { Repository } from "../data/types";
|
||||||
import { useGetRepositories } from "../API/repositories";
|
import { useGetRepositories } from "../API/repositories";
|
||||||
import { useParams } from "react-router";
|
import { type NavigateOptions, useParams } from "react-router";
|
||||||
import { useAppContext } from "../context/AppContext";
|
import { useAppContext } from "../context/AppContext";
|
||||||
import useNavigateWithSearchParams from "../hooks/useNavigateWithSearchParams";
|
import useNavigateWithSearchParams from "../hooks/useNavigateWithSearchParams";
|
||||||
|
|
||||||
@@ -13,8 +13,15 @@ function RepositoryPage() {
|
|||||||
const navigate = useNavigateWithSearchParams();
|
const navigate = useNavigateWithSearchParams();
|
||||||
const { setSelectedRepo, selectedRepo } = useAppContext();
|
const { setSelectedRepo, selectedRepo } = useAppContext();
|
||||||
|
|
||||||
|
const navigateTo = useCallback(
|
||||||
|
async (url: string, ...restArgs: NavigateOptions[]) => {
|
||||||
|
await navigate(url, ...restArgs);
|
||||||
|
},
|
||||||
|
[navigate]
|
||||||
|
);
|
||||||
|
|
||||||
const handleRepositoryChanged = (selectedRepository: Repository) => {
|
const handleRepositoryChanged = (selectedRepository: Repository) => {
|
||||||
navigate(`/repository/${selectedRepository.name}`, {
|
void navigateTo(`/repository/${selectedRepository.name}`, {
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -27,22 +34,17 @@ function RepositoryPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedRepo && !repoFromParams) {
|
if (selectedRepo && !repoFromParams) {
|
||||||
navigate(`/repository/${selectedRepo}`, {
|
void navigateTo(`/repository/${selectedRepo}`, {
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [selectedRepo, repoFromParams, context, navigate]);
|
}, [selectedRepo, repoFromParams, context, navigateTo]);
|
||||||
|
|
||||||
const { data: repositories = [], isSuccess } = useGetRepositories();
|
const { data: repositories = [], isSuccess } = useGetRepositories();
|
||||||
|
|
||||||
const onSuccess = useEffectEvent(() => {
|
const onSuccess = useEffectEvent(() => {
|
||||||
// TODO should we passe sorted to RepositoriesList as in ClustersList?
|
if (repositories && repositories.length && !repoFromParams) {
|
||||||
const sortedData = [...repositories]?.sort((a, b) =>
|
handleRepositoryChanged(repositories[0]);
|
||||||
a.name.localeCompare(b.name)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (sortedData && sortedData.length > 0 && !repoFromParams) {
|
|
||||||
handleRepositoryChanged(sortedData[0]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMemo } from "react";
|
|||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import RevisionDetails from "../components/revision/RevisionDetails";
|
import RevisionDetails from "../components/revision/RevisionDetails";
|
||||||
import RevisionsList from "../components/revision/RevisionsList";
|
import RevisionsList from "../components/revision/RevisionsList";
|
||||||
import { Release, ReleaseRevision } from "../data/types";
|
import type { ReleaseRevision } from "../data/types";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import apiService from "../API/apiService";
|
import apiService from "../API/apiService";
|
||||||
import Spinner from "../components/Spinner";
|
import Spinner from "../components/Spinner";
|
||||||
@@ -19,6 +19,7 @@ function Revision() {
|
|||||||
{
|
{
|
||||||
queryKey: ["releasesHistory", restParams],
|
queryKey: ["releasesHistory", restParams],
|
||||||
queryFn: apiService.getReleasesHistory,
|
queryFn: apiService.getReleasesHistory,
|
||||||
|
select: (data) => data?.sort(descendingSort),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -30,11 +31,6 @@ function Revision() {
|
|||||||
[releaseRevisions]
|
[releaseRevisions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const sortedReleases = useMemo(
|
|
||||||
() => releaseRevisions?.sort(descendingSort),
|
|
||||||
[releaseRevisions]
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedRelease = useMemo(() => {
|
const selectedRelease = useMemo(() => {
|
||||||
if (selectedRevision && releaseRevisions) {
|
if (selectedRevision && releaseRevisions) {
|
||||||
return releaseRevisions.find(
|
return releaseRevisions.find(
|
||||||
@@ -54,7 +50,7 @@ function Revision() {
|
|||||||
<RevisionSidebarSkeleton />
|
<RevisionSidebarSkeleton />
|
||||||
) : (
|
) : (
|
||||||
<RevisionsList
|
<RevisionsList
|
||||||
releaseRevisions={sortedReleases}
|
releaseRevisions={releaseRevisions}
|
||||||
selectedRevision={selectedRevision}
|
selectedRevision={selectedRevision}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -67,7 +63,7 @@ function Revision() {
|
|||||||
</div>
|
</div>
|
||||||
) : selectedRelease ? (
|
) : selectedRelease ? (
|
||||||
<RevisionDetails
|
<RevisionDetails
|
||||||
release={selectedRelease as Release} // TODO fix it
|
release={selectedRelease} // TODO fix it
|
||||||
installedRevision={releaseRevisions?.[0]}
|
installedRevision={releaseRevisions?.[0]}
|
||||||
isLatest={selectedRelease.revision === latestRevision}
|
isLatest={selectedRelease.revision === latestRevision}
|
||||||
latestRevision={latestRevision}
|
latestRevision={latestRevision}
|
||||||
@@ -79,16 +75,12 @@ function Revision() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RevisionSidebarSkeleton = () => {
|
const RevisionSidebarSkeleton = () => {
|
||||||
return (
|
return Array.from({ length: 6 }).map((_, i) => (
|
||||||
<>
|
<div
|
||||||
<div className="mx-5 h-[74px] w-[88%] animate-pulse gap-4 rounded-md border border-gray-200 bg-gray-100 p-2" />
|
key={i}
|
||||||
<div className="mx-5 h-[74px] w-[88%] animate-pulse gap-4 rounded-md border border-gray-200 bg-gray-100 p-2" />
|
className="mx-5 h-[74px] w-[88%] animate-pulse gap-4 rounded-md border border-gray-200 bg-gray-100 p-2"
|
||||||
<div className="mx-5 h-[74px] w-[88%] animate-pulse gap-4 rounded-md border border-gray-200 bg-gray-100 p-2" />
|
/>
|
||||||
<div className="mx-5 h-[74px] w-[88%] animate-pulse gap-4 rounded-md border border-gray-200 bg-gray-100 p-2" />
|
));
|
||||||
<div className="mx-5 h-[74px] w-[88%] animate-pulse gap-4 rounded-md border border-gray-200 bg-gray-100 p-2" />
|
|
||||||
<div className="mx-5 h-[74px] w-[88%] animate-pulse gap-4 rounded-md border border-gray-200 bg-gray-100 p-2" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Revision;
|
export default Revision;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { DateTime, DateTimeMaybeValid, type DurationLikeObject } from "luxon";
|
import type { DateTimeMaybeValid } from "luxon";
|
||||||
import { ReleaseRevision } from "./data/types";
|
import { DateTime, type DurationLikeObject } from "luxon";
|
||||||
|
import type { ReleaseRevision } from "./data/types";
|
||||||
|
|
||||||
export function getAge(obj1: ReleaseRevision, obj2?: ReleaseRevision) {
|
export function getAge(obj1: ReleaseRevision, obj2?: ReleaseRevision) {
|
||||||
const date = DateTime.fromISO(obj1.updated);
|
const date = DateTime.fromISO(obj1.updated);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Diff2HtmlUIConfig } from "diff2html/lib/ui/js/diff2html-ui-base";
|
import type { Diff2HtmlUIConfig } from "diff2html/lib/ui/js/diff2html-ui-base";
|
||||||
import { NonEmptyArray } from "./data/types";
|
import type { NonEmptyArray } from "./data/types";
|
||||||
|
|
||||||
export const isNewerVersion = (oldVer: string, newVer: string) => {
|
export const isNewerVersion = (oldVer: string, newVer: string) => {
|
||||||
if (oldVer && oldVer[0] === "v") {
|
if (oldVer && oldVer[0] === "v") {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
"composite": true,
|
"composite": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -477,10 +477,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/json-schema" "^7.0.15"
|
"@types/json-schema" "^7.0.15"
|
||||||
|
|
||||||
"@eslint/eslintrc@^3.3.1":
|
"@eslint/eslintrc@^3.3.1", "@eslint/eslintrc@^3.3.3":
|
||||||
version "3.3.1"
|
version "3.3.3"
|
||||||
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz"
|
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz"
|
||||||
integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==
|
integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "^6.12.4"
|
ajv "^6.12.4"
|
||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
@@ -488,11 +488,11 @@
|
|||||||
globals "^14.0.0"
|
globals "^14.0.0"
|
||||||
ignore "^5.2.0"
|
ignore "^5.2.0"
|
||||||
import-fresh "^3.2.1"
|
import-fresh "^3.2.1"
|
||||||
js-yaml "^4.1.0"
|
js-yaml "^4.1.1"
|
||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
"@eslint/js@9.39.1":
|
"@eslint/js@^9.39.1", "@eslint/js@9.39.1":
|
||||||
version "9.39.1"
|
version "9.39.1"
|
||||||
resolved "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz"
|
resolved "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz"
|
||||||
integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==
|
integrity sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==
|
||||||
@@ -3333,6 +3333,13 @@ eslint-plugin-storybook@^10.0.8:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/utils" "^8.8.1"
|
"@typescript-eslint/utils" "^8.8.1"
|
||||||
|
|
||||||
|
eslint-plugin-tsc@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.npmjs.org/eslint-plugin-tsc/-/eslint-plugin-tsc-2.0.0.tgz"
|
||||||
|
integrity sha512-we7n063HSoWDpXjuqgplrYxfWnlVgq7GXteEjxtc/Ve6C0BjGQyoNGjApSVspyru1cckAM9ASwPnSU8Y0OTwTA==
|
||||||
|
dependencies:
|
||||||
|
typescript-service "^2.0.3"
|
||||||
|
|
||||||
eslint-scope@^5.1.1:
|
eslint-scope@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
|
||||||
@@ -4454,7 +4461,7 @@ js-file-download@^0.4.12:
|
|||||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||||
|
|
||||||
js-yaml@^4.1.0, js-yaml@=4.1.1:
|
js-yaml@^4.1.0, js-yaml@^4.1.1, js-yaml@=4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz"
|
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz"
|
||||||
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
|
integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
|
||||||
@@ -6454,6 +6461,11 @@ tslib@^1.8.1:
|
|||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
|
tslib@^1.9.3:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||||
|
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||||
|
|
||||||
tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0:
|
tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz"
|
||||||
@@ -6547,6 +6559,13 @@ types-ramda@^0.30.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ts-toolbelt "^9.6.0"
|
ts-toolbelt "^9.6.0"
|
||||||
|
|
||||||
|
typescript-service@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.npmjs.org/typescript-service/-/typescript-service-2.0.3.tgz"
|
||||||
|
integrity sha512-FzRlqRp965UBzGvGwc6rbeko84jLILZrHf++I4cN8usZUB7F8Lh8/WDiCOUvy2l5os+jBWEz4fbYkkj1DhYJcw==
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
typescript@^5.9.3:
|
typescript@^5.9.3:
|
||||||
version "5.9.3"
|
version "5.9.3"
|
||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"
|
resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user