diff --git a/dashboard/src/assets/eye-off.svg b/dashboard/src/assets/eye-off.svg new file mode 100644 index 0000000000..c9fc51a35f --- /dev/null +++ b/dashboard/src/assets/eye-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dashboard/src/assets/eye.svg b/dashboard/src/assets/eye.svg new file mode 100644 index 0000000000..f2885c1efa --- /dev/null +++ b/dashboard/src/assets/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dashboard/src/components/porter/ControlledInput.tsx b/dashboard/src/components/porter/ControlledInput.tsx index 33f4708da0..be33b51502 100644 --- a/dashboard/src/components/porter/ControlledInput.tsx +++ b/dashboard/src/components/porter/ControlledInput.tsx @@ -1,5 +1,12 @@ -import React from "react"; +import React, { useState } from "react"; import styled from "styled-components"; + +import eyeOff from "assets/eye-off.svg"; +import eye from "assets/eye.svg"; + +import Container from "./Container"; +import Icon from "./Icon"; +import Spacer from "./Spacer"; import Tooltip from "./Tooltip"; /* @@ -45,6 +52,11 @@ export const ControlledInput = React.forwardRef< }, ref ) => { + const [isVisible, setIsVisible] = useState(false); + const toggleVisibility = (): void => { + setIsVisible(!isVisible); + }; + return disabled && disabledTooltip ? ( @@ -72,29 +84,41 @@ export const ControlledInput = React.forwardRef< ) : ( - - {label && } - +
+ + + {label && } + + + {type === "password" && ( + <> + +
+ +
+ + )} +
{error && ( error {error} )} - +
); } ); diff --git a/dashboard/src/lib/addons/index.ts b/dashboard/src/lib/addons/index.ts index 9241e8c842..1346720c36 100644 --- a/dashboard/src/lib/addons/index.ts +++ b/dashboard/src/lib/addons/index.ts @@ -57,8 +57,11 @@ export const clientAddonValidator = z.object({ tailscaleConfigValidator, ]), }); +export type ClientAddonType = z.infer< + typeof clientAddonValidator +>["config"]["type"]; export type ClientAddon = z.infer & { - template: AddonTemplate; + template: AddonTemplate; }; export const legacyAddonValidator = z.object({ name: z.string(), diff --git a/dashboard/src/lib/addons/metabase.ts b/dashboard/src/lib/addons/metabase.ts index e38290afa2..d85ef5ed26 100644 --- a/dashboard/src/lib/addons/metabase.ts +++ b/dashboard/src/lib/addons/metabase.ts @@ -8,7 +8,7 @@ export const metabaseConfigValidator = z.object({ datastore: z .object({ host: z.string().nonempty(), - port: z.number(), + port: z.coerce.number(), databaseName: z.string().nonempty(), username: z.string().nonempty(), password: z.string().nonempty(), diff --git a/dashboard/src/lib/addons/template.ts b/dashboard/src/lib/addons/template.ts index 51cdbfc47d..ad43a7b624 100644 --- a/dashboard/src/lib/addons/template.ts +++ b/dashboard/src/lib/addons/template.ts @@ -7,7 +7,7 @@ import NewRelicForm from "main/home/add-on-dashboard/newrelic/NewRelicForm"; import TailscaleForm from "main/home/add-on-dashboard/tailscale/TailscaleForm"; import TailscaleOverview from "main/home/add-on-dashboard/tailscale/TailscaleOverview"; -import { type ClientAddon } from "."; +import { type ClientAddon, type ClientAddonType } from "."; export type AddonTemplateTag = | "Monitoring" @@ -39,34 +39,68 @@ export const DEFAULT_ADDON_TAB = { component: () => null, }; -export type AddonTemplate = { - type: ClientAddon["config"]["type"]; +export type AddonTemplate = { + type: T; displayName: string; description: string; icon: string; tags: AddonTemplateTag[]; tabs: AddonTab[]; // this what is rendered on the dashboard after the addon is deployed + defaultValues: ClientAddon["config"] & { type: T }; }; -export const ADDON_TEMPLATE_REDIS: AddonTemplate = { +export const ADDON_TEMPLATE_REDIS: AddonTemplate<"redis"> = { type: "redis", displayName: "Redis", description: "An in-memory database that persists on disk.", icon: "https://cdn4.iconfinder.com/data/icons/redis-2/1451/Untitled-2-512.png", tags: ["Database"], tabs: [], + defaultValues: { + type: "redis", + cpuCores: { + value: 0.5, + readOnly: false, + }, + ramMegabytes: { + value: 512, + readOnly: false, + }, + storageGigabytes: { + value: 1, + readOnly: false, + }, + password: "", + }, }; -export const ADDON_TEMPLATE_POSTGRES: AddonTemplate = { +export const ADDON_TEMPLATE_POSTGRES: AddonTemplate<"postgres"> = { type: "postgres", displayName: "Postgres", description: "An object-relational database system.", icon: "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/postgresql/postgresql-original.svg", tags: ["Database"], tabs: [], + defaultValues: { + type: "postgres", + cpuCores: { + value: 0.5, + readOnly: false, + }, + ramMegabytes: { + value: 512, + readOnly: false, + }, + storageGigabytes: { + value: 1, + readOnly: false, + }, + username: "postgres", + password: "postgres", + }, }; -export const ADDON_TEMPLATE_DATADOG: AddonTemplate = { +export const ADDON_TEMPLATE_DATADOG: AddonTemplate<"datadog"> = { type: "datadog", displayName: "DataDog", description: @@ -91,9 +125,19 @@ export const ADDON_TEMPLATE_DATADOG: AddonTemplate = { component: Settings, }, ], + defaultValues: { + type: "datadog", + cpuCores: 0.5, + ramMegabytes: 512, + site: "datadoghq.com", + apiKey: "", + loggingEnabled: false, + apmEnabled: false, + dogstatsdEnabled: false, + }, }; -export const ADDON_TEMPLATE_MEZMO: AddonTemplate = { +export const ADDON_TEMPLATE_MEZMO: AddonTemplate<"mezmo"> = { type: "mezmo", displayName: "Mezmo", description: "A popular logging management system.", @@ -117,9 +161,13 @@ export const ADDON_TEMPLATE_MEZMO: AddonTemplate = { component: Settings, }, ], + defaultValues: { + type: "mezmo", + ingestionKey: "", + }, }; -export const ADDON_TEMPLATE_METABASE: AddonTemplate = { +export const ADDON_TEMPLATE_METABASE: AddonTemplate<"metabase"> = { type: "metabase", displayName: "Metabase", description: "An open-source business intelligence tool.", @@ -142,9 +190,22 @@ export const ADDON_TEMPLATE_METABASE: AddonTemplate = { component: Settings, }, ], + defaultValues: { + type: "metabase", + exposedToExternalTraffic: true, + porterDomain: "", + customDomain: "", + datastore: { + host: "", + port: 0, + databaseName: "", + username: "", + password: "", + }, + }, }; -export const ADDON_TEMPLATE_NEWRELIC: AddonTemplate = { +export const ADDON_TEMPLATE_NEWRELIC: AddonTemplate<"newrelic"> = { type: "newrelic", displayName: "New Relic", description: "Monitor your applications and infrastructure.", @@ -168,9 +229,21 @@ export const ADDON_TEMPLATE_NEWRELIC: AddonTemplate = { component: Settings, }, ], + defaultValues: { + type: "newrelic", + licenseKey: "", + insightsKey: "", + personalApiKey: "", + accountId: "", + loggingEnabled: false, + kubeEventsEnabled: false, + metricsAdapterEnabled: false, + prometheusEnabled: false, + pixieEnabled: false, + }, }; -export const ADDON_TEMPLATE_TAILSCALE: AddonTemplate = { +export const ADDON_TEMPLATE_TAILSCALE: AddonTemplate<"tailscale"> = { type: "tailscale", displayName: "Tailscale", description: "A VPN for your applications and datastores.", @@ -199,12 +272,18 @@ export const ADDON_TEMPLATE_TAILSCALE: AddonTemplate = { component: Settings, }, ], + defaultValues: { + type: "tailscale", + authKey: "", + subnetRoutes: [], + }, }; -export const SUPPORTED_ADDON_TEMPLATES: AddonTemplate[] = [ - ADDON_TEMPLATE_DATADOG, - ADDON_TEMPLATE_MEZMO, - ADDON_TEMPLATE_METABASE, - ADDON_TEMPLATE_NEWRELIC, - ADDON_TEMPLATE_TAILSCALE, -]; +export const SUPPORTED_ADDON_TEMPLATES: Array> = + [ + ADDON_TEMPLATE_DATADOG, + ADDON_TEMPLATE_MEZMO, + ADDON_TEMPLATE_METABASE, + ADDON_TEMPLATE_NEWRELIC, + ADDON_TEMPLATE_TAILSCALE, + ]; diff --git a/dashboard/src/main/home/add-on-dashboard/AddonForm.tsx b/dashboard/src/main/home/add-on-dashboard/AddonForm.tsx index b93737e0ec..2303e32955 100644 --- a/dashboard/src/main/home/add-on-dashboard/AddonForm.tsx +++ b/dashboard/src/main/home/add-on-dashboard/AddonForm.tsx @@ -9,7 +9,7 @@ import { ControlledInput } from "components/porter/ControlledInput"; import Spacer from "components/porter/Spacer"; import Text from "components/porter/Text"; import VerticalSteps from "components/porter/VerticalSteps"; -import { defaultClientAddon, type ClientAddon } from "lib/addons"; +import { type ClientAddon, type ClientAddonType } from "lib/addons"; import { type AddonTemplate } from "lib/addons/template"; import { useAddonList } from "lib/hooks/useAddon"; import { useDefaultDeploymentTarget } from "lib/hooks/useDeploymentTarget"; @@ -20,10 +20,12 @@ import DashboardHeader from "../cluster-dashboard/DashboardHeader"; import ClusterContextProvider from "../infrastructure-dashboard/ClusterContextProvider"; import Configuration from "./common/Configuration"; -type Props = { - template: AddonTemplate; +type Props = { + template: AddonTemplate; }; -const AddonForm: React.FC = ({ template }) => { +const AddonForm = ({ + template, +}: Props): JSX.Element => { const { currentProject, currentCluster } = useContext(Context); const { defaultDeploymentTarget, isDefaultDeploymentTargetLoading } = useDefaultDeploymentTarget(); @@ -56,7 +58,12 @@ const AddonForm: React.FC = ({ template }) => { }, [watchName]); useEffect(() => { - reset(defaultClientAddon(template.type)); + reset({ + expanded: true, + name: { readOnly: false, value: template.type }, + config: template.defaultValues, + template, + }); }, [template]); useEffect(() => { diff --git a/dashboard/src/main/home/add-on-dashboard/AddonTemplates.tsx b/dashboard/src/main/home/add-on-dashboard/AddonTemplates.tsx index 0945c20f00..0b1122cac2 100644 --- a/dashboard/src/main/home/add-on-dashboard/AddonTemplates.tsx +++ b/dashboard/src/main/home/add-on-dashboard/AddonTemplates.tsx @@ -4,6 +4,7 @@ import styled from "styled-components"; import Back from "components/porter/Back"; import Spacer from "components/porter/Spacer"; +import { type ClientAddonType } from "lib/addons"; import { AddonTemplateTagColor, SUPPORTED_ADDON_TEMPLATES, @@ -47,31 +48,35 @@ const AddonTemplates: React.FC = () => { disableLineBreak /> - {SUPPORTED_ADDON_TEMPLATES.map((template: AddonTemplate) => { - return ( - { - history.push(`/addons/new?addon_name=${template.type}`); - }} - > - - {template.displayName} - {template.description} - - {template.tags.map((t) => ( - - {t} - - ))} - - ); - })} + {SUPPORTED_ADDON_TEMPLATES.map( + (template: AddonTemplate) => { + return ( + { + history.push(`/addons/new?addon_name=${template.type}`); + }} + > + + {template.displayName} + + {template.description} + + + {template.tags.map((t) => ( + + {t} + + ))} + + ); + } + )} ); diff --git a/dashboard/src/main/home/add-on-dashboard/datadog/DatadogForm.tsx b/dashboard/src/main/home/add-on-dashboard/datadog/DatadogForm.tsx index 03ac2573b6..cfed959394 100644 --- a/dashboard/src/main/home/add-on-dashboard/datadog/DatadogForm.tsx +++ b/dashboard/src/main/home/add-on-dashboard/datadog/DatadogForm.tsx @@ -39,10 +39,10 @@ const DatadogForm: React.FC = () => { DataDog API Key diff --git a/dashboard/src/main/home/add-on-dashboard/metabase/MetabaseForm.tsx b/dashboard/src/main/home/add-on-dashboard/metabase/MetabaseForm.tsx index cf53c1792f..09d6fabf78 100644 --- a/dashboard/src/main/home/add-on-dashboard/metabase/MetabaseForm.tsx +++ b/dashboard/src/main/home/add-on-dashboard/metabase/MetabaseForm.tsx @@ -133,6 +133,7 @@ const MetabaseDatastoreConnection: React.FC = () => { type="text" width="600px" {...register("config.datastore.host")} + placeholder="my-host.com" error={errors.config?.datastore?.host?.message} /> @@ -174,6 +175,7 @@ const MetabaseDatastoreConnection: React.FC = () => { type="text" width="600px" {...register("config.datastore.username")} + placeholder="my-username" error={errors.config?.datastore?.username?.message} /> @@ -184,9 +186,10 @@ const MetabaseDatastoreConnection: React.FC = () => { diff --git a/dashboard/src/main/home/add-on-dashboard/mezmo/MezmoForm.tsx b/dashboard/src/main/home/add-on-dashboard/mezmo/MezmoForm.tsx index 2984263319..1455e88b2f 100644 --- a/dashboard/src/main/home/add-on-dashboard/mezmo/MezmoForm.tsx +++ b/dashboard/src/main/home/add-on-dashboard/mezmo/MezmoForm.tsx @@ -28,10 +28,10 @@ const MezmoForm: React.FC = () => { Ingestion Key diff --git a/dashboard/src/main/home/add-on-dashboard/newrelic/NewRelicForm.tsx b/dashboard/src/main/home/add-on-dashboard/newrelic/NewRelicForm.tsx index 7e07d4f8f7..e8b1122d5a 100644 --- a/dashboard/src/main/home/add-on-dashboard/newrelic/NewRelicForm.tsx +++ b/dashboard/src/main/home/add-on-dashboard/newrelic/NewRelicForm.tsx @@ -28,7 +28,7 @@ const NewRelicForm: React.FC = () => { NewRelic License Key { NewRelic Insights Key { NewRelic Personal API Key {