From 24f063c80596bdbd5001a9f2226ecf33118804d7 Mon Sep 17 00:00:00 2001 From: Moshe Immermam Date: Mon, 17 Jun 2024 08:35:59 +0300 Subject: [PATCH 1/5] chore: add helplink component --- .../SpecEditor/ConfigScrapperSpecEditor.tsx | 191 +---------------- .../SpecEditor/HealthSpecEditor.tsx | 113 ++++------ src/components/SpecEditor/ScraperTypes.ts | 202 ++++++++++++++++++ .../SpecEditor/SpecEditor.stories.tsx | 2 + src/components/SpecEditor/SpecEditor.tsx | 2 +- 5 files changed, 251 insertions(+), 259 deletions(-) create mode 100644 src/components/SpecEditor/ScraperTypes.ts diff --git a/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx b/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx index bf2f1a210..b1fa87e0a 100644 --- a/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx +++ b/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx @@ -11,7 +11,8 @@ import KubernetesFileConfigsFormEditor from "../Forms/Configs/KubernetesFileConf import SQLConfigsFormEditor from "../Forms/Configs/SQLConfigsFormEditor"; import TrivyConfigsFormEditor from "../Forms/Configs/TrivyConfigsFormEditor"; import { SchemaResourceType } from "../SchemaResourcePage/resourceTypes"; -import SpecEditor, { SpecType } from "./SpecEditor"; +import SpecEditor from "./SpecEditor"; +import scraperTypes from "./ScraperTypes"; const resourceInfo: Pick = { name: "Catalog Scraper", @@ -32,193 +33,7 @@ export default function ConfigScrapperSpecEditor({ onBack, onDeleted = () => {} }: ConfigScrapperSpecEditorProps) { - const configTypes: SpecType[] = useMemo( - () => - ( - [ - { - type: "form", - name: "kubernetes", - label: "Kubernetes", - description: "Edit kubernetes configs", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - // probably need to query the spec from the backend - return resourceValue ?? {}; - }, - icon: "kubernetes", - configForm: KubernetesConfigsFormEditor, - specsMapField: "kubernetes.0", - schemaFileName: "config_kubernetes.schema.json", - docsLink: - "https://docs.flanksource.com/config-db/scrapers/kubernetes" - }, - { - type: "form", - name: "azure", - label: "Azure", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "azure", - configForm: AzureConfigsFormEditor, - specsMapField: "azure.0", - schemaFileName: "config_azure.schema.json", - docsLink: "https://docs.flanksource.com/config-db/scrapers/azure" - }, - { - type: "form", - name: "kubernetesFile", - label: "Kubernetes File", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "kubernetes", - configForm: KubernetesFileConfigsFormEditor, - specsMapField: "kubernetesFile.0", - schemaFileName: "config_kubernetesfile.schema.json", - docsLink: - "https://docs.flanksource.com/config-db/scrapers/kubernetes-file" - }, - { - type: "form", - name: "sql", - label: "SQL", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "sql", - configForm: SQLConfigsFormEditor, - specsMapField: "sql.0", - schemaFileName: "config_sql.schema.json", - docsLink: "https://docs.flanksource.com/config-db/scrapers/sql" - }, - { - type: "form", - name: "trivy", - label: "Trivy", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "trivy", - configForm: TrivyConfigsFormEditor, - specsMapField: "trivy.0", - schemaFileName: "config_trivy.schema.json", - docsLink: "https://docs.flanksource.com/config-db/scrapers/trivy" - }, - { - type: "form", - name: "aws", - label: "AWS", - description: "Edit aws configs", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - // probably need to query the spec from the backend - return resourceValue ?? {}; - }, - configForm: AWSConfigsFormEditor, - icon: "aws", - specsMapField: "aws.0", - schemaFileName: "config_aws.schema.json", - docsLink: "https://docs.flanksource.com/config-db/scrapers/aws" - }, - { - type: "form", - name: "file", - label: "File", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "folder", - configForm: FileConfigsFormEditor, - specsMapField: "file.0", - schemaFileName: "config_file.schema.json", - docsLink: "https://docs.flanksource.com/config-db/scrapers/file" - }, - { - type: "form", - name: "githubActions", - label: "Github Actions", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "git", - configForm: GithubActionsConfigsFormEditor, - specsMapField: "githubActions.0", - schemaFileName: "config_githubactions.schema.json" - }, - { - type: "form", - name: "http", - label: "HTTP", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "http", - configForm: HttpConfigsFormEditor, - specsMapField: "http.0", - schemaFileName: undefined - }, - { - type: "form", - name: "azureDevops", - label: "Azure DevOps", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "azure-devops", - configForm: AzureDevopsConfigsFormEditor, - specsMapField: "azureDevops.0", - schemaFileName: "config_azuredevops.schema.json", - docsLink: - "https://docs.flanksource.com/config-db/scrapers/azure-devops" - }, - { - type: "custom", - name: "custom", - label: "Custom", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: FaCog, - schemaFileName: "scrape_config.spec.schema.json", - docsLink: "https://docs.flanksource.com/config-db/scrapers/custom" - } - ] satisfies SpecType[] - ).sort((a, b) => a.label.localeCompare(b.label)), - [onSubmit, resourceValue] - ); + const configTypes = scraperTypes(onSubmit, resourceValue); // there should only be one spec, so we can just grab the first key that isn't // schedule, otherwise we'll just use custom diff --git a/src/components/SpecEditor/HealthSpecEditor.tsx b/src/components/SpecEditor/HealthSpecEditor.tsx index ebb063393..106a5b0c2 100644 --- a/src/components/SpecEditor/HealthSpecEditor.tsx +++ b/src/components/SpecEditor/HealthSpecEditor.tsx @@ -1,5 +1,4 @@ import { useMemo } from "react"; -import { FaCog } from "react-icons/fa"; import { AlertmanagerHealthFormEditor } from "../Forms/Health/AlertmanagerHealthFormEditor"; import { ElasticsearchHealthFormEditor } from "../Forms/Health/ElasticsearchHealthFormEditor"; import { ExecHealthFormEditor } from "../Forms/Health/ExecHealthFormEditor"; @@ -49,7 +48,7 @@ export default function HealthSpecEditor({ configForm: HTTPHealthFormEditor, specsMapField: "http.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/http" + docsLink: "canary-checker/reference/http" }, { type: "custom", @@ -63,7 +62,7 @@ export default function HealthSpecEditor({ }, icon: "aws", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/aws-config-rule" + docsLink: "canary-checker/reference/aws-config-rule" }, { type: "custom", @@ -77,7 +76,7 @@ export default function HealthSpecEditor({ }, icon: "aws-config", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/aws-config" + docsLink: "canary-checker/reference/aws-config" }, { type: "custom", @@ -92,20 +91,6 @@ export default function HealthSpecEditor({ icon: "github", schemaFileName: "canary.spec.schema.json" }, - { - type: "custom", - name: "ec2", - label: "EC2", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "ec2", - schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/ec2" - }, { type: "custom", name: "ldap", @@ -118,22 +103,9 @@ export default function HealthSpecEditor({ }, icon: "ldap", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/ldap" - }, - { - type: "custom", - name: "pod", - label: "Pod", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "pod", - schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/pod" + docsLink: "canary-checker/reference/ldap" }, + { type: "form", name: "exec", @@ -148,7 +120,7 @@ export default function HealthSpecEditor({ configForm: ExecHealthFormEditor, specsMapField: "exec.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/exec" + docsLink: "canary-checker/reference/exec" }, { type: "form", @@ -164,7 +136,7 @@ export default function HealthSpecEditor({ configForm: AlertmanagerHealthFormEditor, specsMapField: "alertManager.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/alert-manager" + docsLink: "canary-checker/reference/alert-manager" }, { type: "custom", @@ -178,7 +150,7 @@ export default function HealthSpecEditor({ }, icon: "cloudwatch", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/cloudwatch" + docsLink: "canary-checker/reference/cloudwatch" }, { type: "form", @@ -194,7 +166,7 @@ export default function HealthSpecEditor({ configForm: ElasticsearchHealthFormEditor, specsMapField: "elasticsearch.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/elasticsearch" + docsLink: "canary-checker/reference/elasticsearch" }, { type: "form", @@ -210,7 +182,7 @@ export default function HealthSpecEditor({ configForm: RedisHealthFormEditor, specsMapField: "redis.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/redis" + docsLink: "canary-checker/reference/redis" }, { type: "form", @@ -226,7 +198,7 @@ export default function HealthSpecEditor({ configForm: MongoHealthFormEditor, specsMapField: "mongo.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/mongo" + docsLink: "canary-checker/reference/mongo" }, { type: "custom", @@ -240,7 +212,7 @@ export default function HealthSpecEditor({ }, icon: "dns", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/dns" + docsLink: "canary-checker/reference/dns" }, { type: "form", @@ -256,7 +228,7 @@ export default function HealthSpecEditor({ configForm: ICMPHealthFormEditor, specsMapField: "icmp.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/icmp" + docsLink: "canary-checker/reference/icmp" }, { type: "custom", @@ -270,7 +242,7 @@ export default function HealthSpecEditor({ }, icon: "gcp", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/gcs-bucket" + docsLink: "canary-checker/reference/gcs-bucket" }, { type: "custom", @@ -284,7 +256,7 @@ export default function HealthSpecEditor({ }, icon: "aws-s3-bucket", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/s3-bucket" + docsLink: "canary-checker/reference/s3-bucket" }, { type: "custom", @@ -298,7 +270,7 @@ export default function HealthSpecEditor({ }, icon: "smb", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/smb" + docsLink: "canary-checker/reference/smb" }, { type: "custom", @@ -312,7 +284,7 @@ export default function HealthSpecEditor({ }, icon: "sftp", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/sftp" + docsLink: "canary-checker/reference/sftp" }, { type: "form", @@ -328,7 +300,7 @@ export default function HealthSpecEditor({ configForm: FolderHealthFormEditor, specsMapField: "folder.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/folder" + docsLink: "canary-checker/reference/folder" }, { type: "custom", @@ -342,7 +314,7 @@ export default function HealthSpecEditor({ }, icon: "prometheus", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/prometheus" + docsLink: "canary-checker/reference/prometheus" }, { type: "custom", @@ -356,7 +328,22 @@ export default function HealthSpecEditor({ }, icon: "kubernetes", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/kubernetes" + docsLink: "canary-checker/reference/kubernetes" + }, + + { + type: "custom", + name: "kubernetesResource", + label: "Kubernetes Resource", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "kubernetes", + schemaFileName: "canary.spec.schema.json", + docsLink: "canary-checker/reference/kubernetesResource" }, { type: "custom", @@ -370,11 +357,11 @@ export default function HealthSpecEditor({ }, icon: "postgres", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/postgres" + docsLink: "canary-checker/reference/postgres" }, { type: "custom", - name: "configDB", + name: "catalog", label: "Mission Control Catalog", updateSpec: (value: Record) => { onSubmit(value); @@ -384,7 +371,7 @@ export default function HealthSpecEditor({ }, icon: "config", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/configdb" + docsLink: "canary-checker/reference/catalog" }, { type: "custom", @@ -398,7 +385,7 @@ export default function HealthSpecEditor({ }, icon: "azure-devops", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/azure-devops" + docsLink: "canary-checker/reference/azure-devops" }, { type: "custom", @@ -412,7 +399,7 @@ export default function HealthSpecEditor({ }, icon: "jmeter", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/jmeter" + docsLink: "canary-checker/reference/jmeter" }, { type: "custom", @@ -426,7 +413,7 @@ export default function HealthSpecEditor({ }, icon: "junit", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/junit" + docsLink: "canary-checker/reference/junit" }, { type: "custom", @@ -441,20 +428,6 @@ export default function HealthSpecEditor({ icon: "dynatrace", schemaFileName: "canary.spec.schema.json" }, - { - type: "custom", - name: "namespace", - label: "Kubernetes Namespace", - updateSpec: (value: Record) => { - onSubmit(value); - }, - loadSpec: () => { - return resourceValue ?? {}; - }, - icon: "namespace", - schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/kubernetes" - }, { type: "form", name: "tcp", @@ -469,7 +442,7 @@ export default function HealthSpecEditor({ configForm: TCPHealthFormEditor, specsMapField: "tcp.0", schemaFileName: "canary.spec.schema.json", - docsLink: "https://canarychecker.io/reference/tcp" + docsLink: "canary-checker/reference/tcp" }, { type: "custom", @@ -481,7 +454,7 @@ export default function HealthSpecEditor({ loadSpec: () => { return resourceValue ?? {}; }, - icon: FaCog, + icon: "cog", schemaFileName: "canary.spec.schema.json" } ] satisfies SpecType[] diff --git a/src/components/SpecEditor/ScraperTypes.ts b/src/components/SpecEditor/ScraperTypes.ts new file mode 100644 index 000000000..485c38627 --- /dev/null +++ b/src/components/SpecEditor/ScraperTypes.ts @@ -0,0 +1,202 @@ +import { FaCog } from "react-icons/fa"; +import AWSConfigsFormEditor from "../Forms/Configs/AWSConfigsFormEditor"; +import AzureConfigsFormEditor from "../Forms/Configs/AzureConfigsFormEditor"; +import AzureDevopsConfigsFormEditor from "../Forms/Configs/AzureDevopsConfigsFormEditor"; +import FileConfigsFormEditor from "../Forms/Configs/FileConfigsFormEditor"; +import GithubActionsConfigsFormEditor from "../Forms/Configs/GithubActionsConfigsFormEditor"; +import HttpConfigsFormEditor from "../Forms/Configs/HttpConfigsFormEditor"; +import KubernetesConfigsFormEditor from "../Forms/Configs/KubernetesConfigsFormEditor"; +import KubernetesFileConfigsFormEditor from "../Forms/Configs/KubernetesFileConfigsFormEditor"; +import SQLConfigsFormEditor from "../Forms/Configs/SQLConfigsFormEditor"; +import TrivyConfigsFormEditor from "../Forms/Configs/TrivyConfigsFormEditor"; +import { SpecType } from "./SpecEditor"; + +export default function scraperTypes( + onSubmit: (values: Record) => void, + resourceValue?: Record +) { + let types: SpecType[] = [ + { + type: "form", + name: "aws", + label: "AWS", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + // probably need to query the spec from the backend + return resourceValue ?? {}; + }, + icon: "aws", + configForm: AWSConfigsFormEditor, + specsMapField: "aws.0", + schemaFileName: "config_aws.schema.json", + docsLink: "config-db/scrapers/aws" + }, + { + type: "form", + name: "kubernetes", + label: "Kubernetes", + description: "Edit kubernetes configs", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + // probably need to query the spec from the backend + return resourceValue ?? {}; + }, + icon: "kubernetes", + configForm: KubernetesConfigsFormEditor, + specsMapField: "kubernetes.0", + schemaFileName: "config_kubernetes.schema.json", + docsLink: "config-db/scrapers/kubernetes" + }, + { + type: "form", + name: "azure", + label: "Azure", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "azure", + configForm: AzureConfigsFormEditor, + specsMapField: "azure.0", + schemaFileName: "config_azure.schema.json", + docsLink: "config-db/scrapers/azure" + }, + { + type: "form", + name: "kubernetesFile", + label: "File (Kubernetes Pod)", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "k8s-pod", + configForm: KubernetesFileConfigsFormEditor, + specsMapField: "kubernetesFile.0", + schemaFileName: "config_kubernetesfile.schema.json", + docsLink: "config-db/scrapers/kubernetes-file" + }, + { + type: "form", + name: "sql", + label: "SQL", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "sql", + configForm: SQLConfigsFormEditor, + specsMapField: "sql.0", + schemaFileName: "config_sql.schema.json", + docsLink: "config-db/scrapers/sql" + }, + { + type: "form", + name: "trivy", + label: "Trivy", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "trivy", + configForm: TrivyConfigsFormEditor, + specsMapField: "trivy.0", + schemaFileName: "config_trivy.schema.json", + docsLink: "config-db/scrapers/trivy" + }, + + { + type: "form", + name: "file", + label: "File", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "folder", + configForm: FileConfigsFormEditor, + specsMapField: "file.0", + schemaFileName: "config_file.schema.json", + docsLink: "config-db/scrapers/file" + }, + + { + type: "form", + name: "http", + label: "HTTP", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "http", + configForm: HttpConfigsFormEditor, + specsMapField: "http.0", + schemaFileName: undefined + }, + + { + type: "form", + name: "azureDevops", + label: "Azure DevOps", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "azure-devops", + configForm: AzureDevopsConfigsFormEditor, + specsMapField: "azureDevops.0", + schemaFileName: "config_azuredevops.schema.json", + docsLink: "config-db/scrapers/azure-devops" + }, + + { + type: "form", + name: "githubActions", + label: "Github Actions", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: "github", + configForm: GithubActionsConfigsFormEditor, + specsMapField: "githubActions.0", + schemaFileName: "config_githubactions.schema.json" + }, + { + name: "custom", + label: "Custom", + updateSpec: (value: Record) => { + onSubmit(value); + }, + loadSpec: () => { + return resourceValue ?? {}; + }, + icon: FaCog, + configForm: null, + specsMapField: "spec", + rawSpecInput: true, + schemaFileName: "scrape_config.schema.json" + } + ]; + types.sort((a, b) => a.label.localeCompare(b.label)); + return types; +} diff --git a/src/components/SpecEditor/SpecEditor.stories.tsx b/src/components/SpecEditor/SpecEditor.stories.tsx index 49257cbe4..1b7cc53dd 100644 --- a/src/components/SpecEditor/SpecEditor.stories.tsx +++ b/src/components/SpecEditor/SpecEditor.stories.tsx @@ -7,6 +7,7 @@ import SpecEditor, { SpecType } from "./SpecEditor"; const specTypes: SpecType[] = [ { type: "form", + label: "kubernetes", name: "kubernetes", updateSpec: (value: Record) => { console.log(value); @@ -19,6 +20,7 @@ const specTypes: SpecType[] = [ }, { type: "form", + label: "kubernetes", name: "aws", updateSpec: (value: Record) => { console.log(value); diff --git a/src/components/SpecEditor/SpecEditor.tsx b/src/components/SpecEditor/SpecEditor.tsx index 8436bb796..ea0e6a6e4 100644 --- a/src/components/SpecEditor/SpecEditor.tsx +++ b/src/components/SpecEditor/SpecEditor.tsx @@ -127,7 +127,7 @@ export default function SpecEditor({ key={type.name} > {typeof type.icon === "string" ? ( - + ) : ( )} From 8cc9968ecced2a7d739917f674ad37704a14b3a6 Mon Sep 17 00:00:00 2001 From: Moshe Immermam Date: Mon, 17 Jun 2024 23:22:18 +0300 Subject: [PATCH 2/5] chore: modal improvements --- package-lock.json | 26 +++++++++++++++++-- package.json | 2 +- .../InstallAgentModal.tsx | 7 +---- .../FeatureFlags/FeatureFlagForm.tsx | 2 +- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa02d36cc..ced135db8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,6 @@ "packages": { "": { "name": "@flanksource/flanksource-ui", - "version": "1.0.674", "dependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@clerk/nextjs": "^4.29.12", @@ -68,6 +67,7 @@ "qs": "^6.7.0", "react": "^18.2.0", "react-calendar": "^3.7.0", + "react-custom-events": "^1.1.1", "react-diff-view": "^3.0.3", "react-dom": "^18.2.0", "react-hook-form": "^7.15.0", @@ -83,6 +83,8 @@ "react-toastify": "^9.0.8", "react-tooltip": "^5.26.3", "react-top-loading-bar": "^2.3.1", + "react-use-intercom": "^5.1.4", + "react-use-size": "^3.0.3", "reactflow": "^11.11.3", "recharts": "2.1.12", "tailwindcss": "^3.4.1", @@ -34149,6 +34151,15 @@ "react-dom": ">=16.8.0" } }, + "node_modules/react-custom-events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/react-custom-events/-/react-custom-events-1.1.1.tgz", + "integrity": "sha512-71iEu3zHsBn3uvF+Sq4Fu5imtRt+cLZO6nG2zqUhdqGVIpZIfeLcl6yieqPghrE+18KFrS5BaHD0NBPP/EZJNw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -37025,6 +37036,17 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-use-size": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-use-size/-/react-use-size-3.0.3.tgz", + "integrity": "sha512-+UNmV3IycQKhEELnsIBQDFir2OCoB+0umeNZsm73BZjAXoHbWgnaG2LV201ogJDips+NdcaewzhJdVFM5iVRYQ==", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/reactflow": { "version": "11.11.3", "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.3.tgz", @@ -43795,4 +43817,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index e3fda69a9..7fcc343b0 100644 --- a/package.json +++ b/package.json @@ -213,4 +213,4 @@ "msw": { "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx b/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx index ec78f601f..6f0132b9f 100644 --- a/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx +++ b/src/components/Agents/InstalAgentInstruction/InstallAgentModal.tsx @@ -99,12 +99,7 @@ export default function InstallAgentModal({ const [activeTab, setActiveTab] = useState<"cli" | "flux">("cli"); return ( - +

Install the Mission Control agent using instructions below diff --git a/src/components/FeatureFlags/FeatureFlagForm.tsx b/src/components/FeatureFlags/FeatureFlagForm.tsx index b6de070f2..851223cce 100644 --- a/src/components/FeatureFlags/FeatureFlagForm.tsx +++ b/src/components/FeatureFlags/FeatureFlagForm.tsx @@ -29,7 +29,7 @@ export default function FeatureFlagForm({ return ( { setIsOpen(false); }} From 66f6ca351fb1eb4899526d702b5c7d8fbf84828b Mon Sep 17 00:00:00 2001 From: Moshe Immermam Date: Mon, 17 Jun 2024 23:48:50 +0300 Subject: [PATCH 3/5] chore: improve integration wizard chore: improve integration wizard --- .../Integrations/Add/AddIntegrationForm.tsx | 49 ++- .../Integrations/Add/AddIntegrationModal.tsx | 36 ++- .../Add/steps/AddIntegrationOptionsList.tsx | 298 +++++++++--------- .../steps/MissionControlRegistryOptions.tsx | 58 +++- .../FluxAddIntegrationCommand.tsx | 22 +- .../HelmCLIAddIntegrationCommand.tsx | 19 +- .../RegistryInstallationInstructions.tsx | 95 +----- .../CheckboxCollapsibleGroup.tsx | 6 +- src/ui/Modal/index.tsx | 168 ++++------ 9 files changed, 328 insertions(+), 423 deletions(-) diff --git a/src/components/Integrations/Add/AddIntegrationForm.tsx b/src/components/Integrations/Add/AddIntegrationForm.tsx index c226cacc6..f2b792188 100644 --- a/src/components/Integrations/Add/AddIntegrationForm.tsx +++ b/src/components/Integrations/Add/AddIntegrationForm.tsx @@ -1,12 +1,13 @@ import TopologyResourceForm from "@flanksource-ui/components/Integrations/Topology/TopologyResourceForm"; -import LogBackendsForm from "@flanksource-ui/components/Logs/LogBackends/LogBackendsForm"; -import { CreateIntegrationOption } from "./steps/AddIntegrationOptionsList"; +import { IntegrationOption } from "./steps/AddIntegrationOptionsList"; import CatalogFormOption from "./steps/CatalogFormOption"; import MissionControlRegistryOptions from "./steps/MissionControlRegistryOptions"; +import { SpecEditorProps } from "@flanksource-ui/components/SpecEditor/SpecEditor"; +import { useMemo } from "react"; -type Props = { +type Props = Pick & { onSuccess: () => void; - selectedOption: CreateIntegrationOption; + selectedOption: IntegrationOption; onBack: () => void; }; @@ -15,29 +16,21 @@ export default function AddIntegrationForm({ selectedOption, onBack }: Props) { - switch (selectedOption) { - case "AWS": - case "Azure": - case "Prometheus": - case "Flux": - case "Kubernetes": - case "Mission Control": - return ( - - ); - case "Custom Topology": - return ( - - ); - case "Log Backends": - return ; - case "Catalog Scraper": + + + return useMemo(() => { + if (selectedOption.category === "Scraper" || selectedOption.name === "Catalog Scraper") { + return ; - default: - return null; - } + } else if (selectedOption.name === "Custom Topology") { + return + } else { + return + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedOption]) } diff --git a/src/components/Integrations/Add/AddIntegrationModal.tsx b/src/components/Integrations/Add/AddIntegrationModal.tsx index c12deb193..b3dff6bfc 100644 --- a/src/components/Integrations/Add/AddIntegrationModal.tsx +++ b/src/components/Integrations/Add/AddIntegrationModal.tsx @@ -1,11 +1,10 @@ -import { Modal } from "@flanksource-ui/ui/Modal"; import { atom, useAtom } from "jotai"; import { useCallback, useEffect, useState } from "react"; import { AiFillPlusCircle } from "react-icons/ai"; import AddIntegrationForm from "./AddIntegrationForm"; -import AddIntegrationOptionsList, { - CreateIntegrationOption -} from "./steps/AddIntegrationOptionsList"; +import AddIntegrationOptionsList, { IntegrationOption } from "./steps/AddIntegrationOptionsList"; +import clsx from "clsx"; +import { Modal } from "@flanksource-ui/components"; type Props = { refresh: () => void; @@ -15,10 +14,8 @@ export const integrationsModalSubTitle = atom(undefined); export default function AddIntegrationModal({ refresh }: Props) { const [isOpen, setIsOpen] = useState(false); - const [selectedOption, setSelectedOption] = - useState(); + const [selectedOption, setSelectedOption] = useState(); const [subTitle, setSubTitle] = useAtom(integrationsModalSubTitle); - // use effect, to reset the selected option when the modal is closed useEffect(() => { if (isOpen) { @@ -26,10 +23,11 @@ export default function AddIntegrationModal({ refresh }: Props) { } }, [isOpen]); + const onSelectOption = useCallback( - (option: CreateIntegrationOption) => { + (option: IntegrationOption) => { setSelectedOption(option); - switch (option) { + switch (option.name) { case "Catalog Scraper": setSubTitle("Catalog Scraper"); break; @@ -40,7 +38,7 @@ export default function AddIntegrationModal({ refresh }: Props) { setSubTitle("Log Backends"); break; default: - setSubTitle(option); + setSubTitle(option.name); break; } }, @@ -58,8 +56,19 @@ export default function AddIntegrationModal({ refresh }: Props) { onClose={() => { setIsOpen(false); }} - bodyClass="flex flex-col flex-1 overflow-y-auto" - size="full" + onBack={() => + setSelectedOption(undefined) + } + onSave={(values) => { + console.log(values) + setIsOpen(false) + }} + childClassName={clsx( + selectedOption && { + "w-full": true, + "h-full": selectedOption.name !== "Catalog Scraper" && selectedOption.category !== "Scraper" && selectedOption.name !== "Custom Topology" + }, !selectedOption && "w-full")} + // bodyClass="flex flex-col flex-1 overflow-y-auto" > {selectedOption ? ( { - setSubTitle(undefined); setSelectedOption(undefined); }} /> ) : ( )} - + ); } diff --git a/src/components/Integrations/Add/steps/AddIntegrationOptionsList.tsx b/src/components/Integrations/Add/steps/AddIntegrationOptionsList.tsx index 394699dac..76839db24 100644 --- a/src/components/Integrations/Add/steps/AddIntegrationOptionsList.tsx +++ b/src/components/Integrations/Add/steps/AddIntegrationOptionsList.tsx @@ -1,135 +1,142 @@ import { useFeatureFlagsContext } from "@flanksource-ui/context/FeatureFlagsContext"; import { features } from "@flanksource-ui/services/permissions/features"; +import CardButton from "@flanksource-ui/ui/Buttons/CardButton"; import { Icon } from "@flanksource-ui/ui/Icons/Icon"; import { LogsIcon } from "@flanksource-ui/ui/Icons/LogsIcon"; import { SearchInListIcon } from "@flanksource-ui/ui/Icons/SearchInListIcon"; import { TopologyIcon } from "@flanksource-ui/ui/Icons/TopologyIcon"; import { useMemo } from "react"; -import { TupleToUnion } from "type-fest"; -export const createIntegrationOption = [ - "AWS", - "Azure", - "Prometheus", - "Flux", - "Kubernetes", - "Mission Control", - "Log Backends", - "Custom Topology", - "Catalog Scraper" -] as const; +const Kubernetes = + "Prerequisite: The Kubernetes bundle is required for this integration."; -export type CreateIntegrationOption = TupleToUnion< - typeof createIntegrationOption ->; - -type CreateIntegrationOptionMap = { - name: CreateIntegrationOption; - icon: React.ReactNode; - category: "Add Via GitOps" | "Add Directly"; +export type IntegrationOption = { + dependencies?: string[]; + name: string; + help?: string; + icon?: React.ReactNode; + directory?: boolean; + chart?: string; + schemaURL?: string; + category: "Add Via GitOps" | "Add Directly" | "Scraper" | "Topology"; }; -export const options = new Map< - CreateIntegrationOption, - CreateIntegrationOptionMap ->([ - [ - "AWS", - { - category: "Add Via GitOps", - icon: , - name: "AWS" - } - ], - [ - "Azure", - { - category: "Add Via GitOps", - icon: , - name: "Azure" - } - ], - [ - "Prometheus", - { - category: "Add Via GitOps", - icon: , - name: "Prometheus" - } - ], - [ - "Flux", - { - category: "Add Via GitOps", - icon: , - name: "Flux" - } - ], - [ - "Kubernetes", - { - category: "Add Via GitOps", - icon: , - name: "Kubernetes" - } - ], - [ - "Mission Control", - { - category: "Add Via GitOps", - icon: , - name: "Mission Control" - } - ], - [ - "Custom Topology", - { - category: "Add Directly", - icon: , - name: "Custom Topology" - } - ], - [ - "Catalog Scraper", - { - category: "Add Directly", - icon: , - name: "Catalog Scraper" - } - ], - [ - "Log Backends", - { - category: "Add Directly", - icon: , - name: "Log Backends" - } - ] -]); +var integrations = [ + { + category: "Add Via GitOps", + help: "registry/aws", + icon: , + name: "AWS", + chart: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/aws/Chart.yaml", + schemaURL: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/aws/values.schema.json" + }, + + { + category: "Add Via GitOps", + help: "registry/azure", + icon: , + name: "Azure", + chart: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/azure/Chart.yaml", + schemaURL: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/azure/values.schema.json" + }, + { + category: "Add Via GitOps", + help: "registry/kubernetes", + icon: ( + + ), + name: "Kubernetes", + chart: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/kubernetes/Chart.yaml", + schemaURL: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/kubernetes/values.schema.json" + }, + { + category: "Add Via GitOps", + help: "registry/prometheus", + icon: ( + + ), + name: "Prometheus", + chart: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/prometheus/Chart.yaml", + schemaURL: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/prometheus/values.schema.json" + }, + { + dependencies: [Kubernetes], + category: "Add Via GitOps", + icon: , + name: "Flux", + help: "registry/flux", + chart: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/flux/Chart.yaml", + + schemaURL: + "https://github.com/flanksource/mission-control-registry/raw/main/charts/flux/values.schema.json" + }, + + { + dependencies: [Kubernetes], + category: "Add Via GitOps", + help: "registry/argocd", + icon: , + name: "Argo", + chart: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/argocd/Chart.yaml", + + schemaURL: + "https://github.com/flanksource/mission-control-registry/raw/main/charts/argocd/values.schema.json" + }, + { + category: "Add Via GitOps", + help: "registry/mission-control", + icon: ( + + ), + name: "Mission Control", + chart: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/mission-control/Chart.yaml", + schemaURL: + "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/mission-control/values.schema.json" + }, + + { + category: "Add Directly", + help: "topology", + directory: true, + + icon: , + name: "Custom Topology" + }, + { + category: "Add Directly", + help: "config-db", + directory: true, + + icon: , + name: "Catalog Scraper" + }, -export const IntegrationsOptionToIconMap = new Map< - CreateIntegrationOption, - React.ReactNode ->([ - ["AWS", ], - ["Azure", ], - ["Prometheus", ], - ["Flux", ], - ["Kubernetes", ], - [ - "Mission Control", - - ], - ["Custom Topology", ], - [ - "Catalog Scraper", - - ], - ["Log Backends", ] -]); + { + category: "Add Directly", + directory: true, + help: "logging", + icon: , + name: "Log Backends" + } +] as IntegrationOption[]; type AddTopologyOptionsListProps = { - onSelectOption: (options: CreateIntegrationOption) => void; + onSelectOption: (options: IntegrationOption) => void; }; export default function AddIntegrationOptionsList({ @@ -147,24 +154,16 @@ export default function AddIntegrationOptionsList({
Add via GitOps
- {Array.from(options) - .filter(([key, value]) => value.category === "Add Via GitOps") - .map(([, item]) => { + {Array.from(integrations) + .filter((value) => value.category === "Add Via GitOps") + .map((item: IntegrationOption) => { return ( -
-
{ - onSelectOption(item.name); - }} - > -
- {IntegrationsOptionToIconMap.get(item.name)} -
-
{item.name}
-
-
+ onSelectOption(item)} + /> ); })}
@@ -172,28 +171,19 @@ export default function AddIntegrationOptionsList({
Add Directly
- {Array.from(options) - .filter(([key, value]) => value.category === "Add Directly") + {Array.from(integrations) + .filter((value) => value.category === "Add Directly") .filter( - ([key, value]) => - value.name !== "Log Backends" || !isLogsFeatureDisabled + (value) => value.name !== "Log Backends" || !isLogsFeatureDisabled ) - .map(([, item]) => { + .map((item) => { return ( -
-
{ - onSelectOption(item.name); - }} - > -
- {IntegrationsOptionToIconMap.get(item.name)} -
-
{item.name}
-
-
+ onSelectOption(item)} + /> ); })}
diff --git a/src/components/Integrations/Add/steps/MissionControlRegistryOptions.tsx b/src/components/Integrations/Add/steps/MissionControlRegistryOptions.tsx index ce29d4a31..4b7cb7ce2 100644 --- a/src/components/Integrations/Add/steps/MissionControlRegistryOptions.tsx +++ b/src/components/Integrations/Add/steps/MissionControlRegistryOptions.tsx @@ -1,12 +1,12 @@ import { FormikCodeEditor } from "@flanksource-ui/components/Forms/Formik/FormikCodeEditor"; -import { Button } from "@flanksource-ui/ui/Buttons/Button"; -import clsx from "clsx"; import { Form, Formik } from "formik"; import FormikTextInput from "../../../Forms/Formik/FormikTextInput"; -import { CreateIntegrationOption } from "./AddIntegrationOptionsList"; -import RegistryInstallationInstructions, { - selectedOptionChartsDetails -} from "./RegistryInstallationGuide/RegistryInstallationInstructions"; +import { IntegrationOption } from "./AddIntegrationOptionsList"; +import RegistryInstallationInstructions from "./RegistryInstallationGuide/RegistryInstallationInstructions"; +import Admonition from "@flanksource-ui/ui/BannerMessage/Admonition"; +import FormikCheckbox from "@flanksource-ui/components/Forms/Formik/FormikCheckbox"; +import { useWindowSize } from "react-use-size"; +import { Button } from "@flanksource-ui/ui/Buttons/Button"; export type TopologyResource = { id: string; @@ -24,7 +24,7 @@ type TopologyResourceFormProps = { * the spec field is populated with the current topology's spec and this * prop is ignored. */ - selectedOption?: CreateIntegrationOption; + selectedOption: IntegrationOption; onBack?: () => void; footerClassName?: string; onSuccess?: () => void; @@ -36,15 +36,22 @@ export default function MissionControlRegistryOptions({ footerClassName = "bg-gray-100 p-4", onSuccess = () => {} }: TopologyResourceFormProps) { - const chartDetails = selectedOptionChartsDetails.get(selectedOption!); + const { height } = useWindowSize(); + var lines = 6; + if (height && height > 1024) { + lines = 8; + } return ( -
+
(
-
+
+ {selectedOption.dependencies && + selectedOption.dependencies.length > 0 && ( + + )} + + +
+
diff --git a/src/components/Integrations/Add/steps/RegistryInstallationGuide/FluxAddIntegrationCommand.tsx b/src/components/Integrations/Add/steps/RegistryInstallationGuide/FluxAddIntegrationCommand.tsx index 29d86493f..74ed9d2de 100644 --- a/src/components/Integrations/Add/steps/RegistryInstallationGuide/FluxAddIntegrationCommand.tsx +++ b/src/components/Integrations/Add/steps/RegistryInstallationGuide/FluxAddIntegrationCommand.tsx @@ -4,20 +4,21 @@ import { useMemo } from "react"; import { stringify } from "yaml"; import { HelmChartValues } from "./RegistryInstallationInstructions"; -const fluxInstallationTemplate = `apiVersion: v1 +const fluxInstallationTemplate = ` +{{#if createNamespace}}apiVersion: v1 kind: Namespace metadata: name: {{ namespace }} ---- -apiVersion: source.toolkit.fluxcd.io/v1beta1 +---{{/if}} +{{#if createRepository}}apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: HelmRepository metadata: name: flanksource - namespace: mission-control-agent + namespace: {{ namespace }} spec: interval: 5m0s url: https://flanksource.github.io/charts ---- +---{{/if}} {{ chartContent }} `; @@ -39,7 +40,7 @@ export default function FluxAddIntegrationCommand({ apiVersion: "helm.toolkit.fluxcd.io/v2beta1", kind: "HelmRelease", metadata: { - name: "mission-control", + name: "mission-control-" + formValues.name?.toLowerCase(), namespace: formValues.namespace }, spec: { @@ -48,8 +49,7 @@ export default function FluxAddIntegrationCommand({ chart: helmChartValues.name, sourceRef: { kind: "HelmRepository", - name: "flanksource", - namespace: "mission-control" + name: "flanksource" }, interval: "1m" }, @@ -66,11 +66,13 @@ export default function FluxAddIntegrationCommand({ const fluxYaml = template({ namespace: formValues.namespace, + createNamespace: formValues.createNamespace, + createRepository: formValues.createRepository, chartContent - }); + }).trim(); return ( -
+
); diff --git a/src/components/Integrations/Add/steps/RegistryInstallationGuide/HelmCLIAddIntegrationCommand.tsx b/src/components/Integrations/Add/steps/RegistryInstallationGuide/HelmCLIAddIntegrationCommand.tsx index c4def9ebe..470e15aa3 100644 --- a/src/components/Integrations/Add/steps/RegistryInstallationGuide/HelmCLIAddIntegrationCommand.tsx +++ b/src/components/Integrations/Add/steps/RegistryInstallationGuide/HelmCLIAddIntegrationCommand.tsx @@ -25,15 +25,16 @@ export function yamlObjectToCliFlags( return flags.flat(); } -const helmInstallationCommandTemplate = `helm repo add flanksource https://flanksource.github.io/charts +const helmInstallationCommandTemplate = ` +{{#if createRepository }}helm repo add flanksource https://flanksource.github.io/charts +helm repo update{{/if}} -helm repo update - -helm install flanksource/{{ chartName }} -n "{{namespace}}" \\ +helm install mission-control-{{name}} flanksource/{{ chartName }} \\ + -n "{{namespace}}" \\ {{#each flags}} --set {{key}}="{{value}}" \\ {{/each}} - --create-namespace +{{#if createNamespace}} --create-namespace{{/if}} `; const template = Handlebars.compile(helmInstallationCommandTemplate); @@ -49,16 +50,16 @@ export default function HelmCLIAddIntegrationCommand({ }: Props) { const flags = yamlObjectToCliFlags(formValues.chartValues); - console.log(chartValues); - const helmInstallationCommand = template({ - name: formValues.name, + name: formValues.name.toLowerCase(), + createNamespace: formValues.createNamespace, + createRepository: formValues.createRepository, namespace: formValues.namespace, interval: formValues.interval, chartName: chartValues.name, version: chartValues?.version, flags - }); + }).trim(); return ( - - You will need to have Kubernetes chart installed in flux to use this - option. - -
- ); -} - -export const selectedOptionChartsDetails = new Map< - CreateIntegrationOption, - { - chart: string; - schemaURL?: string; - } ->([ - [ - "Flux", - { - chart: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/flux/Chart.yaml", - - schemaURL: - "https://github.com/flanksource/mission-control-registry/raw/main/charts/flux/values.schema.json" - } - ], - [ - "Kubernetes", - { - chart: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/kubernetes/Chart.yaml", - schemaURL: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/kubernetes/values.schema.json" - } - ], - [ - "Prometheus", - { - chart: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/prometheus/Chart.yaml", - schemaURL: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/prometheus/values.schema.json" - } - ], - [ - "Mission Control", - { - chart: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/mission-control/Chart.yaml", - schemaURL: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/mission-control/values.schema.json" - } - ], - [ - "AWS", - { - chart: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/aws/Chart.yaml", - schemaURL: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/aws/values.schema.json" - } - ], - [ - "Azure", - { - chart: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/azure/Chart.yaml", - schemaURL: - "https://raw.githubusercontent.com/flanksource/mission-control-registry/main/charts/azure/values.schema.json" - } - ] -]); - export type HelmChartValues = { apiVersion: string; name: string; @@ -95,7 +17,7 @@ export type HelmChartValues = { }; type Props = { - selectedOption: CreateIntegrationOption; + selectedOption: IntegrationOption; formValues: Record; }; @@ -110,15 +32,18 @@ export default function RegistryInstallationInstructions({ const { data: helmChartValues, isLoading: isLoadingSpec } = useQuery({ queryKey: ["Github", "mission-control-registry", "values", selectedOption], queryFn: async () => { - const opt = selectedOptionChartsDetails.get(selectedOption!); - const response = await fetch(opt?.chart!); + if (!selectedOption.chart) { + return {} as HelmChartValues; + } + + const response = await fetch(selectedOption.chart); const data = await response.text(); return YAML.parse(data) as HelmChartValues; }, enabled: !!selectedOption && - selectedOption !== "Custom Topology" && - selectedOption !== "Catalog Scraper" + selectedOption.name !== "Custom Topology" && + selectedOption.name !== "Catalog Scraper" }); if (isLoadingSpec || !helmChartValues) { @@ -144,7 +69,6 @@ export default function RegistryInstallationInstructions({ formValues={formValues} helmChartValues={helmChartValues} /> - -
diff --git a/src/ui/CheckboxCollapsibleGroup/CheckboxCollapsibleGroup.tsx b/src/ui/CheckboxCollapsibleGroup/CheckboxCollapsibleGroup.tsx index 09ead6605..fbf6e6dea 100644 --- a/src/ui/CheckboxCollapsibleGroup/CheckboxCollapsibleGroup.tsx +++ b/src/ui/CheckboxCollapsibleGroup/CheckboxCollapsibleGroup.tsx @@ -12,7 +12,7 @@ export default function CheckboxCollapsibleGroup({ label, children, isChecked = false, - className = "flex-1 flex flex-col px-2 py-2 transform origin-top duration-1000 overflow-y-auto", + className = "flex-1 flex flex-col transform origin-top duration-1000 overflow-y-auto border-l-gray-200 border-l-2 pl-2 py-1 border-dotted ", labelClassName = "flex-1 font-semibold", onChange = () => {}, ...props @@ -22,10 +22,10 @@ export default function CheckboxCollapsibleGroup({ return (
void; +}; + +export function useModal() { + return useContext(ModalContext); +} + +export const ModalContext = createContext(undefined!); /** * @@ -16,9 +33,9 @@ import DialogButton from "../Buttons/DialogButton"; * */ export const modalHelpLinkAtom = atom(undefined); -export type ModalSize = "very-small" | "small" | "medium" | "large" | "full"; -const HEADER_HEIGHT = 60; +export type ModalSize = "small" | "medium" | "large" | "full"; + export interface IModalProps { title?: React.ReactNode; showExpand?: boolean; @@ -38,82 +55,39 @@ export interface IModalProps { dialogClassName?: string; } -export function useDialogSize(size?: string): { - windowHeight: number; - windowWidth: number; - height: string; - width: string; - classNames: string; -} { +export function useDialogSize(size?: string) { const { height, width } = useWindowSize(); - let ret = { - classNames: "", - windowHeight: height, - windowWidth: width, - height: "", - width: "" - }; - - if (size === "very-small" || size === "small") { - ret.height = " h-full"; - } else { - ret.classNames += " mx-auto my-auto"; - } - - if (size === "small") { - ret.classNames += " max-w-[800px] max-h-[50vh]"; - } - - if (size === "full") { - if (width >= 1600) { - ret.width = " w-[1500px]"; - } else if (width >= 1280) { - ret.width = " w-[1200px]"; - } else if (width >= 1024) { - ret.width = " w-[1000px]"; - } else if (width >= 768) { - ret.width = " w-[90vw]"; - } else if (width < 768) { - ret.width = " w-[95vw]"; - } - ret.height = " h-[90vh]"; - } - - if (size === "medium" || size === undefined) { - if (width >= 1280) { - ret.width = " w-[1000px]"; - } else if (width >= 1024) { - ret.width = " w-[850px]"; - } else if (width >= 768) { - ret.width = " w-[500vw]"; - } else if (width < 768) { - ret.width = " w-[95vw]"; - } - if (height < 1000) { - ret.height = ` max-h-[${height - HEADER_HEIGHT * 2}px]`; - } else if (height > 1000) { - ret.height = " max-h-[80vh]"; - } - } - - if (size === "large") { - if (width >= 1280) { - ret.width = " w-[1240px]"; - } else if (width >= 1024) { - ret.width = " w-[1000px]"; - } else if (width >= 768) { - ret.width = " w-[600px]"; - } else if (width < 768) { - ret.width = " w-[95vw]"; - } - if (height < 1000) { - ret.height = ` max-h-[${height - HEADER_HEIGHT * 2}px]`; - } else if (height > 1000) { - ret.height = " max-h-[90vh]"; - } - } - - return ret; + return useMemo( + () => + clsx( + size === "full" && { + "min-w-[1240px] w-[1240px]": width >= 1280, + "w-[1000px]": width < 1280 && width >= 1024, + "w-[90vw]": width < 1024 && width >= 768, + "w-[95vw]": width < 768, + "h-[90vh]": height < 1000, + "h-[1000px]": height > 1000 + }, + size === "small" && "max-w-[800px] max-h-[50vh]", + size === "medium" && { + "w-[1000px]": width >= 1280, + "w-[850px]": width < 1280 && width >= 1024, + "w-[500vw]": width < 1024 && width >= 768, + "w-[95vw]": width < 768, + "h-[70vh]": height < 1000, + "h-[800px]": height > 1000 + }, + size === "large" && { + "min-w-[1240px] w-[1240px]": width >= 1280, + "w-[1000px]": width < 1280 && width >= 1024, + "w-[600px]": width < 1024 && width >= 768, + "w-[95vw]": width < 768, + "h-[80vh]": height < 1000, + "h-[1000px]": height > 1000 + } + ), + [height, width, size] + ); } export function Modal({ @@ -125,10 +99,10 @@ export function Modal({ actions, open, onClose = () => {}, - childClassName = "w-full", + childClassName = "w-full h-full", allowBackgroundClose = true, hideCloseButton, - size = "medium", + size, children, containerClassName = "overflow-auto max-h-full", dialogClassName = "fixed z-50 inset-0 overflow-y-auto min-h-2xl:my-20 py-4", @@ -137,10 +111,8 @@ export function Modal({ const [helpLink] = useAtom(modalHelpLinkAtom); const [_size, setSize] = useState(size); const sizeClass = useDialogSize(_size); - const isSmall = _size === "very-small" || _size === "small"; return ( - /* @ts-ignore */
{/* @ts-ignore */} @@ -180,11 +151,8 @@ export function Modal({ >
@@ -201,35 +169,27 @@ export function Modal({ corner of the modal that links to the documentation for the modal. */} {helpLink && } - {showExpand && _size !== "full" && !isSmall && ( + {showExpand && _size !== "full" && size !== "small" && ( { - setSize("full"); - }} + onClick={() => setSize("full")} /> )} {showExpand && _size === "full" && ( setSize(size)} + onClick={() => setSize("medium")} /> )} {/* top-right close button */} {!hideCloseButton && ( - + )}
-
+
{children}
From 12142f0f04e4a78e9722d8437ae4059a98b28d1d Mon Sep 17 00:00:00 2001 From: Moshe Immermam Date: Tue, 18 Jun 2024 00:12:54 +0300 Subject: [PATCH 4/5] chore: fix build errors --- .../Integrations/Add/AddIntegrationModal.tsx | 25 +++++++++---------- src/components/SpecEditor/ScraperTypes.ts | 5 ++-- src/components/SpecEditor/SpecEditor.tsx | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/components/Integrations/Add/AddIntegrationModal.tsx b/src/components/Integrations/Add/AddIntegrationModal.tsx index b3dff6bfc..59c185a62 100644 --- a/src/components/Integrations/Add/AddIntegrationModal.tsx +++ b/src/components/Integrations/Add/AddIntegrationModal.tsx @@ -2,7 +2,9 @@ import { atom, useAtom } from "jotai"; import { useCallback, useEffect, useState } from "react"; import { AiFillPlusCircle } from "react-icons/ai"; import AddIntegrationForm from "./AddIntegrationForm"; -import AddIntegrationOptionsList, { IntegrationOption } from "./steps/AddIntegrationOptionsList"; +import AddIntegrationOptionsList, { + IntegrationOption +} from "./steps/AddIntegrationOptionsList"; import clsx from "clsx"; import { Modal } from "@flanksource-ui/components"; @@ -23,7 +25,6 @@ export default function AddIntegrationModal({ refresh }: Props) { } }, [isOpen]); - const onSelectOption = useCallback( (option: IntegrationOption) => { setSelectedOption(option); @@ -56,19 +57,17 @@ export default function AddIntegrationModal({ refresh }: Props) { onClose={() => { setIsOpen(false); }} - onBack={() => - setSelectedOption(undefined) - } - onSave={(values) => { - console.log(values) - setIsOpen(false) - }} childClassName={clsx( selectedOption && { "w-full": true, - "h-full": selectedOption.name !== "Catalog Scraper" && selectedOption.category !== "Scraper" && selectedOption.name !== "Custom Topology" - }, !selectedOption && "w-full")} - // bodyClass="flex flex-col flex-1 overflow-y-auto" + "h-full": + selectedOption.name !== "Catalog Scraper" && + selectedOption.category !== "Scraper" && + selectedOption.name !== "Custom Topology" + }, + !selectedOption && "w-full" + )} + // bodyClass="flex flex-col flex-1 overflow-y-auto" > {selectedOption ? ( )} - + ); } diff --git a/src/components/SpecEditor/ScraperTypes.ts b/src/components/SpecEditor/ScraperTypes.ts index 485c38627..edc28f7e7 100644 --- a/src/components/SpecEditor/ScraperTypes.ts +++ b/src/components/SpecEditor/ScraperTypes.ts @@ -183,6 +183,7 @@ export default function scraperTypes( }, { name: "custom", + type: "code", label: "Custom", updateSpec: (value: Record) => { onSubmit(value); @@ -191,12 +192,10 @@ export default function scraperTypes( return resourceValue ?? {}; }, icon: FaCog, - configForm: null, specsMapField: "spec", - rawSpecInput: true, schemaFileName: "scrape_config.schema.json" } ]; - types.sort((a, b) => a.label.localeCompare(b.label)); + types.sort((a, b) => a?.label?.localeCompare(b?.label || "") || 0); return types; } diff --git a/src/components/SpecEditor/SpecEditor.tsx b/src/components/SpecEditor/SpecEditor.tsx index ea0e6a6e4..2fca942cc 100644 --- a/src/components/SpecEditor/SpecEditor.tsx +++ b/src/components/SpecEditor/SpecEditor.tsx @@ -49,7 +49,7 @@ export type SpecTypeCustom = SpecTypeCommonFields & { export type SpecType = SpecTypeInputForm | SpecTypeCode | SpecTypeCustom; -type SpecEditorProps = { +export type SpecEditorProps = { types: SpecType[]; format?: "json" | "yaml"; resourceInfo: Pick; From 127dba33e491f71bcea677ed314f2920461f1f21 Mon Sep 17 00:00:00 2001 From: Moshe Immermam Date: Tue, 18 Jun 2024 13:53:56 +0300 Subject: [PATCH 5/5] wip: formik modal chore: improve modal fix: fix type issues --- src/components/Forms/SpecEditorForm.tsx | 324 +++++++++--------- .../Add/steps/CatalogFormOption.tsx | 8 +- .../SpecEditor/ConfigScrapperSpecEditor.tsx | 34 +- .../SpecEditor/HealthSpecEditor.tsx | 52 +-- src/components/SpecEditor/ScraperTypes.ts | 17 +- src/components/SpecEditor/SpecEditor.tsx | 34 +- src/ui/Modal/FormikModal.stories.tsx | 66 ++++ src/ui/Modal/FormikModal.tsx | 278 +++++++++++++++ src/ui/Modal/index.tsx | 4 +- 9 files changed, 583 insertions(+), 234 deletions(-) create mode 100644 src/ui/Modal/FormikModal.stories.tsx create mode 100644 src/ui/Modal/FormikModal.tsx diff --git a/src/components/Forms/SpecEditorForm.tsx b/src/components/Forms/SpecEditorForm.tsx index b07e381bb..f0220bef4 100644 --- a/src/components/Forms/SpecEditorForm.tsx +++ b/src/components/Forms/SpecEditorForm.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { Form, Formik } from "formik"; +import { Form, Formik, useFormikContext } from "formik"; import { useCallback, useMemo, useRef, useState } from "react"; import { Button } from "../../ui/Buttons/Button"; import { Tab, Tabs } from "../../ui/Tabs/Tabs"; @@ -18,6 +18,7 @@ import FormikTextInput from "./Formik/FormikTextInput"; type SpecEditorFormProps = { loadSpec: () => Record; + footer: boolean; updateSpec: (spec: Record) => void; onBack: () => void; specFormat: "yaml" | "json"; @@ -26,14 +27,17 @@ type SpecEditorFormProps = { onDeleted: () => void; }; + + export default function SpecEditorForm({ resourceInfo, loadSpec = () => ({}), - updateSpec = () => {}, - onBack = () => {}, + updateSpec = () => { }, + onBack = () => { }, specFormat = "yaml", + footer = true, selectedSpec, - onDeleted = () => {} + onDeleted = () => { } }: SpecEditorFormProps) { const formRef = useRef(null); const [activeTabs, setActiveTabs] = useState<"Form" | "Code">( @@ -90,164 +94,170 @@ export default function SpecEditorForm({ }; return ( - { - updateSpec(values); - }} - validateOnBlur - validateOnChange - > - {({ handleSubmit, handleReset, setFieldTouched }) => ( - { - handleSubmit(e); - touchAllFormFields(setFieldTouched); - }} - onReset={handleReset} - className="flex flex-col flex-1 overflow-y-auto space-y-4" - ref={formRef} - > -
-
- {isFieldSupportedByResourceType("name") && ( - - )} - {isFieldSupportedByResourceType("icon") && ( - - )} - {isFieldSupportedByResourceType("labels") && ( - - )} - {isFieldSupportedByResourceType("source") && ( - { + // console.log('123') + // updateSpec(values); + // }} + // validateOnBlur + // validateOnChange + // > + // {({ handleSubmit, handleReset, setFieldTouched }) => ( + // { + // console.log('456') + // handleSubmit(e); + // touchAllFormFields(setFieldTouched); + // }} + // onReset={handleReset} + // className="flex flex-col flex-1 overflow-y-auto space-y-4" + // ref={formRef} + // > + <> +
+
+ {isFieldSupportedByResourceType("name") && ( + + )} + {isFieldSupportedByResourceType("icon") && ( + + )} + {isFieldSupportedByResourceType("labels") && ( + + )} + {isFieldSupportedByResourceType("source") && ( +
+
+ {selectedSpec.type !== "form" ? ( + <> + + + + ) : ( + setActiveTabs(v as "Code" | "Form")} + > + +
+ +
+
+ + + +
+ )} +
+
+ {footer && +
+
+ + {!initialValues.id && ( +
+
)} - {isFieldSupportedByResourceType("schedule") && ( - )} -
-
- {selectedSpec.type !== "form" ? ( - <> - - - - ) : ( - setActiveTabs(v as "Code" | "Form")} - > - -
- -
-
- - - -
- )} -
-
-
-
- - {!initialValues.id && ( -
-
- )} - {!!initialValues.id && ( - - )} -
+
- - )} - +
+ } + + // + // )} + // ); } diff --git a/src/components/Integrations/Add/steps/CatalogFormOption.tsx b/src/components/Integrations/Add/steps/CatalogFormOption.tsx index fc991a689..123f931b4 100644 --- a/src/components/Integrations/Add/steps/CatalogFormOption.tsx +++ b/src/components/Integrations/Add/steps/CatalogFormOption.tsx @@ -1,13 +1,13 @@ import { useSettingsCreateResource } from "@flanksource-ui/api/query-hooks/mutations/useSettingsResourcesMutations"; import { schemaResourceTypes } from "@flanksource-ui/components/SchemaResourcePage/resourceTypes"; import ConfigScrapperSpecEditor from "@flanksource-ui/components/SpecEditor/ConfigScrapperSpecEditor"; +import { SpecEditorProps } from "@flanksource-ui/components/SpecEditor/SpecEditor"; -type Props = { +type Props = Pick & { onSuccess: () => void; - onBack: () => void; }; -export default function CatalogFormOption({ onSuccess, onBack }: Props) { +export default function CatalogFormOption({ onSuccess, ...props }: Props) { const resourceInfo = schemaResourceTypes.find( (resource) => resource.name === "Catalog Scraper" ); @@ -19,7 +19,7 @@ export default function CatalogFormOption({ onSuccess, onBack }: Props) { return (
- +
); } diff --git a/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx b/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx index b1fa87e0a..971ffbd75 100644 --- a/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx +++ b/src/components/SpecEditor/ConfigScrapperSpecEditor.tsx @@ -1,18 +1,7 @@ import { useMemo } from "react"; -import { FaCog } from "react-icons/fa"; -import AWSConfigsFormEditor from "../Forms/Configs/AWSConfigsFormEditor"; -import AzureConfigsFormEditor from "../Forms/Configs/AzureConfigsFormEditor"; -import AzureDevopsConfigsFormEditor from "../Forms/Configs/AzureDevopsConfigsFormEditor"; -import FileConfigsFormEditor from "../Forms/Configs/FileConfigsFormEditor"; -import GithubActionsConfigsFormEditor from "../Forms/Configs/GithubActionsConfigsFormEditor"; -import HttpConfigsFormEditor from "../Forms/Configs/HttpConfigsFormEditor"; -import KubernetesConfigsFormEditor from "../Forms/Configs/KubernetesConfigsFormEditor"; -import KubernetesFileConfigsFormEditor from "../Forms/Configs/KubernetesFileConfigsFormEditor"; -import SQLConfigsFormEditor from "../Forms/Configs/SQLConfigsFormEditor"; -import TrivyConfigsFormEditor from "../Forms/Configs/TrivyConfigsFormEditor"; import { SchemaResourceType } from "../SchemaResourcePage/resourceTypes"; -import SpecEditor from "./SpecEditor"; import scraperTypes from "./ScraperTypes"; +import SpecEditor, { SpecEditorProps } from "./SpecEditor"; const resourceInfo: Pick = { name: "Catalog Scraper", @@ -20,20 +9,25 @@ const resourceInfo: Pick = { table: "config_scrapers" }; -type ConfigScrapperSpecEditorProps = { +type ConfigScrapperSpecEditorProps = Omit< + SpecEditorProps, + "types" | "resourceInfo" +> & { + footer?: boolean; resourceValue?: Record; onSubmit?: (spec: Record) => void; - onBack?: () => void; - onDeleted?: () => void; }; export default function ConfigScrapperSpecEditor({ resourceValue, + footer = true, onSubmit = () => {}, - onBack, - onDeleted = () => {} + ...props }: ConfigScrapperSpecEditorProps) { - const configTypes = scraperTypes(onSubmit, resourceValue); + const configTypes = useMemo( + () => scraperTypes(onSubmit, resourceValue), + [onSubmit, resourceValue] + ); // there should only be one spec, so we can just grab the first key that isn't // schedule, otherwise we'll just use custom @@ -46,11 +40,11 @@ export default function ConfigScrapperSpecEditor({ return ( ); } diff --git a/src/components/SpecEditor/HealthSpecEditor.tsx b/src/components/SpecEditor/HealthSpecEditor.tsx index 106a5b0c2..6405452ff 100644 --- a/src/components/SpecEditor/HealthSpecEditor.tsx +++ b/src/components/SpecEditor/HealthSpecEditor.tsx @@ -48,7 +48,7 @@ export default function HealthSpecEditor({ configForm: HTTPHealthFormEditor, specsMapField: "http.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/http" + help: "canary-checker/reference/http" }, { type: "custom", @@ -62,7 +62,7 @@ export default function HealthSpecEditor({ }, icon: "aws", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/aws-config-rule" + help: "canary-checker/reference/aws-config-rule" }, { type: "custom", @@ -76,7 +76,7 @@ export default function HealthSpecEditor({ }, icon: "aws-config", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/aws-config" + help: "canary-checker/reference/aws-config" }, { type: "custom", @@ -103,7 +103,7 @@ export default function HealthSpecEditor({ }, icon: "ldap", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/ldap" + help: "canary-checker/reference/ldap" }, { @@ -120,7 +120,7 @@ export default function HealthSpecEditor({ configForm: ExecHealthFormEditor, specsMapField: "exec.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/exec" + help: "canary-checker/reference/exec" }, { type: "form", @@ -136,7 +136,7 @@ export default function HealthSpecEditor({ configForm: AlertmanagerHealthFormEditor, specsMapField: "alertManager.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/alert-manager" + help: "canary-checker/reference/alert-manager" }, { type: "custom", @@ -150,7 +150,7 @@ export default function HealthSpecEditor({ }, icon: "cloudwatch", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/cloudwatch" + help: "canary-checker/reference/cloudwatch" }, { type: "form", @@ -166,7 +166,7 @@ export default function HealthSpecEditor({ configForm: ElasticsearchHealthFormEditor, specsMapField: "elasticsearch.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/elasticsearch" + help: "canary-checker/reference/elasticsearch" }, { type: "form", @@ -182,7 +182,7 @@ export default function HealthSpecEditor({ configForm: RedisHealthFormEditor, specsMapField: "redis.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/redis" + help: "canary-checker/reference/redis" }, { type: "form", @@ -198,7 +198,7 @@ export default function HealthSpecEditor({ configForm: MongoHealthFormEditor, specsMapField: "mongo.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/mongo" + help: "canary-checker/reference/mongo" }, { type: "custom", @@ -212,7 +212,7 @@ export default function HealthSpecEditor({ }, icon: "dns", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/dns" + help: "canary-checker/reference/dns" }, { type: "form", @@ -228,7 +228,7 @@ export default function HealthSpecEditor({ configForm: ICMPHealthFormEditor, specsMapField: "icmp.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/icmp" + help: "canary-checker/reference/icmp" }, { type: "custom", @@ -242,7 +242,7 @@ export default function HealthSpecEditor({ }, icon: "gcp", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/gcs-bucket" + help: "canary-checker/reference/gcs-bucket" }, { type: "custom", @@ -256,7 +256,7 @@ export default function HealthSpecEditor({ }, icon: "aws-s3-bucket", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/s3-bucket" + help: "canary-checker/reference/s3-bucket" }, { type: "custom", @@ -270,7 +270,7 @@ export default function HealthSpecEditor({ }, icon: "smb", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/smb" + help: "canary-checker/reference/smb" }, { type: "custom", @@ -284,7 +284,7 @@ export default function HealthSpecEditor({ }, icon: "sftp", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/sftp" + help: "canary-checker/reference/sftp" }, { type: "form", @@ -300,7 +300,7 @@ export default function HealthSpecEditor({ configForm: FolderHealthFormEditor, specsMapField: "folder.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/folder" + help: "canary-checker/reference/folder" }, { type: "custom", @@ -314,7 +314,7 @@ export default function HealthSpecEditor({ }, icon: "prometheus", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/prometheus" + help: "canary-checker/reference/prometheus" }, { type: "custom", @@ -328,7 +328,7 @@ export default function HealthSpecEditor({ }, icon: "kubernetes", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/kubernetes" + help: "canary-checker/reference/kubernetes" }, { @@ -343,7 +343,7 @@ export default function HealthSpecEditor({ }, icon: "kubernetes", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/kubernetesResource" + help: "canary-checker/reference/kubernetesResource" }, { type: "custom", @@ -357,7 +357,7 @@ export default function HealthSpecEditor({ }, icon: "postgres", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/postgres" + help: "canary-checker/reference/postgres" }, { type: "custom", @@ -371,7 +371,7 @@ export default function HealthSpecEditor({ }, icon: "config", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/catalog" + help: "canary-checker/reference/catalog" }, { type: "custom", @@ -385,7 +385,7 @@ export default function HealthSpecEditor({ }, icon: "azure-devops", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/azure-devops" + help: "canary-checker/reference/azure-devops" }, { type: "custom", @@ -399,7 +399,7 @@ export default function HealthSpecEditor({ }, icon: "jmeter", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/jmeter" + help: "canary-checker/reference/jmeter" }, { type: "custom", @@ -413,7 +413,7 @@ export default function HealthSpecEditor({ }, icon: "junit", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/junit" + help: "canary-checker/reference/junit" }, { type: "custom", @@ -442,7 +442,7 @@ export default function HealthSpecEditor({ configForm: TCPHealthFormEditor, specsMapField: "tcp.0", schemaFileName: "canary.spec.schema.json", - docsLink: "canary-checker/reference/tcp" + help: "canary-checker/reference/tcp" }, { type: "custom", diff --git a/src/components/SpecEditor/ScraperTypes.ts b/src/components/SpecEditor/ScraperTypes.ts index edc28f7e7..da842b4ff 100644 --- a/src/components/SpecEditor/ScraperTypes.ts +++ b/src/components/SpecEditor/ScraperTypes.ts @@ -31,7 +31,7 @@ export default function scraperTypes( configForm: AWSConfigsFormEditor, specsMapField: "aws.0", schemaFileName: "config_aws.schema.json", - docsLink: "config-db/scrapers/aws" + help: "config-db/scrapers/aws" }, { type: "form", @@ -49,7 +49,7 @@ export default function scraperTypes( configForm: KubernetesConfigsFormEditor, specsMapField: "kubernetes.0", schemaFileName: "config_kubernetes.schema.json", - docsLink: "config-db/scrapers/kubernetes" + help: "config-db/scrapers/kubernetes" }, { type: "form", @@ -65,7 +65,7 @@ export default function scraperTypes( configForm: AzureConfigsFormEditor, specsMapField: "azure.0", schemaFileName: "config_azure.schema.json", - docsLink: "config-db/scrapers/azure" + help: "config-db/scrapers/azure" }, { type: "form", @@ -81,7 +81,7 @@ export default function scraperTypes( configForm: KubernetesFileConfigsFormEditor, specsMapField: "kubernetesFile.0", schemaFileName: "config_kubernetesfile.schema.json", - docsLink: "config-db/scrapers/kubernetes-file" + help: "config-db/scrapers/kubernetes-file" }, { type: "form", @@ -97,7 +97,7 @@ export default function scraperTypes( configForm: SQLConfigsFormEditor, specsMapField: "sql.0", schemaFileName: "config_sql.schema.json", - docsLink: "config-db/scrapers/sql" + help: "config-db/scrapers/sql" }, { type: "form", @@ -113,7 +113,7 @@ export default function scraperTypes( configForm: TrivyConfigsFormEditor, specsMapField: "trivy.0", schemaFileName: "config_trivy.schema.json", - docsLink: "config-db/scrapers/trivy" + help: "config-db/scrapers/trivy" }, { @@ -130,7 +130,7 @@ export default function scraperTypes( configForm: FileConfigsFormEditor, specsMapField: "file.0", schemaFileName: "config_file.schema.json", - docsLink: "config-db/scrapers/file" + help: "config-db/scrapers/file" }, { @@ -163,9 +163,8 @@ export default function scraperTypes( configForm: AzureDevopsConfigsFormEditor, specsMapField: "azureDevops.0", schemaFileName: "config_azuredevops.schema.json", - docsLink: "config-db/scrapers/azure-devops" + help: "config-db/scrapers/azure-devops" }, - { type: "form", name: "githubActions", diff --git a/src/components/SpecEditor/SpecEditor.tsx b/src/components/SpecEditor/SpecEditor.tsx index 2fca942cc..5c68c9c20 100644 --- a/src/components/SpecEditor/SpecEditor.tsx +++ b/src/components/SpecEditor/SpecEditor.tsx @@ -1,5 +1,5 @@ import { Button } from "@flanksource-ui/ui/Buttons/Button"; -import { modalHelpLinkAtom } from "@flanksource-ui/ui/Modal"; +import { modalHelpLinkAtom, useModal } from "@flanksource-ui/ui/Modal"; import { useAtom } from "jotai"; import React, { useEffect, useState } from "react"; import { Icon } from "../../ui/Icons/Icon"; @@ -15,7 +15,7 @@ export type SpecTypeCommonFields = { loadSpec: () => Record; updateSpec: (spec: Record) => void; schemaFileName: string | undefined; - docsLink?: string; + help?: string; }; export type SpecTypeInputForm = SpecTypeCommonFields & { @@ -51,23 +51,27 @@ export type SpecType = SpecTypeInputForm | SpecTypeCode | SpecTypeCustom; export type SpecEditorProps = { types: SpecType[]; + footer?: boolean; format?: "json" | "yaml"; resourceInfo: Pick; selectedSpec?: string; onBack?: () => void; onDeleted?: () => void; + onTypeSelected?: (type?: SpecType) => void; }; export default function SpecEditor({ types, format = "yaml", + footer = true, resourceInfo, selectedSpec, onBack, - onDeleted = () => {} + onTypeSelected, + onDeleted = () => { } }: SpecEditorProps) { - const [, setModalHelpLink] = useAtom(modalHelpLinkAtom); - const [, setIntegrationsModalSubTitle] = useAtom(integrationsModalSubTitle); + + const { props, setProps } = useModal(); const [selectedSpecItem, setSelectedSpecItem] = useState< SpecType | undefined @@ -101,6 +105,7 @@ export default function SpecEditor({
{ setSelectedSpecItem(undefined); @@ -119,8 +124,12 @@ export default function SpecEditor({
{ setSelectedSpecItem(type); - setModalHelpLink(type.docsLink); - setIntegrationsModalSubTitle(type.label ?? type.name); + setProps({ + ...props, + helpLink: type.help, + title: `Add ${type.label}`, + + }) }} role={"button"} className="flex flex-col items-center space-y-2 justify-center p-2 border border-gray-300 hover:border-blue-200 hover:bg-gray-100 rounded-md text-center h-20" @@ -136,16 +145,7 @@ export default function SpecEditor({
))}
- {onBack && ( -
-
- )} +
)}
diff --git a/src/ui/Modal/FormikModal.stories.tsx b/src/ui/Modal/FormikModal.stories.tsx new file mode 100644 index 000000000..62af2b9fd --- /dev/null +++ b/src/ui/Modal/FormikModal.stories.tsx @@ -0,0 +1,66 @@ +import { FormikCodeEditor } from "@flanksource-ui/components/Forms/Formik/FormikCodeEditor"; +import FormikTextInput from "@flanksource-ui/components/Forms/Formik/FormikTextInput"; +import { StoryFn } from "@storybook/react"; +import { useState } from "react"; +import FormikModal from "./FormikModal"; + +export default { + title: "FormikModal", + component: FormikModal +}; + +const Template: StoryFn = (arg) => { + const [isOpen, setIsOpen] = useState(false); + const handleClose = () => setIsOpen(false); + return ( + <> + + + + ); +}; + +export const Variant1 = Template.bind({}); +Variant1.args = { + title: "Modal title", + formikFormProps: { + onDelete: (values) => console.log("deleted", values), + onSave: (values) => console.log("saved", values), + onBack: () => console.log("backed"), + showClose: true, + showDelete: true, + showSave: true + }, + size: "medium", + showExpand: true, + children: ( + <> + + + + + + + ) +}; diff --git a/src/ui/Modal/FormikModal.tsx b/src/ui/Modal/FormikModal.tsx new file mode 100644 index 000000000..44c0c47cb --- /dev/null +++ b/src/ui/Modal/FormikModal.tsx @@ -0,0 +1,278 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { XIcon } from "@heroicons/react/solid"; +import clsx from "clsx"; +import { Form, Formik } from "formik"; +import { Fragment, createContext, useContext, useRef, useState } from "react"; +import { BsArrowsFullscreen, BsFullscreenExit } from "react-icons/bs"; +import { IModalProps, useDialogSize } from "."; +import { Button } from "../Buttons/Button"; +import DialogButton from "../Buttons/DialogButton"; +import HelpLink from "../Buttons/HelpLink"; + +interface FormikFormModalProps { + showBack?: boolean; + showDelete?: boolean; + showSave?: boolean; + showClose?: boolean; + touchAllOnSubmit?: boolean; + saveTitle?: string; + backTitle?: string; + deleteTitle?: string; + closeTitle?: string; + onBack?: () => void; + onSave?: (values: Record) => void | Promise; + onDelete?: (values: Record) => void | Promise; +} + +export type FormikModalContextProps = { + props: FormikFormModalProps; + setProps: (props: FormikFormModalProps) => void; + values: Record; +}; + +export function useFormikModal() { + const { props, setProps: set, values } = useContext(FormikModalContext); + const setProps = (p: FormikFormModalProps) => { + set(p); + }; + return { + props, + setProps, + values + }; +} + +export const FormikModalContext = createContext( + undefined! +); + +type FormikModalProps = IModalProps & { + formikFormProps: FormikFormModalProps; + /** + * Can only be used during the initial render of the modal, after that, you + * can use Formik's setFieldValue to update the form values. + */ + initialValues?: Record; +}; + +export default function FormikModal({ + onClose = () => {}, + formikFormProps, + size = "medium", + helpLink, + showExpand, + bodyClass, + initialValues, + title, + titleClass, + hideCloseButton, + childClassName, + ...props +}: FormikModalProps) { + const [formikProps, setFormikProps] = useState(() => { + // set default values for formikFormProps + const { + saveTitle = "Save", + backTitle = "Back", + deleteTitle = "Delete", + closeTitle = "Close", + ...props + } = formikFormProps; + + return { + ...props, + saveTitle, + backTitle, + deleteTitle, + closeTitle + }; + }); + + const [_size, _setSize] = useState(size); + const sizeClass = useDialogSize(_size); + const formRef = useRef(null); + + const touchAllFormFields = ( + setFieldTouched: ( + field: string, + isTouched?: boolean | undefined, + shouldValidate?: boolean | undefined + ) => void + ) => { + [...(formRef.current?.elements || [])].forEach((element) => { + setFieldTouched(element.getAttribute("name")!, true, true); + }); + }; + return ( + + { + if (props.allowBackgroundClose) { + onClose(); + } + }} + {...props} + > +
+ {/* @ts-ignore */} + + + + + +
+
+

+ {title} +

+ + {helpLink && } + {showExpand && _size !== "full" && _size !== "small" && ( + _setSize("full")} + /> + )} + + {showExpand && _size === "full" && ( + _setSize("medium")} + /> + )} + + {!hideCloseButton && ( + + )} +
+ +
+ { + if (formikProps.onSave) { + formikProps.onSave(values); + } + }} + validateOnBlur + validateOnChange + > + {({ handleSubmit, handleReset, setFieldTouched, values }) => ( + +
{ + handleSubmit(e); + if (formikProps.touchAllOnSubmit) { + touchAllFormFields(setFieldTouched); + } + }} + onReset={handleReset} + className="flex flex-col h-full justify-between flex-1 space-y-4" + ref={formRef} + > +
+ {props.children} +
+ + {(formikProps.showBack || + formikProps.showSave || + formikProps.showDelete || + formikProps.showClose) && ( +
+
+ {formikProps.showBack && formikProps.onBack && ( +
+ +
+ )} + {formikProps.showDelete && + formikProps.onDelete && ( + + )} + + {formikProps.showClose && onClose && ( + + )} + {formikProps.showSave && ( + + )} +
+
+ )} +
+
+ )} +
+
+
+
+
+
+
+ ); +} diff --git a/src/ui/Modal/index.tsx b/src/ui/Modal/index.tsx index 949482526..292701e43 100644 --- a/src/ui/Modal/index.tsx +++ b/src/ui/Modal/index.tsx @@ -34,7 +34,7 @@ export const ModalContext = createContext(undefined!); */ export const modalHelpLinkAtom = atom(undefined); -export type ModalSize = "small" | "medium" | "large" | "full"; +export type ModalSize = "very-small" | "small" | "medium" | "large" | "full"; export interface IModalProps { title?: React.ReactNode; @@ -113,6 +113,7 @@ export function Modal({ const sizeClass = useDialogSize(_size); return ( + // @ts-ignore + {/* @ts-ignore */}