From 50553b0565c7e161962ec9aa3c92ba6baeed9f8a Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Thu, 11 Jul 2024 11:59:10 -0500 Subject: [PATCH 01/14] feat: interpolate install.install_inputs.values into the post_install_markdown (#46) * fix: update install inputs * feat: interpolate install.install_inputs.values into the post_install_markdown * feat: hijack enter for stepper navigation the stepper is wrapped in one big form element so pressing enter in any of the steps would submit the form. this is less than intutitive behavior since users are likely to interpret the sections of the stepper as distinct forms. as a result, we opt to hijack/intercept enters on the form and use it to navigate the stepper instead of submitting the form. now, the only way to submit the form is via the (re)provision button. * feat: interpolate install.id into the post install markdown --- app/[installer-slug]/actions.ts | 2 +- .../InstallStatusContent/InstallButton.tsx | 7 +- .../InstallStatusContent/index.tsx | 95 +++++++++++++------ components/InstallStepper/index.tsx | 16 +++- 4 files changed, 87 insertions(+), 33 deletions(-) diff --git a/app/[installer-slug]/actions.ts b/app/[installer-slug]/actions.ts index 22ba534..3144ead 100644 --- a/app/[installer-slug]/actions.ts +++ b/app/[installer-slug]/actions.ts @@ -125,7 +125,7 @@ export async function redeployInstall( return updateRes; } - if (reqBody.inputs.length > 0) { + if (Object.keys(reqBody.inputs).length > 0) { const inputsRes = await updateInputs(id, app, formData); if (inputsRes.error) { return inputsRes; diff --git a/components/InstallStepper/InstallStatusContent/InstallButton.tsx b/components/InstallStepper/InstallStatusContent/InstallButton.tsx index 8ea4e44..625ee73 100644 --- a/components/InstallStepper/InstallStatusContent/InstallButton.tsx +++ b/components/InstallStepper/InstallStatusContent/InstallButton.tsx @@ -4,9 +4,12 @@ export const InstallButton = ({ install }) => { const loading = install.status === "provisioning"; let label = "Create Install"; - if (install && install.id && install.id.length > 0) + if (install && install.id && install.id.length > 0) { label = "Reprovision Install"; - if (loading) label = "Provisioning"; + } + if (loading) { + label = "Provisioning"; + } return ( diff --git a/components/InstallStepper/index.tsx b/components/InstallStepper/index.tsx index 51b4d1f..4363eb0 100644 --- a/components/InstallStepper/index.tsx +++ b/components/InstallStepper/index.tsx @@ -148,7 +148,7 @@ const InstallStepper = ({ group={group} install_input_values={install_input_values} idx={idx} - setActiveStep={() => setActiveStep(idx + 2)} + setActiveStep={() => setActiveStep(idx + 1)} activeStep={activeStep} /> )); @@ -159,9 +159,9 @@ const InstallStepper = ({ activeClassName="border-4 border-step-active-border-color dark:border-step-active-border-color" completedClassName="border-4 border-step-complete-border-color dark:border-step-complete-border-color" key={idx} - onClick={() => setActiveStep(idx + 2)} + onClick={() => setActiveStep(idx + 1)} > - {idx + 3} + {idx + 2}
{group.display_name} @@ -200,13 +200,15 @@ const InstallStepper = ({
+ {...steps} + setActiveStep(1)} + onClick={() => setActiveStep(steps.length + 1)} > - 2 + {steps.length + 2}
AWS IAM Role @@ -214,8 +216,6 @@ const InstallStepper = ({
- {...steps} - setActiveStep(steps.length + 2)} > {steps.length + 3} -
Install Status @@ -248,18 +247,18 @@ const InstallStepper = ({ onClick={() => setActiveStep(0)} /> + {...stepContent} + setActiveStep(1)} + open={activeStep == steps.length + 1} + onClick={() => setActiveStep(steps.length + 1)} searchParams={searchParams} regions={regions} /> - {...stepContent} - setActiveStep(steps.length + 2)} diff --git a/theme.ts.example b/theme.ts.example index 1266e1b..cd80cc2 100644 --- a/theme.ts.example +++ b/theme.ts.example @@ -35,8 +35,9 @@ export default { "button-bg-active": primary["700"], }, boxShadow: { - "card-shadow": - "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", + // card shadow classes are applied not applied by default. only on landing page cards. + "card-shadow": "4px 4px 0 0 black, 0 1px 2px -1px black", + "card-shadow-dark": "4px 4px 0 0 white, 0 1px 2px -1px white", "button-shadow": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", }, From e32dd9bec724fe15f1cecbbc2f16699eaff4c4e4 Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Tue, 16 Jul 2024 15:48:25 -0500 Subject: [PATCH 07/14] fix: add padding to accordion header in azure section (#52) --- .../InstallStepper/CloudAccountContent.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/components/InstallStepper/CloudAccountContent.tsx b/components/InstallStepper/CloudAccountContent.tsx index 5aa6122..9e6c1de 100644 --- a/components/InstallStepper/CloudAccountContent.tsx +++ b/components/InstallStepper/CloudAccountContent.tsx @@ -49,7 +49,21 @@ export const CloudAccountContent = ({ {app?.cloud_platform === "azure" && ( <> - Azure Account + + Azure Account + From ccc805d6c235e17d02f08605258b007e5949842b Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Wed, 17 Jul 2024 10:01:14 -0500 Subject: [PATCH 08/14] Fd/fix rendering of large logos (#53) * theme: update default button shadow * logo: ensure largest logos render --- app/[installer-slug]/page.tsx | 4 ++-- theme.ts.example | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/[installer-slug]/page.tsx b/app/[installer-slug]/page.tsx index e2005c9..dd247a6 100644 --- a/app/[installer-slug]/page.tsx +++ b/app/[installer-slug]/page.tsx @@ -19,13 +19,13 @@ export default async function Installer({ params, searchParams }) { return ( <>
-
+
{"< Other installation options"} {installer?.metadata?.name} diff --git a/theme.ts.example b/theme.ts.example index cd80cc2..9ba7a77 100644 --- a/theme.ts.example +++ b/theme.ts.example @@ -38,8 +38,8 @@ export default { // card shadow classes are applied not applied by default. only on landing page cards. "card-shadow": "4px 4px 0 0 black, 0 1px 2px -1px black", "card-shadow-dark": "4px 4px 0 0 white, 0 1px 2px -1px white", - "button-shadow": - "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", + "button-shadow": "3px 3px 0 0 black, 0 1px 2px -1px black", + "button-shadow-dark": "3px 3px 0 0 white, 0 1px 2px -1px white", }, borderWidth: { "card-border-width": "1px", From 204fb07843f27100fad7807da2eafde0b1746d03 Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Wed, 24 Jul 2024 17:10:02 -0600 Subject: [PATCH 09/14] feat: delegation aware installer (#54) * feat: ignore .env files * feat: delegation aware installer only works for ios. is backwards compatible. --- .gitignore | 4 ++ components/AWSInstallerFormFields.tsx | 1 + .../InstallStepper/CloudAccountContent.tsx | 13 +++++- components/StepOneAWS.tsx | 42 ++++++++++++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index fd3dbb5..77a5cff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +.env +.env.local +.env.stage + # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies diff --git a/components/AWSInstallerFormFields.tsx b/components/AWSInstallerFormFields.tsx index 282c195..2aadfb6 100644 --- a/components/AWSInstallerFormFields.tsx +++ b/components/AWSInstallerFormFields.tsx @@ -25,6 +25,7 @@ export const AWSInstallerFormFields: FC<{ searchParams?: Record; regions: Array; aws_account: any | null; + aws_delegation_config: { iam_role_arn: "" } | null; }> = ({ searchParams = {}, regions, aws_account }) => { return (
diff --git a/components/InstallStepper/CloudAccountContent.tsx b/components/InstallStepper/CloudAccountContent.tsx index 9e6c1de..48da0db 100644 --- a/components/InstallStepper/CloudAccountContent.tsx +++ b/components/InstallStepper/CloudAccountContent.tsx @@ -7,8 +7,18 @@ import { Card, } from "@/components"; +interface AWSDelegationConfig { + iam_role_arn: string | null; +} +interface SandboxConfig { + aws_delegation_config: AWSDelegationConfig; +} + export const CloudAccountContent = ({ - app = { cloud_platform: "aws" }, + app = { + cloud_platform: "aws", + sandbox_config: { aws_delegation_config: { iam_role_arn: null } }, + }, aws_account = null, azure_account = null, open = false, @@ -38,6 +48,7 @@ export const CloudAccountContent = ({ { + const base_url = + "https://us-west-2.console.aws.amazon.com/cloudformation/home#/stacks/quickcreate"; + + let delegationEnabled = + aws_delegation_config && aws_delegation_config.iam_role_arn; + let params = {}; + + if (delegationEnabled) { + params = { + templateUrl: + "https://nuon-artifacts.s3.us-west-2.amazonaws.com/sandbox/aws-ecs/cloudformation-template.yaml", + stackName: `nuon-${app?.sandbox_config?.public_git_vcs_config?.directory}-permissions`, + param_DelegationRoleARN: `${aws_delegation_config?.iam_role_arn}`, + }; + } else { + params = { + templateUrl: + app?.sandbox_config?.artifacts?.cloudformation_stack_template, + stackName: `nuon-${app?.sandbox_config?.public_git_vcs_config?.directory}-permissions`, + param_DelegationRoleARN: `${aws_delegation_config?.iam_role_arn}`, + }; + } + + let searchParams = new URLSearchParams(params); + let url = new URL(base_url); + return url + "?" + searchParams.toString(); +}; + export const StepOneAWS: FC<{ app: Record }> = ({ app }) => { + let aws_delegation_config = app?.sandbox_config?.aws_delegation_config; + let quickCreateUrl = composeCloudformationQuickCreateUrl( + app, + aws_delegation_config, + ); return (

@@ -40,7 +80,7 @@ export const StepOneAWS: FC<{ app: Record }> = ({ app }) => { From 571b6c21341b27b809abef5dbe9d2f631c9387b3 Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Mon, 29 Jul 2024 16:05:30 -0500 Subject: [PATCH 10/14] fix: use the delegation template if delegation is enabled (#55) --- components/StepOneAWS.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/StepOneAWS.tsx b/components/StepOneAWS.tsx index 152b9b6..8daf48e 100644 --- a/components/StepOneAWS.tsx +++ b/components/StepOneAWS.tsx @@ -18,7 +18,7 @@ const composeCloudformationQuickCreateUrl = (app, aws_delegation_config) => { if (delegationEnabled) { params = { templateUrl: - "https://nuon-artifacts.s3.us-west-2.amazonaws.com/sandbox/aws-ecs/cloudformation-template.yaml", + "https://nuon-artifacts.s3.us-west-2.amazonaws.com/sandbox/aws-ecs/cloudformation-template-delegation.yaml", stackName: `nuon-${app?.sandbox_config?.public_git_vcs_config?.directory}-permissions`, param_DelegationRoleARN: `${aws_delegation_config?.iam_role_arn}`, }; From e685460c2fd333fb8dbface7db69df32414cbb7e Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Tue, 30 Jul 2024 11:16:40 -0500 Subject: [PATCH 11/14] feat: better color determination for last step (#56) the last step is special in the sense that its state depends on the state of the Stepper/Steps AND on the state of the Install. in this commit, we introduce a method for dynamically determining the last Step's activeClassName to achieve the desired state-based border color. --- components/InstallStepper/index.tsx | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/components/InstallStepper/index.tsx b/components/InstallStepper/index.tsx index 4363eb0..ce45f3b 100644 --- a/components/InstallStepper/index.tsx +++ b/components/InstallStepper/index.tsx @@ -13,6 +13,26 @@ import { GroupContent } from "./GroupContent"; import { InstallStatusContent } from "./InstallStatusContent"; import { ErrorAlert } from "./ErrorAlert"; +const getFinalStepActiveClassName = (status: string | null) => { + // default is the "orange"/in-progress colors + const defaultClassName = + "border-4 border-step-active-border-color dark:border-step-active-border-color"; + // success is the "green"/complete colors + let successClassName = + "border-4 border-step-complete-border-color dark:border-step-complete-border-color"; + // error is the "red" color. there is no themeable color for this state. + let errorClassName = "border-4 border-red-800 dark:border-red-800"; + + if (status === "active") { + return successClassName; + } else if (status === "error") { + return errorClassName; + } else if (status === "") { + return defaultClassName; + } + return defaultClassName; +}; + const InstallStepper = ({ app, existingInstall, @@ -177,6 +197,7 @@ const InstallStepper = ({ ); } + let finalStepActiveClassName = getFinalStepActiveClassName(install?.status); return (

+ {/* this step needs to be in the complete state if the installation is complete. as a result, the activeClassName + is determined dynamically. */} setActiveStep(steps.length + 2)} > From 92cb420f99373109c02d76db21e8866763e7b81b Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Tue, 30 Jul 2024 12:13:37 -0500 Subject: [PATCH 12/14] feat: installer status: more responsive flow (#57) --- components/InstallStepper/InstallStatusContent/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/InstallStepper/InstallStatusContent/index.tsx b/components/InstallStepper/InstallStatusContent/index.tsx index b928f5c..b0d03aa 100644 --- a/components/InstallStepper/InstallStatusContent/index.tsx +++ b/components/InstallStepper/InstallStatusContent/index.tsx @@ -71,7 +71,7 @@ export const InstallStatusContent = ({ -
+
From 8e243e2dab9f4e55c346eb0fd3a2a277cab162e7 Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Mon, 5 Aug 2024 08:43:07 -0500 Subject: [PATCH 13/14] feat: use og_image_url in installer metadata (#58) --- app/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/layout.tsx b/app/layout.tsx index 1edc944..b14866e 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -25,7 +25,7 @@ export async function generateMetadata(): Promise { type: "website", images: [ { - url: metadata.logo_url, + url: metadata.og_image_url, }, ], }, From 383fe0d4aaeeee63d2aca572f1a379d147d06820 Mon Sep 17 00:00:00 2001 From: Dat Boi Diego Date: Wed, 28 Aug 2024 17:54:20 -0500 Subject: [PATCH 14/14] feat: allow installer to pull the installerId from searchParams (#59) * chore: npm audit fix * feat: allow installer to pull the installerId from searchParams this required a refactor of the root layout and modifications to the way we handle `Metadata`. - [ ] figure out how to use `searchParams` in the `generateMetadata` - [ ] ensure the metadata is generated well regardless of what method we use to get the installerId. * feat: include metadata in all pages --- app/[installer-slug]/[install-id]/page.tsx | 55 +++++++++++++++- app/[installer-slug]/page.tsx | 54 ++++++++++++++- app/layout.tsx | 76 ++-------------------- app/page.tsx | 53 ++++++++++++++- common/index.ts | 32 ++++++--- components/Footer.tsx | 35 ++++++++++ package-lock.json | 27 ++++---- 7 files changed, 236 insertions(+), 96 deletions(-) create mode 100644 components/Footer.tsx diff --git a/app/[installer-slug]/[install-id]/page.tsx b/app/[installer-slug]/[install-id]/page.tsx index 30864dc..cc25099 100644 --- a/app/[installer-slug]/[install-id]/page.tsx +++ b/app/[installer-slug]/[install-id]/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata } from "next"; + import { createInstall, getInstall, @@ -5,16 +7,66 @@ import { } from "@/app/[installer-slug]/actions"; import { getAppBySlug, getInstaller } from "@/common"; import { Link } from "@/components"; + +import { Footer } from "@/components/Footer"; + import InstallStepper from "@/components/InstallStepper"; import { getCloudPlatformRegions } from "@/common"; +type Props = { + searchParams: { [key: string]: string | string[] | undefined }; +}; + +export async function generateMetadata({ + searchParams, +}: Props): Promise { + const { metadata } = await getInstaller( + searchParams ? searchParams.installerId : null, + ); + console.debug("[install] Generating Metdata"); + + // TODO(fd): we need to address this. + if (!!!metadata) { + console.debug("[install] No Metdata Found"); + return {}; + } + + return { + title: `Install Details | ${metadata.name}`, + description: metadata.description, + icons: { + icon: metadata.favicon_url, + shortcut: metadata.favicon_url, + }, + openGraph: { + title: metadata.name, + description: metadata.description, + type: "website", + images: [ + { + url: metadata.og_image_url, + }, + ], + }, + twitter: { + title: metadata.name, + description: metadata.description, + images: [ + { + url: metadata.logo_url, + }, + ], + }, + }; +} + export default async function Installer({ params, searchParams }) { const slug = params?.["installer-slug"]; const installId = params?.["install-id"]; const [app, installer, install] = await Promise.all([ getAppBySlug(slug), - getInstaller(), + getInstaller(searchParams ? searchParams.installerId : null), getInstall(installId), ]); const regions = await getCloudPlatformRegions(app.cloud_platform); @@ -55,6 +107,7 @@ export default async function Installer({ params, searchParams }) { regions={regions} /> +
); } diff --git a/app/[installer-slug]/page.tsx b/app/[installer-slug]/page.tsx index dd247a6..6aa3b3d 100644 --- a/app/[installer-slug]/page.tsx +++ b/app/[installer-slug]/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata } from "next"; + import { createInstall, getInstall, @@ -5,14 +7,62 @@ import { } from "@/app/[installer-slug]/actions"; import { getAppBySlug, getInstaller } from "@/common"; import { Link } from "@/components"; +import { Footer } from "@/components/Footer"; import InstallStepper from "@/components/InstallStepper"; import { getCloudPlatformRegions } from "@/common"; +type Props = { + searchParams: { [key: string]: string | string[] | undefined }; +}; + +export async function generateMetadata({ + searchParams, +}: Props): Promise { + const { metadata } = await getInstaller( + searchParams ? searchParams.installerId : null, + ); + console.debug("[installer] Generating Metdata"); + + // TODO(fd): we need to address this. + if (!!!metadata) { + console.debug("[installer] No Metdata Found"); + return {}; + } + + return { + title: `Installer | ${metadata.name}`, + description: metadata.description, + icons: { + icon: metadata.favicon_url, + shortcut: metadata.favicon_url, + }, + openGraph: { + title: metadata.name, + description: metadata.description, + type: "website", + images: [ + { + url: metadata.og_image_url, + }, + ], + }, + twitter: { + title: metadata.name, + description: metadata.description, + images: [ + { + url: metadata.logo_url, + }, + ], + }, + }; +} + export default async function Installer({ params, searchParams }) { const slug = params?.["installer-slug"]; const [app, installer] = await Promise.all([ getAppBySlug(slug), - getInstaller(), + getInstaller(searchParams ? searchParams.installerId : null), ]); const regions = await getCloudPlatformRegions(app.cloud_platform); @@ -39,7 +89,6 @@ export default async function Installer({ params, searchParams }) {

{app?.description}

-
+